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 fist_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
            run_to_session(fist_session_era_3_pruned);
283
1

            
284
1
            let slashes_after_bonding_period =
285
1
                ExternalValidatorSlashes::slashes(first_era_deferred);
286
1
            assert_eq!(slashes_after_bonding_period.len(), 0);
287
1
        });
288
1
}
289

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

            
308
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
309
1

            
310
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
311
1
            assert_eq!(reports.len(), 1);
312
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
313

            
314
1
            let deferred_era =
315
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
316
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
317
1
            assert_eq!(slashes.len(), 1);
318
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
319

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

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

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

            
355
1
            run_to_block(2);
356
1
            assert_ok!(ExternalValidators::remove_whitelisted(
357
1
                RuntimeOrigin::root(),
358
1
                AccountId::from(ALICE)
359
1
            ));
360

            
361
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
362
1

            
363
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
364
1
            assert_eq!(reports.len(), 1);
365
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
366

            
367
1
            let deferred_era =
368
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
369
1

            
370
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
371
1
            assert_eq!(slashes.len(), 1);
372
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
373

            
374
            // The first session in which the era 3 will be deferred is 18
375
            // 3 sessions per era
376
            // (externalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)*SessionsPerEra
377
            // formula is:
378

            
379
1
            let first_deferred_session =
380
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
381
1
                    * SessionsPerEra::get();
382
1
            run_to_session(first_deferred_session);
383
1

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

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

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

            
427
1
            run_to_block(2);
428
1

            
429
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
430
1

            
431
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
432
1
            assert_eq!(reports.len(), 1);
433
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
434

            
435
1
            let deferred_era =
436
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
437
1

            
438
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
439
1
            assert_eq!(slashes.len(), 1);
440
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
441

            
442
1
            let session_in_which_slashes_are_sent =
443
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
444
1
                    * SessionsPerEra::get();
445
1
            run_to_session(session_in_which_slashes_are_sent);
446
1

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

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

            
466
            // Slashes start being sent after the era block
467
            // They are scheduled as unprocessedSlashes
468
1
            run_block();
469
1

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

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

            
499
1
            // This one is related to slashes
500
1
            assert_eq!(
501
                outbound_msg_queue_event, 1,
502
                "MessageQueued event should be emitted"
503
            );
504

            
505
1
            assert_eq!(
506
                ext_validators_slashes_event, 1,
507
                "SlashesMessageSent event should be emitted"
508
            );
509

            
510
1
            let expected_slashes = vec![SlashData {
511
1
                encoded_validator_id: AccountId::from(ALICE).encode(),
512
1
                slash_fraction: Perbill::from_percent(100).deconstruct(),
513
1
                external_idx: 0,
514
1
            }];
515
1

            
516
1
            let expected_slashes_command = Command::ReportSlashes {
517
1
                era_index: 1u32,
518
1
                slashes: expected_slashes,
519
1
            };
520
1

            
521
1
            assert_eq!(
522
1
                slashes_command_found.unwrap(),
523
                expected_slashes_command,
524
                "Both slashes commands should match!"
525
            );
526

            
527
1
            assert_eq!(message_id_found.unwrap(), read_last_entropy().into());
528

            
529
            // EthereumOutboundQueue -> queue_message -> MessageQQueuePallet (queue)
530
            // MessageQueuePallet on_initialize -> dispatch queue -> process_message -> EthereumOutboundQueue_process_message
531
1
            let nonce = snowbridge_pallet_outbound_queue::Nonce::<Runtime>::get(
532
1
                snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL,
533
1
            );
534
1

            
535
1
            // We dispatched 2 already
536
1
            assert_eq!(nonce, 2);
537
1
        });
538
1
}
539

            
540
use {frame_support::traits::Get, tp_bridge::SlashData};
541

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

            
567
1
            assert_ok!(EthereumSystem::register_token(root_origin(), Box::new(token_location), snowbridge_core::AssetMetadata {
568
1
                name: "dance".as_bytes().to_vec().try_into().unwrap(),
569
1
                symbol: "dance".as_bytes().to_vec().try_into().unwrap(),
570
1
                decimals: 12,
571
1
		    }));
572

            
573

            
574
1
            run_to_block(2);
575
1

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

            
588
1
            let deferred_era = ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
589
1

            
590
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
591
1
            assert_eq!(slashes.len() as u32, page_limit +1);
592

            
593
1
            let session_in_which_slashes_are_sent =
594
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
595
1
                    * SessionsPerEra::get();
596
1
            run_to_session(session_in_which_slashes_are_sent);
597
1

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

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

            
617
            // We still have all slashes as unprocessed
618
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
619
1
            assert_eq!(unprocessed_slashes.len() as u32, page_limit +1);
620

            
621

            
622
            // Slashes start being sent after the era block
623
            // They are scheduled as unprocessedSlashes
624
1
            run_block();
625
1

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

            
636
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
637
1

            
638
1
            // This one is related to slashes
639
1
            assert_eq!(
640
                outbound_msg_queue_event, 1,
641
                "MessageQueued event should be emitted"
642
            );
643

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

            
647
1
            run_block();
648
1

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

            
659
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
660
1

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

            
667
            // Now we should have 0
668
1
            assert_eq!(unprocessed_slashes.len() as u32, 0);
669

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

            
676
1
            // We dispatched 3 already
677
1
            // 1 reward + 2 slashes
678
1
            assert_eq!(nonce, 3);
679
1
        });
680
1
}
681

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

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

            
713
1
            run_to_block(2);
714
1

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

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

            
730
1
            let deferred_era = ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
731
1

            
732
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
733
1
            assert_eq!(slashes.len() as u32, total_slashes_to_inject);
734

            
735
1
            let session_in_which_slashes_are_sent =
736
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
737
1
                    * SessionsPerEra::get();
738
1
            run_to_session(session_in_which_slashes_are_sent);
739
1

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

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

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

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

            
766
1
            let unprocessed_slashes = ExternalValidatorSlashes::unreported_slashes();
767
1

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

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

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

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

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

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

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

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

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