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 frame_support::traits::KeyOwnerProofSystem;
18
use sp_core::Pair;
19
use sp_runtime::Perbill;
20
use {
21
    crate::tests::common::*,
22
    crate::{
23
        BondingDuration, ExternalValidatorSlashes, ExternalValidators, Grandpa, Historical,
24
        SessionsPerEra, SlashDeferDuration,
25
    },
26
    frame_support::{assert_noop, assert_ok},
27
    sp_core::H256,
28
    sp_std::vec,
29
};
30

            
31
#[test]
32
1
fn invulnerables_cannot_be_slashed() {
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 non_invulnerables_can_be_slashed_with_babe() {
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
            assert_ok!(ExternalValidators::remove_whitelisted(
70
1
                RuntimeOrigin::root(),
71
1
                AccountId::from(ALICE)
72
1
            ));
73

            
74
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
75
1

            
76
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
77
1
            assert_eq!(reports.len(), 1);
78
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
79

            
80
1
            let slashes = ExternalValidatorSlashes::slashes(
81
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
82
1
            );
83
1
            assert_eq!(slashes.len(), 1);
84
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
85
            //the formula is (3*offenders/num_validators)^2
86
            // since we have 1 offender, 2 validators, this makes it a maximum of 1
87
1
            assert_eq!(slashes[0].percentage, Perbill::from_percent(100));
88
1
        });
