1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use {
18
    crate::{
19
        tests::common::*, BondingDuration, EthereumSystem, ExternalValidatorSlashes,
20
        ExternalValidators, Grandpa, Historical, RuntimeEvent, SessionsPerEra, SlashDeferDuration,
21
    },
22
    alloc::vec,
23
    frame_support::{assert_noop, assert_ok, traits::KeyOwnerProofSystem},
24
    parity_scale_codec::Encode,
25
    sp_core::{Pair, H256},
26
    sp_runtime::Perbill,
27
    tp_bridge::Command,
28
    xcm::{latest::prelude::*, VersionedLocation},
29
};
30

            
31
#[test]
32
1
fn invulnerables_cannot_be_slashed_with_babe() {
33
1
    ExtBuilder::default()
34
1
        .with_balances(vec![
35
1
            // Alice gets 10k extra tokens for her mapping deposit
36
1
            (AccountId::from(ALICE), 210_000 * UNIT),
37
1
            (AccountId::from(BOB), 100_000 * UNIT),
38
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
39
1
            (AccountId::from(DAVE), 100_000 * UNIT),
40
1
        ])
41
1
        .build()
42
1
        .execute_with(|| {
43
1
            run_to_block(2);
44
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
45
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
46
1
            assert_eq!(reports.len(), 1);
47
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
48

            
49
1
            let slashes = ExternalValidatorSlashes::slashes(
50
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
51
            );
52
1
            assert_eq!(slashes.len(), 0);
53
1
        });
54
1
}
55

            
56
#[test]
57
1
fn invulnerables_cannot_be_slashed_with_grandpa() {
58
1
    ExtBuilder::default()
59
1
        .with_balances(vec![
60
1
            // Alice gets 10k extra tokens for her mapping deposit
61
1
            (AccountId::from(ALICE), 210_000 * UNIT),
62
1
            (AccountId::from(BOB), 100_000 * UNIT),
63
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
64
1
            (AccountId::from(DAVE), 100_000 * UNIT),
65
1
        ])
66
1
        .build()
67
1
        .execute_with(|| {
68
1
            run_to_block(2);
69
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
70
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
71
1
            assert_eq!(reports.len(), 1);
72
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
73

            
74
1
            let slashes = ExternalValidatorSlashes::slashes(
75
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
76
            );
77
1
            assert_eq!(slashes.len(), 0);
78
1
        });
79
1
}
80

            
81
#[test]
82
1
fn non_invulnerables_can_be_slashed_with_babe() {
83
1
    ExtBuilder::default()
84
1
        .with_balances(vec![
85
1
            // Alice gets 10k extra tokens for her mapping deposit
86
1
            (AccountId::from(ALICE), 210_000 * UNIT),
87
1
            (AccountId::from(BOB), 100_000 * UNIT),
88
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
89
1
            (AccountId::from(DAVE), 100_000 * UNIT),
90
1
        ])
91
1
        .build()
92
1
        .execute_with(|| {
93
1
            run_to_block(2);
94
1
            assert_ok!(ExternalValidators::remove_whitelisted(
95
1
                RuntimeOrigin::root(),
96
1
                AccountId::from(ALICE)
97
            ));
98

            
99
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
100

            
101
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
102
1
            assert_eq!(reports.len(), 1);
103
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
104

            
105
1
            let slashes = ExternalValidatorSlashes::slashes(
106
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
107
            );
108
1
            assert_eq!(slashes.len(), 1);
109
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
110
            //the formula is (3*offenders/num_validators)^2
111
            // since we have 1 offender, 2 validators, this makes it a maximum of 1
112
1
            assert_eq!(slashes[0].percentage, Perbill::from_percent(100));
113
1
        });
114
1
}
115

            
116
#[test]
117
1
fn non_invulnerables_can_be_slashed_with_grandpa() {
118
1
    ExtBuilder::default()
119
1
        .with_balances(vec![
120
1
            // Alice gets 10k extra tokens for her mapping deposit
121
1
            (AccountId::from(ALICE), 210_000 * UNIT),
122
1
            (AccountId::from(BOB), 100_000 * UNIT),
123
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
124
1
            (AccountId::from(DAVE), 100_000 * UNIT),
125
1
        ])
126
1
        .build()
127
1
        .execute_with(|| {
128
1
            run_to_block(2);
129
1
            assert_ok!(ExternalValidators::remove_whitelisted(
130
1
                RuntimeOrigin::root(),
131
1
                AccountId::from(ALICE)
132
            ));
133

            
134
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
135

            
136
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
137
1
            assert_eq!(reports.len(), 1);
138
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
139

            
140
1
            let slashes = ExternalValidatorSlashes::slashes(
141
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
142
            );
143
1
            assert_eq!(slashes.len(), 1);
144
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
145
            //the formula is (3*offenders/num_validators)^2
146
            // since we have 1 offender, 2 validators, this makes it a maximum of 1
147
1
            assert_eq!(slashes[0].percentage, Perbill::from_percent(100));
148
1
        });
149
1
}
150

            
151
#[test]
152
1
fn test_slashing_percentage_applied_correctly() {
153
1
    ExtBuilder::default()
154
1
        .with_balances(vec![
155
1
            // Alice gets 10k extra tokens for her mapping deposit
156
1
            (AccountId::from(ALICE), 210_000 * UNIT),
157
1
            (AccountId::from(BOB), 100_000 * UNIT),
158
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
159
1
            (AccountId::from(DAVE), 100_000 * UNIT),
160
1
        ])
161
1
        .with_validators(vec![
162
1
            (AccountId::from(ALICE), 210 * UNIT),
163
1
            (AccountId::from(BOB), 100 * UNIT),
164
1
            (AccountId::from(CHARLIE), 100 * UNIT),
165
1
            (AccountId::from(DAVE), 100 * UNIT),
166
1
        ])
167
1
        .build()
168
1
        .execute_with(|| {
169
1
            run_to_block(2);
170
1
            assert_ok!(ExternalValidators::remove_whitelisted(
171
1
                RuntimeOrigin::root(),
172
1
                AccountId::from(ALICE)
173
            ));
174

            
175
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
176

            
177
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
178
1
            assert_eq!(reports.len(), 1);
179
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
180

            
181
1
            let slashes = ExternalValidatorSlashes::slashes(
182
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
183
            );
184
1
            assert_eq!(slashes.len(), 1);
185
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
186
            //the formula is (3*offenders/num_validators)^2
187
            // since we have 1 offender, 4 validators, this makes it a maximum of 0.75^2=0.5625
188
1
            assert_eq!(slashes[0].percentage, Perbill::from_parts(562500000));
189
1
        });
190
1
}
191

            
192
#[test]
193
1
fn test_slashes_are_not_additive_in_percentage() {
194
1
    ExtBuilder::default()
195
1
        .with_balances(vec![
196
1
            // Alice gets 10k extra tokens for her mapping deposit
197
1
            (AccountId::from(ALICE), 210_000 * UNIT),
198
1
            (AccountId::from(BOB), 100_000 * UNIT),
199
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
200
1
            (AccountId::from(DAVE), 100_000 * UNIT),
201
1
            (AccountId::from(EVE), 100_000 * UNIT),
202
1
        ])
203
1
        .with_validators(vec![
204
1
            (AccountId::from(ALICE), 210 * UNIT),
205
1
            (AccountId::from(BOB), 100 * UNIT),
206
1
            (AccountId::from(CHARLIE), 100 * UNIT),
207
1
            (AccountId::from(DAVE), 100 * UNIT),
208
1
            (AccountId::from(EVE), 100 * UNIT),
209
1
        ])
210
1
        .build()
211
1
        .execute_with(|| {
212
1
            run_to_block(2);
213
1
            assert_ok!(ExternalValidators::remove_whitelisted(
214
1
                RuntimeOrigin::root(),
215
1
                AccountId::from(ALICE)
216
            ));
217

            
218
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
219

            
220
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
221

            
222
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
223

            
224
            // we have 2 reports
225
1
            assert_eq!(reports.len(), 2);
226
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
227

            
228
1
            let slashes = ExternalValidatorSlashes::slashes(
229
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
230
            );
231

            
232
            // but a single slash
233
1
            assert_eq!(slashes.len(), 1);
234
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
235
            // the formula is (3*offenders/num_validators)^2
236
            // since we have 1 offender, 5 validators, this makes it 0.36
237
            // we injected 2 offences BUT THEY ARE NOT ADDITIVE
238
1
            assert_eq!(slashes[0].percentage, Perbill::from_parts(360000000));
239
1
        });
240
1
}
241
#[test]
242
1
fn test_slashes_are_cleaned_after_bonding_period() {
243
1
    ExtBuilder::default()
244
1
        .with_balances(vec![
245
1
            // Alice gets 10k extra tokens for her mapping deposit
246
1
            (AccountId::from(ALICE), 210_000 * UNIT),
247
1
            (AccountId::from(BOB), 100_000 * UNIT),
248
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
249
1
            (AccountId::from(DAVE), 100_000 * UNIT),
250
1
        ])
251
1
        .build()
252
1
        .execute_with(|| {
253
1
            run_to_block(2);
254
1
            assert_ok!(ExternalValidators::remove_whitelisted(
255
1
                RuntimeOrigin::root(),
256
1
                AccountId::from(ALICE)
257
            ));
258

            
259
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
260

            
261
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
262
1
            assert_eq!(reports.len(), 1);
263
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
264

            
265
1
            let slashes = ExternalValidatorSlashes::slashes(
266
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
267
            );
268
1
            assert_eq!(slashes.len(), 1);
269
            // The first session in which the era 3 will be pruned is
270
            // (28+3+1)*sessionsPerEra
271
1
            let first_session_era_3_pruned = (ExternalValidators::current_era().unwrap()
272
1
                + SlashDeferDuration::get()
273
1
                + 1
274
1
                + BondingDuration::get()
275
1
                + 1)
276
1
                * SessionsPerEra::get();
277

            
278
1
            let first_era_deferred =
279
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
280

            
281
1
            println!("first era deferred {:?}", first_era_deferred);
282
1
            println!("first_session_era_3_pruned {:?}", first_session_era_3_pruned);
283
1
            if first_session_era_3_pruned > 20 {
284
                panic!("Test will take too long, aborting. Make sure to compile with --features fast-runtime");
285
1
            }
286
1
            run_to_session(first_session_era_3_pruned);
287

            
288
1
            let slashes_after_bonding_period =
289
1
                ExternalValidatorSlashes::slashes(first_era_deferred);
290
1
            assert_eq!(slashes_after_bonding_period.len(), 0);
291
1
        });
292
1
}
293

            
294
#[test]
295
1
fn test_slashes_can_be_cleared_before_deferred_period_applies() {
296
1
    ExtBuilder::default()
297
1
        .with_balances(vec![
298
1
            // Alice gets 10k extra tokens for her mapping deposit
299
1
            (AccountId::from(ALICE), 210_000 * UNIT),
300
1
            (AccountId::from(BOB), 100_000 * UNIT),
301
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
302
1
            (AccountId::from(DAVE), 100_000 * UNIT),
303
1
        ])
304
1
        .build()
305
1
        .execute_with(|| {
306
1
            run_to_block(2);
307
1
            assert_ok!(ExternalValidators::remove_whitelisted(
308
1
                RuntimeOrigin::root(),
309
1
                AccountId::from(ALICE)
310
            ));
311

            
312
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
313

            
314
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
315
1
            assert_eq!(reports.len(), 1);
316
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
317

            
318
1
            let deferred_era =
319
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
320
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
321
1
            assert_eq!(slashes.len(), 1);
322
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
323

            
324
            // Now let's clean it up
325
1
            assert_ok!(ExternalValidatorSlashes::cancel_deferred_slash(
326
1
                RuntimeOrigin::root(),
327
1
                deferred_era,
328
1
                vec![0]
329
            ));
330
1
            let slashes_after_cancel = ExternalValidatorSlashes::slashes(deferred_era);
331
1
            assert_eq!(slashes_after_cancel.len(), 0);
332
1
        });
333
1
}
334

            
335
#[test]
336
1
fn test_slashes_cannot_be_cancelled_after_defer_period() {
337
1
    ExtBuilder::default()
338
1
        .with_balances(vec![
339
1
            // Alice gets 10k extra tokens for her mapping deposit
340
1
            (AccountId::from(ALICE), 210_000 * UNIT),
341
1
            (AccountId::from(BOB), 100_000 * UNIT),
342
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
343
1
            (AccountId::from(DAVE), 100_000 * UNIT),
344
1
        ])
345
1
        .build()
346
1
        .execute_with(|| {
347
1
            let token_location: VersionedLocation = Location::here().into();
348

            
349
1
            assert_ok!(EthereumSystem::register_token(
350
1
                root_origin(),
351
1
                Box::new(token_location),
352
1
                snowbridge_core::AssetMetadata {
353
1
                    name: "dance".as_bytes().to_vec().try_into().unwrap(),
354
1
                    symbol: "dance".as_bytes().to_vec().try_into().unwrap(),
355
1
                    decimals: 12,
356
1
                }
357
            ));
358

            
359
1
            run_to_block(2);
360
1
            assert_ok!(ExternalValidators::remove_whitelisted(
361
1
                RuntimeOrigin::root(),
362
1
                AccountId::from(ALICE)
363
            ));
364

            
365
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
366

            
367
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
368
1
            assert_eq!(reports.len(), 1);
369
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
370

            
371
1
            let deferred_era =
372
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
373

            
374
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
375
1
            assert_eq!(slashes.len(), 1);
376
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
377

            
378
            // The first session in which the era 3 will be deferred is 18
379
            // 3 sessions per era
380
            // (externalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)*SessionsPerEra
381
            // formula is:
382

            
383
1
            let first_deferred_session =
384
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
385
1
                    * SessionsPerEra::get();
386
1
            run_to_session(first_deferred_session);
387

            
388
1
            assert_eq!(ExternalValidators::current_era().unwrap(), deferred_era);
389
            // Now let's clean it up
390
1
            assert_noop!(
391
1
                ExternalValidatorSlashes::cancel_deferred_slash(
392
1
                    RuntimeOrigin::root(),
393
1
                    deferred_era,
394
1
                    vec![0]
395
                ),
396
1
                pallet_external_validator_slashes::Error::<crate::Runtime>::DeferPeriodIsOver
397
            );
398
1
        });
399
1
}
400

            
401
#[test]
402
1
fn test_slashes_are_sent_to_ethereum() {
403
1
    sp_tracing::try_init_simple();
404
1
    ExtBuilder::default()
405
1
        .with_balances(vec![
406
1
            // Alice gets 10k extra tokens for her mapping deposit
407
1
            (AccountId::from(ALICE), 210_000 * UNIT),
408
1
            (AccountId::from(BOB), 100_000 * UNIT),
409
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
410
1
            (AccountId::from(DAVE), 100_000 * UNIT),
411
1
        ])
412
1
        .with_validators(vec![])
413
1
        .with_external_validators(vec![
414
1
            (AccountId::from(ALICE), 210 * UNIT),
415
1
            (AccountId::from(BOB), 100 * UNIT),
416
1
        ])
417
1
        .build()
418
1
        .execute_with(|| {
419
1
            let token_location: VersionedLocation = Location::here().into();
420

            
421
1
            assert_ok!(EthereumSystem::register_token(
422
1
                root_origin(),
423
1
                Box::new(token_location),
424
1
                snowbridge_core::AssetMetadata {
425
1
                    name: "dance".as_bytes().to_vec().try_into().unwrap(),
426
1
                    symbol: "dance".as_bytes().to_vec().try_into().unwrap(),
427
1
                    decimals: 12,
428
1
                }
429
            ));
430

            
431
1
            run_to_block(2);
432

            
433
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
434

            
435
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
436
1
            assert_eq!(reports.len(), 1);
437
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
438

            
439
1
            let deferred_era =
440
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
441

            
442
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
443
1
            assert_eq!(slashes.len(), 1);
444
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
445

            
446
1
            let session_in_which_slashes_are_sent =
447
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
448
1
                    * SessionsPerEra::get();
449
1
            run_to_session(session_in_which_slashes_are_sent);
450

            
451
1
            let outbound_msg_queue_event = System::events()
452
1
                .iter()
453
11
                .filter(|r| {
454
10
                    matches!(
455
2
                        r.event,
456
                        RuntimeEvent::EthereumOutboundQueue(
457
                            snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
458
                        )
459
                    )
460
11
                })
461
1
                .count();
462

            
463
            // We have two reasons for sending messages:
464
            // 1, because on_era_end sends rewards
465
            // 2, because on_era_start sends slashes
466
            // Both session ends and session starts are done on_initialize of frame-sesssion
467
1
            assert_eq!(
468
                outbound_msg_queue_event, 1,
469
                "MessageQueued event should be emitted"
470
            );
471

            
472
            // Slashes start being sent after the era block
473
            // They are scheduled as unprocessedSlashes
474
1
            run_block();
475

            
476
1
            let outbound_msg_queue_event = System::events()
477
1
                .iter()
478
4
                .filter(|r| {
479
3
                    matches!(
480
2
                        r.event,
481
                        RuntimeEvent::EthereumOutboundQueue(
482
                            snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
483
                        )
484
                    )
485
4
                })
486
1
                .count();
487

            
488
1
            let mut slashes_command_found: Option<Command> = None;
489
1
            let mut message_id_found: Option<H256> = None;
490
1
            let ext_validators_slashes_event = System::events()
491
1
                .iter()
492
1
                .filter(|r| match &r.event {
493
                    RuntimeEvent::ExternalValidatorSlashes(
494
                        pallet_external_validator_slashes::Event::SlashesMessageSent {
495
1
                            slashes_command,
496
1
                            message_id,
497
                        },
498
                    ) => {
499
1
                        message_id_found = Some(*message_id);
500
1
                        slashes_command_found = Some(slashes_command.clone());
501
1
                        true
502
                    }
503
3
                    _ => false,
504
4
                })
505
1
                .count();
506

            
507
            // This one is related to slashes
508
1
            assert_eq!(
509
                outbound_msg_queue_event, 1,
510
                "MessageQueued event should be emitted"
511
            );
512

            
513
1
            assert_eq!(
514
                ext_validators_slashes_event, 1,
515
                "SlashesMessageSent event should be emitted"
516
            );
517

            
518
1
            let expected_slashes = vec![SlashData {
519
1
                encoded_validator_id: AccountId::from(ALICE).encode(),
520
1
                slash_fraction: Perbill::from_percent(100).deconstruct(),
521
1
                external_idx: 0,
522
1
            }];
523

            
524
1
            let expected_slashes_command = Command::ReportSlashes {
525
1
                era_index: 1u32,
526
1
                slashes: expected_slashes,
527
1
            };
528

            
529
1
            assert_eq!(
530
1
                slashes_command_found.unwrap(),
531
                expected_slashes_command,
532
                "Both slashes commands should match!"
533
            );
534

            
535
1
            assert_eq!(message_id_found.unwrap(), read_last_entropy().into());
536

            
537
            // EthereumOutboundQueue -> queue_message -> MessageQQueuePallet (queue)
538
            // MessageQueuePallet on_initialize -> dispatch queue -> process_message -> EthereumOutboundQueue_process_message
539
1
            let nonce = snowbridge_pallet_outbound_queue::Nonce::<Runtime>::get(
540
                snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL,
541
            );
542

            
543
            // We dispatched 2 already
544
1
            assert_eq!(nonce, 2);
545
1
        });
546
1
}
547

            
548
use {frame_support::traits::Get, tp_bridge::SlashData};
549

            
550
#[test]
551
1
fn test_slashes_are_sent_to_ethereum_accumulatedly() {
552
1
    sp_tracing::try_init_simple();
553
1
    ExtBuilder::default()
554
1
        .with_balances(vec![
555
1
            // Alice gets 10k extra tokens for her mapping deposit
556
1
            (AccountId::from(ALICE), 210_000 * UNIT),
557
1
            (AccountId::from(BOB), 100_000 * UNIT),
558
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
559
1
            (AccountId::from(DAVE), 100_000 * UNIT),
560
1
        ])
561
1
        .with_validators(
562
1
            vec![]
563
1
        )
564
1
        .with_external_validators(
565
1
            vec![
566
1
                (AccountId::from(ALICE), 210 * UNIT),
567
1
                (AccountId::from(BOB), 100 * UNIT),
568
1
            ]
569
1
        )
570
1
        .build()
571
1
        .execute_with(|| {
572
1
            let token_location: VersionedLocation = Location::here()
573
1
            .into();
574

            
575
1
            assert_ok!(EthereumSystem::register_token(root_origin(), Box::new(token_location), snowbridge_core::AssetMetadata {
576
1
                name: "dance".as_bytes().to_vec().try_into().unwrap(),
577
1
                symbol: "dance".as_bytes().to_vec().try_into().unwrap(),
578
1
                decimals: 12,
579
1
		    }));
580

            
581

            
582
1
            run_to_block(2);
583

            
584
            // We can inject arbitraqry slashes for arbitary accounts with root
585
1
            let page_limit: u32 = <Runtime as pallet_external_validator_slashes::Config>::QueuedSlashesProcessedPerBlock::get();
586
11
            for i in 0..page_limit +1 {
587
11
                assert_ok!(ExternalValidatorSlashes::force_inject_slash(
588
11
                    RuntimeOrigin::root(),
589
                    0,
590
11
                    AccountId::new(H256::from_low_u64_be(u64::from(i)).to_fixed_bytes()),
591
11
                    Perbill::from_percent(75),
592
                    1
593
                ));
594
            }
595

            
596
1
            let deferred_era = ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
597

            
598
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
599
1
            assert_eq!(slashes.len() as u32, page_limit +1);
600

            
601
1
            let session_in_which_slashes_are_sent =
602
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
603
1
                    * SessionsPerEra::get();
604
1
            run_to_session(session_in_which_slashes_are_sent);
605

            
606
1
            let outbound_msg_queue_event = System::events()
607
1
                .iter()
608
2
                .filter(|r| matches!(r.event,
609
                    RuntimeEvent::EthereumOutboundQueue(
610
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
611
                    )))
612
1
                .count();
613

            
614
            // We have two reasons for sending messages:
615
            // 1, because on_era_end sends rewards
616
            // 2, because on_era_start sends slashes
617
            // Both session ends and session starts are done on_initialize of frame-sesssion
618
1
            assert_eq!(
619
                outbound_msg_queue_event, 1,
620
                "MessageQueued event should be emitted"
621
            );
622

            
623
            // We still have all slashes as unprocessed
624
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
625
1
            assert_eq!(unprocessed_slashes.len() as u32, page_limit +1);
626

            
627

            
628
            // Slashes start being sent after the era block
629
            // They are scheduled as unprocessedSlashes
630
1
            run_block();
631

            
632
1
            let outbound_msg_queue_event = System::events()
633
1
                .iter()
634
2
                .filter(|r| matches!(r.event,
635
                    RuntimeEvent::EthereumOutboundQueue(
636
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
637
                    )))
638
1
                .count();
639

            
640
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
641

            
642
            // This one is related to slashes
643
1
            assert_eq!(
644
                outbound_msg_queue_event, 1,
645
                "MessageQueued event should be emitted"
646
            );
647

            
648
            // We still should have one pending unprocessed slash, to be sent in the next block
649
1
            assert_eq!(unprocessed_slashes.len() as u32, 1);
650

            
651
1
            run_block();
652

            
653
1
            let outbound_msg_queue_event = System::events()
654
1
                .iter()
655
2
                .filter(|r| matches!(r.event,
656
                    RuntimeEvent::EthereumOutboundQueue(
657
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
658
                    )
659
                ))
660
1
                .count();
661

            
662
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
663

            
664
            // This one is related to slashes
665
1
            assert_eq!(
666
                outbound_msg_queue_event, 1,
667
                "MessageQueued event should be emitted"
668
            );
669

            
670
            // Now we should have 0
671
1
            assert_eq!(unprocessed_slashes.len() as u32, 0);
672

            
673
            // EthereumOutboundQueue -> queue_message -> MessageQQueuePallet (queue)
674
            // MessageQueuePallet on_initialize -> dispatch queue -> process_message -> EthereumOutboundQueue_process_message
675
1
            let nonce = snowbridge_pallet_outbound_queue::Nonce::<Runtime>::get(
676
                snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL,
677
            );
678

            
679
            // We dispatched 3 already
680
            // 1 reward + 2 slashes
681
1
            assert_eq!(nonce, 3);
682
1
        });
683
1
}
684

            
685
#[test]
686
1
fn test_slashes_are_sent_to_ethereum_accumulate_until_next_era() {
687
1
    sp_tracing::try_init_simple();
688
1
    ExtBuilder::default()
689
1
        .with_balances(vec![
690
1
            // Alice gets 10k extra tokens for her mapping deposit
691
1
            (AccountId::from(ALICE), 210_000 * UNIT),
692
1
            (AccountId::from(BOB), 100_000 * UNIT),
693
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
694
1
            (AccountId::from(DAVE), 100_000 * UNIT),
695
1
        ])
696
1
        .with_validators(
697
1
            vec![]
698
1
        )
699
1
        .with_external_validators(
700
1
            vec![
701
1
                (AccountId::from(ALICE), 210 * UNIT),
702
1
                (AccountId::from(BOB), 100 * UNIT),
703
1
            ]
704
1
        )
705
1
        .build()
706
1
        .execute_with(|| {
707
1
            let token_location: VersionedLocation = Location::here()
708
1
            .into();
709

            
710
1
            assert_ok!(EthereumSystem::register_token(root_origin(), Box::new(token_location), snowbridge_core::AssetMetadata {
711
1
                name: "dance".as_bytes().to_vec().try_into().unwrap(),
712
1
                symbol: "dance".as_bytes().to_vec().try_into().unwrap(),
713
1
                decimals: 12,
714
1
		    }));
715

            
716
1
            run_to_block(2);
717

            
718
            // We can inject arbitraqry slashes for arbitary accounts with root
719
1
            let page_limit: u32 = <Runtime as pallet_external_validator_slashes::Config>::QueuedSlashesProcessedPerBlock::get();
720

            
721
1
            let blocks_in_era = crate::EpochDurationInBlocks::get() * SessionsPerEra::get();
722
1
            let total_slashes_to_inject = blocks_in_era*page_limit +1;
723
301
            for i in 0..total_slashes_to_inject {
724
301
                assert_ok!(ExternalValidatorSlashes::force_inject_slash(
725
301
                    RuntimeOrigin::root(),
726
                    0,
727
301
                    AccountId::new(H256::from_low_u64_be(u64::from(i)).to_fixed_bytes()),
728
301
                    Perbill::from_percent(75),
729
                    1
730
                ));
731
            }
732

            
733
1
            let deferred_era = ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
734

            
735
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
736
1
            assert_eq!(slashes.len() as u32, total_slashes_to_inject);
737

            
738
1
            let session_in_which_slashes_are_sent =
739
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
740
1
                    * SessionsPerEra::get();
741
1
            run_to_session(session_in_which_slashes_are_sent);
742

            
743
1
            let outbound_msg_queue_event = System::events()
744
1
                .iter()
745
2
                .filter(|r| matches!(r.event,
746
                    RuntimeEvent::EthereumOutboundQueue(
747
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
748
                    )
749
                ))
750
1
                .count();
751

            
752
            // We have two reasons for sending messages:
753
            // 1, because on_era_end sends rewards
754
            // 2, because on_era_start sends slashes
755
            // Both session ends and session starts are done on_initialize of frame-sesssion
756
1
            assert_eq!(
757
                outbound_msg_queue_event, 1,
758
                "MessageQueued event should be emitted"
759
            );
760

            
761
            // We still have all slashes as unprocessed
762
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
763
1
            assert_eq!(unprocessed_slashes.len() as u32, total_slashes_to_inject);
764

            
765
            // Running to the next era, but we should still have unprocessed
766
1
            run_to_session((ExternalValidators::current_era().unwrap() +1)*SessionsPerEra::get());
767

            
768
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
769

            
770
            // We still should have one pending unprocessed slash, to be sent in the next block
771
1
            assert_eq!(unprocessed_slashes.len() as u32, 1);
772

            
773
            // And in this case, we have 2 events
774
            // the rewards one plus the one where we sent remaining slashes
775
1
            let outbound_msg_queue_event = System::events()
776
1
                .iter()
777
3
                .filter(|r| matches!(r.event,
778
                    RuntimeEvent::EthereumOutboundQueue(
779
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
780
                    )
781
                ))
782
1
                .count();
783
1
            assert_eq!(
784
                outbound_msg_queue_event, 2,
785
                "MessageQueued event should be emitted"
786
            );
787
1
        });
788
1
}
789

            
790
8
fn inject_babe_slash(seed: &str) {
791
8
    let babe_key = get_pair_from_seed::<babe_primitives::AuthorityId>(seed);
792
8
    let equivocation_proof = generate_babe_equivocation_proof(&babe_key);
793

            
794
    // create the key ownership proof
795
8
    let key = (babe_primitives::KEY_TYPE, babe_key.public());
796
8
    let key_owner_proof = Historical::prove(key).unwrap();
797

            
798
    // report the equivocation
799
8
    assert_ok!(Babe::report_equivocation_unsigned(
800
8
        RuntimeOrigin::none(),
801
8
        Box::new(equivocation_proof),
802
8
        key_owner_proof,
803
    ));
804
8
}
805

            
806
3
fn inject_grandpa_slash(seed: &str) {
807
3
    let grandpa_key = get_pair_from_seed::<grandpa_primitives::AuthorityId>(seed);
808

            
809
3
    let set_id = Grandpa::current_set_id();
810

            
811
3
    let equivocation_proof = generate_grandpa_equivocation_proof(
812
3
        set_id,
813
3
        (1, H256::random(), 1, &grandpa_key),
814
3
        (1, H256::random(), 1, &grandpa_key),
815
    );
816
    // create the key ownership proof
817
3
    let key = (grandpa_primitives::KEY_TYPE, grandpa_key.public());
818
3
    let key_owner_proof = Historical::prove(key).unwrap();
819

            
820
    // report the equivocation
821
3
    assert_ok!(Grandpa::report_equivocation_unsigned(
822
3
        RuntimeOrigin::none(),
823
3
        Box::new(equivocation_proof),
824
3
        key_owner_proof,
825
    ));
826
3
}