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::tests::common::*,
19
    crate::{
20
        BondingDuration, EthereumSystem, ExternalValidatorSlashes, ExternalValidators, Grandpa,
21
        Historical, RuntimeEvent, SessionsPerEra, SlashDeferDuration,
22
    },
23
    frame_support::{assert_noop, assert_ok, traits::KeyOwnerProofSystem},
24
    parity_scale_codec::Encode,
25
    sp_core::{Pair, H256},
26
    sp_runtime::Perbill,
27
    sp_std::vec,
28
    tp_bridge::Command,
29
    xcm::latest::prelude::*,
30
    xcm::VersionedLocation,
31
};
32

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

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

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

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

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

            
101
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
102
1

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

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

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

            
136
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
137
1

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

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

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

            
177
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
178
1

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

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

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

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

            
222
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
223
1

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

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

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

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

            
261
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
262
1

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

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

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

            
283
1
            println!("first era deferred {:?}", first_era_deferred);
284
1
            run_to_session(fist_session_era_3_pruned);
285
1

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

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

            
310
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
311
1

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

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

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

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

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

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

            
363
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
364
1

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

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

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

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

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

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

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

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

            
429
1
            run_to_block(2);
430
1

            
431
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
432
1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
529
1
            assert_eq!(message_id_found.unwrap(), read_last_entropy().into());
530

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

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

            
542
use frame_support::traits::Get;
543
use tp_bridge::SlashData;
544

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

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

            
576

            
577
1
            run_to_block(2);
578
1

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

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

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

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

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

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

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

            
624

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

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

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

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

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

            
650
1
            run_block();
651
1

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

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

            
664
1
            // 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
1
                snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL,
677
1
            );
678
1

            
679
1
            // We dispatched 3 already
680
1
            // 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
1

            
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
1

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

            
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
301
                    0,
727
301
                    AccountId::new(H256::from_low_u64_be(i as u64).to_fixed_bytes()),
728
301
                    Perbill::from_percent(75),
729
301
                    1
730
301
                ));
731
            }
732

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

            
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
1

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

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

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

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

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

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

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

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

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

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

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

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

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

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