89
1
}
90

            
91
#[test]
92
1
fn non_invulnerables_can_be_slashed_with_grandpa() {
93
1
    ExtBuilder::default()
94
1
        .with_balances(vec![
95
1
            // Alice gets 10k extra tokens for her mapping deposit
96
1
            (AccountId::from(ALICE), 210_000 * UNIT),
97
1
            (AccountId::from(BOB), 100_000 * UNIT),
98
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
99
1
            (AccountId::from(DAVE), 100_000 * UNIT),
100
1
        ])
101
1
        .build()
102
1
        .execute_with(|| {
103
1
            run_to_block(2);
104
1
            assert_ok!(ExternalValidators::remove_whitelisted(
105
1
                RuntimeOrigin::root(),
106
1
                AccountId::from(ALICE)
107
1
            ));
108

            
109
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
110
1

            
111
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
112
1
            assert_eq!(reports.len(), 1);
113
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
114

            
115
1
            let slashes = ExternalValidatorSlashes::slashes(
116
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
117
1
            );
118
1
            assert_eq!(slashes.len(), 1);
119
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
120
            //the formula is (3*offenders/num_validators)^2
121
            // since we have 1 offender, 2 validators, this makes it a maximum of 1
122
1
            assert_eq!(slashes[0].percentage, Perbill::from_percent(100));
123
1
        });
124
1
}
125

            
126
#[test]
127
1
fn test_slashing_percentage_applied_correctly() {
128
1
    ExtBuilder::default()
129
1
        .with_balances(vec![
130
1
            // Alice gets 10k extra tokens for her mapping deposit
131
1
            (AccountId::from(ALICE), 210_000 * UNIT),
132
1
            (AccountId::from(BOB), 100_000 * UNIT),
133
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
134
1
            (AccountId::from(DAVE), 100_000 * UNIT),
135
1
        ])
136
1
        .with_validators(vec![
137
1
            (AccountId::from(ALICE), 210 * UNIT),
138
1
            (AccountId::from(BOB), 100 * UNIT),
139
1
            (AccountId::from(CHARLIE), 100 * UNIT),
140
1
            (AccountId::from(DAVE), 100 * UNIT),
141
1
        ])
142
1
        .build()
143
1
        .execute_with(|| {
144
1
            run_to_block(2);
145
1
            assert_ok!(ExternalValidators::remove_whitelisted(
146
1
                RuntimeOrigin::root(),
147
1
                AccountId::from(ALICE)
148
1
            ));
149

            
150
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
151
1

            
152
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
153
1
            assert_eq!(reports.len(), 1);
154
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
155

            
156
1
            let slashes = ExternalValidatorSlashes::slashes(
157
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
158
1
            );
159
1
            assert_eq!(slashes.len(), 1);
160
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
161
            //the formula is (3*offenders/num_validators)^2
162
            // since we have 1 offender, 4 validators, this makes it a maximum of 0.75^2=0.5625
163
1
            assert_eq!(slashes[0].percentage, Perbill::from_parts(562500000));
164
1
        });
165
1
}
166

            
167
#[test]
168
1
fn test_slashes_are_not_additive_in_percentage() {
169
1
    ExtBuilder::default()
170
1
        .with_balances(vec![
171
1
            // Alice gets 10k extra tokens for her mapping deposit
172
1
            (AccountId::from(ALICE), 210_000 * UNIT),
173
1
            (AccountId::from(BOB), 100_000 * UNIT),
174
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
175
1
            (AccountId::from(DAVE), 100_000 * UNIT),
176
1
            (AccountId::from(EVE), 100_000 * UNIT),
177
1
        ])
178
1
        .with_validators(vec![
179
1
            (AccountId::from(ALICE), 210 * UNIT),
180
1
            (AccountId::from(BOB), 100 * UNIT),
181
1
            (AccountId::from(CHARLIE), 100 * UNIT),
182
1
            (AccountId::from(DAVE), 100 * UNIT),
183
1
            (AccountId::from(EVE), 100 * UNIT),
184
1
        ])
185
1
        .build()
186
1
        .execute_with(|| {
187
1
            run_to_block(2);
188
1
            assert_ok!(ExternalValidators::remove_whitelisted(
189
1
                RuntimeOrigin::root(),
190
1
                AccountId::from(ALICE)
191
1
            ));
192

            
193
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
194
1

            
195
1
            inject_grandpa_slash(&AccountId::from(ALICE).to_string());
196
1

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

            
199
1
            // we have 2 reports
200
1
            assert_eq!(reports.len(), 2);
201
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
202

            
203
1
            let slashes = ExternalValidatorSlashes::slashes(
204
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
205
1
            );
206
1

            
207
1
            // but a single slash
208
1
            assert_eq!(slashes.len(), 1);
209
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
210
            // the formula is (3*offenders/num_validators)^2
211
            // since we have 1 offender, 5 validators, this makes it 0.36
212
            // we injected 2 offences BUT THEY ARE NOT ADDITIVE
213
1
            assert_eq!(slashes[0].percentage, Perbill::from_parts(360000000));
214
1
        });
215
1
}
216
#[test]
217
1
fn test_slashes_are_cleaned_after_bonding_period() {
218
1
    ExtBuilder::default()
219
1
        .with_balances(vec![
220
1
            // Alice gets 10k extra tokens for her mapping deposit
221
1
            (AccountId::from(ALICE), 210_000 * UNIT),
222
1
            (AccountId::from(BOB), 100_000 * UNIT),
223
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
224
1
            (AccountId::from(DAVE), 100_000 * UNIT),
225
1
        ])
226
1
        .build()
227
1
        .execute_with(|| {
228
1
            run_to_block(2);
229
1
            assert_ok!(ExternalValidators::remove_whitelisted(
230
1
                RuntimeOrigin::root(),
231
1
                AccountId::from(ALICE)
232
1
            ));
233

            
234
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
235
1

            
236
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
237
1
            assert_eq!(reports.len(), 1);
238
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
239

            
240
1
            let slashes = ExternalValidatorSlashes::slashes(
241
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1,
242
1
            );
243
1
            assert_eq!(slashes.len(), 1);
244
            // The first session in which the era 3 will be pruned is
245
            // (28+3+1)*sessionsPerEra
246
1
            let fist_session_era_3_pruned = (ExternalValidators::current_era().unwrap()
247
1
                + SlashDeferDuration::get()
248
1
                + 1
249
1
                + BondingDuration::get()
250
1
                + 1)
251
1
                * SessionsPerEra::get();
252
1

            
253
1
            let first_era_deferred =
254
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
255
1

            
256
1
            println!("first era deferred {:?}", first_era_deferred);
257
1
            run_to_session(fist_session_era_3_pruned);
258
1

            
259
1
            let slashes_after_bonding_period =
260
1
                ExternalValidatorSlashes::slashes(first_era_deferred);
261
1
            assert_eq!(slashes_after_bonding_period.len(), 0);
262
1
        });
263
1
}
264

            
265
#[test]
266
1
fn test_slashes_can_be_cleared_before_deferred_period_applies() {
267
1
    ExtBuilder::default()
268
1
        .with_balances(vec![
269
1
            // Alice gets 10k extra tokens for her mapping deposit
270
1
            (AccountId::from(ALICE), 210_000 * UNIT),
271
1
            (AccountId::from(BOB), 100_000 * UNIT),
272
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
273
1
            (AccountId::from(DAVE), 100_000 * UNIT),
274
1
        ])
275
1
        .build()
276
1
        .execute_with(|| {
277
1
            run_to_block(2);
278
1
            assert_ok!(ExternalValidators::remove_whitelisted(
279
1
                RuntimeOrigin::root(),
280
1
                AccountId::from(ALICE)
281
1
            ));
282

            
283
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
284
1

            
285
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
286
1
            assert_eq!(reports.len(), 1);
287
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
288

            
289
1
            let deferred_era =
290
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
291
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
292
1
            assert_eq!(slashes.len(), 1);
293
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
294

            
295
            // Now let's clean it up
296
1
            assert_ok!(ExternalValidatorSlashes::cancel_deferred_slash(
297
1
                RuntimeOrigin::root(),
298
1
                deferred_era,
299
1
                vec![0]
300
1
            ));
301
1
            let slashes_after_cancel = ExternalValidatorSlashes::slashes(deferred_era);
302
1
            assert_eq!(slashes_after_cancel.len(), 0);
303
1
        });
304
1
}
305

            
306
#[test]
307
1
fn test_slashes_cannot_be_cancelled_after_defer_period() {
308
1
    ExtBuilder::default()
309
1
        .with_balances(vec![
310
1
            // Alice gets 10k extra tokens for her mapping deposit
311
1
            (AccountId::from(ALICE), 210_000 * UNIT),
312
1
            (AccountId::from(BOB), 100_000 * UNIT),
313
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
314
1
            (AccountId::from(DAVE), 100_000 * UNIT),
315
1
        ])
316
1
        .build()
317
1
        .execute_with(|| {
318
1
            run_to_block(2);
319
1
            assert_ok!(ExternalValidators::remove_whitelisted(
320
1
                RuntimeOrigin::root(),
321
1
                AccountId::from(ALICE)
322
1
            ));
323

            
324
1
            inject_babe_slash(&AccountId::from(ALICE).to_string());
325
1

            
326
1
            let reports = pallet_offences::Reports::<crate::Runtime>::iter().collect::<Vec<_>>();
327
1
            assert_eq!(reports.len(), 1);
328
1
            assert_eq!(ExternalValidators::current_era().unwrap(), 0);
329

            
330
1
            let deferred_era =
331
1
                ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1;
332
1

            
333
1
            let slashes = ExternalValidatorSlashes::slashes(deferred_era);
334
1
            assert_eq!(slashes.len(), 1);
335
1
            assert_eq!(slashes[0].validator, AccountId::from(ALICE));
336

            
337
            // The first session in which the era 3 will be deferred is 18
338
            // 3 sessions per era
339
            // (externalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)*SessionsPerEra
340
            // formula is:
341

            
342
1
            let first_deferred_session =
343
1
                (ExternalValidators::current_era().unwrap() + SlashDeferDuration::get() + 1)
344
1
                    * SessionsPerEra::get();
345
1
            run_to_session(first_deferred_session);
346
1

            
347
1
            assert_eq!(ExternalValidators::current_era().unwrap(), deferred_era);
348
            // Now let's clean it up
349
1
            assert_noop!(
350
1
                ExternalValidatorSlashes::cancel_deferred_slash(
351
1
                    RuntimeOrigin::root(),
352
1
                    deferred_era,
353
1
                    vec![0]
354
1
                ),
355
1
                pallet_external_validator_slashes::Error::<crate::Runtime>::DeferPeriodIsOver
356
1
            );
357
1
        });
358
1
}
359

            
360
7
fn inject_babe_slash(seed: &str) {
361
7
    let babe_key = get_pair_from_seed::<babe_primitives::AuthorityId>(seed);
362
7
    let equivocation_proof = generate_babe_equivocation_proof(&babe_key);
363
7

            
364
7
    // create the key ownership proof
365
7
    let key = (babe_primitives::KEY_TYPE, babe_key.public());
366
7
    let key_owner_proof = Historical::prove(key).unwrap();
367
7

            
368
7
    // report the equivocation
369
7
    assert_ok!(Babe::report_equivocation_unsigned(
370
7
        RuntimeOrigin::none(),
371
7
        Box::new(equivocation_proof),
372
7
        key_owner_proof,
373
7
    ));
374
7
}
375

            
376
2
fn inject_grandpa_slash(seed: &str) {
377
2
    let grandpa_key = get_pair_from_seed::<grandpa_primitives::AuthorityId>(seed);
378
2

            
379
2
    let set_id = Grandpa::current_set_id();
380
2

            
381
2
    let equivocation_proof = generate_grandpa_equivocation_proof(
382
2
        set_id,
383
2
        (1, H256::random(), 1, &grandpa_key),
384
2
        (1, H256::random(), 1, &grandpa_key),
385
2
    );
386
2
    // create the key ownership proof
387
2
    let key = (grandpa_primitives::KEY_TYPE, grandpa_key.public());
388
2
    let key_owner_proof = Historical::prove(key).unwrap();
389
2

            
390
2
    // report the equivocation
391
2
    assert_ok!(Grandpa::report_equivocation_unsigned(
392
2
        RuntimeOrigin::none(),
393
2
        Box::new(equivocation_proof),
394
2
        key_owner_proof,
395
2
    ));
396
2
}