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
    frame_support::{assert_noop, assert_ok, traits::KeyOwnerProofSystem},
23
    parity_scale_codec::Encode,
24
    sp_core::{Pair, H256},
25
    sp_runtime::Perbill,
26
    sp_std::vec,
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
1
            );
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
1
            );
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
1
            ));
98

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

            
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
1
            );
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
1
            ));
133

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

            
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
1
            );
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
1
            ));
174

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

            
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
1
            );
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
1
            ));
217

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

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

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

            
224
1
            // 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
1
            );
231
1

            
232
1
            // 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
1
            ));
258

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

            
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
1
            );
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
1

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

            
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
1

            
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
1
            ));
311

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

            
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
1
            ));
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
1

            
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
1
            ));
358

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

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

            
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
1

            
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
1

            
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
1
                ),
396
1
                pallet_external_validator_slashes::Error::<crate::Runtime>::DeferPeriodIsOver
397
1
            );
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
1

            
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
1
            ));
430

            
431
1
            run_to_block(2);
432
1

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

            
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
1

            
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
1

            
451
1
            let outbound_msg_queue_event = System::events()
452
1
                .iter()
453
10
                .filter(|r| match r.event {
454
                    RuntimeEvent::EthereumOutboundQueue(
455
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
456
1
                    ) => true,
457
9
                    _ => false,
458
10
                })
459
1
                .count();
460
1

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

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

            
474
1
            let outbound_msg_queue_event = System::events()
475
1
                .iter()
476
4
                .filter(|r| match r.event {
477
                    RuntimeEvent::EthereumOutboundQueue(
478
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
479
1
                    ) => true,
480
3
                    _ => false,
481
4
                })
482
1
                .count();
483
1

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

            
503
1
            // This one is related to slashes
504
1
            assert_eq!(
505
                outbound_msg_queue_event, 1,
506
                "MessageQueued event should be emitted"
507
            );
508

            
509
1
            assert_eq!(
510
                ext_validators_slashes_event, 1,
511
                "SlashesMessageSent event should be emitted"
512
            );
513

            
514
1
            let expected_slashes = vec![SlashData {
515
1
                encoded_validator_id: AccountId::from(ALICE).encode(),
516
1
                slash_fraction: Perbill::from_percent(100).deconstruct(),
517
1
                external_idx: 0,
518
1
            }];
519
1

            
520
1
            let expected_slashes_command = Command::ReportSlashes {
521
1
                era_index: 1u32,
522
1
                slashes: expected_slashes,
523
1
            };
524
1

            
525
1
            assert_eq!(
526
1
                slashes_command_found.unwrap(),
527
                expected_slashes_command,
528
                "Both slashes commands should match!"
529
            );
530

            
531
1
            assert_eq!(message_id_found.unwrap(), read_last_entropy().into());
532

            
533
            // EthereumOutboundQueue -> queue_message -> MessageQQueuePallet (queue)
534
            // MessageQueuePallet on_initialize -> dispatch queue -> process_message -> EthereumOutboundQueue_process_message
535
1
            let nonce = snowbridge_pallet_outbound_queue::Nonce::<Runtime>::get(
536
1
                snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL,
537
1
            );
538
1

            
539
1
            // We dispatched 2 already
540
1
            assert_eq!(nonce, 2);
541
1
        });
542
1
}
543

            
544
use {frame_support::traits::Get, tp_bridge::SlashData};
545

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

            
571
1
            assert_ok!(EthereumSystem::register_token(root_origin(), Box::new(token_location), snowbridge_core::AssetMetadata {
572
1
                name: "dance".as_bytes().to_vec().try_into().unwrap(),
573
1
                symbol: "dance".as_bytes().to_vec().try_into().unwrap(),
574
1
                decimals: 12,
575
1
		    }));
576

            
577

            
578
1
            run_to_block(2);
579
1

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

            
592
1
            let deferred_era = ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
593
1

            
594
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
595
1
            assert_eq!(slashes.len() as u32, page_limit +1);
596

            
597
1
            let session_in_which_slashes_are_sent =
598
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
599
1
                    * SessionsPerEra::get();
600
1
            run_to_session(session_in_which_slashes_are_sent);
601
1

            
602
1
            let outbound_msg_queue_event = System::events()
603
1
                .iter()
604
10
                .filter(|r| match r.event {
605
                    RuntimeEvent::EthereumOutboundQueue(
606
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
607
1
                    ) => true,
608
9
                    _ => false,
609
10
                })
610
1
                .count();
611
1

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

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

            
625

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

            
630
1
            let outbound_msg_queue_event = System::events()
631
1
                .iter()
632
4
                .filter(|r| match r.event {
633
                    RuntimeEvent::EthereumOutboundQueue(
634
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
635
1
                    ) => true,
636
3
                    _ => false,
637
4
                })
638
1
                .count();
639
1

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

            
642
1
            // 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
1

            
653
1
            let outbound_msg_queue_event = System::events()
654
1
                .iter()
655
4
                .filter(|r| match r.event {
656
                    RuntimeEvent::EthereumOutboundQueue(
657
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
658
1
                    ) => true,
659
3
                    _ => false,
660
4
                })
661
1
                .count();
662
1

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

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

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

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

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

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

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

            
717
1
            run_to_block(2);
718
1

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

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

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

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

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

            
744
1
            let outbound_msg_queue_event = System::events()
745
1
                .iter()
746
10
                .filter(|r| match r.event {
747
                    RuntimeEvent::EthereumOutboundQueue(
748
                        snowbridge_pallet_outbound_queue::Event::MessageQueued { .. },
749
1
                    ) => true,
750
9
                    _ => false,
751
10
                })
752
1
                .count();
753
1

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

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

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

            
770
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
771
1

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

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

            
793
8
fn inject_babe_slash(seed: &str) {
794
8
    let babe_key = get_pair_from_seed::<babe_primitives::AuthorityId>(seed);
795
8
    let equivocation_proof = generate_babe_equivocation_proof(&babe_key);
796
8

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

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

            
809
3
fn inject_grandpa_slash(seed: &str) {
810
3
    let grandpa_key = get_pair_from_seed::<grandpa_primitives::AuthorityId>(seed);
811
3

            
812
3
    let set_id = Grandpa::current_set_id();
813
3

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

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