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
#![cfg(test)]
18

            
19
use {
20
    crate::{
21
        tests::common::*, Historical, MaintenanceMode, RuntimeCall, RuntimeEvent, SessionKeys,
22
    },
23
    alloc::vec,
24
    frame_support::{assert_noop, assert_ok, traits::KeyOwnerProofSystem},
25
    pallet_assets::Instance1,
26
    sp_application_crypto::Pair,
27
    sp_runtime::traits::Dispatchable,
28
};
29

            
30
#[test]
31
1
fn maintenance_mode_can_be_set_by_sudo() {
32
1
    ExtBuilder::default()
33
1
        .with_balances(vec![
34
1
            // Alice gets 10k extra tokens for her mapping deposit
35
1
            (AccountId::from(ALICE), 210_000 * UNIT),
36
1
            (AccountId::from(BOB), 100_000 * UNIT),
37
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
38
1
            (AccountId::from(DAVE), 100_000 * UNIT),
39
1
        ])
40
1
        .with_sudo(AccountId::from(ALICE))
41
1
        .build()
42
1
        .execute_with(|| {
43
1
            run_to_block(2);
44
            // Root should be able to enter maintenance mode
45
1
            assert_ok!(MaintenanceMode::enter_maintenance_mode(root_origin(),));
46
1
        });
47
1
}
48

            
49
#[test]
50
1
fn asset_maintenance_mode_cannot_be_set_by_signed_origin() {
51
1
    ExtBuilder::default()
52
1
        .with_balances(vec![
53
1
            // Alice gets 10k extra tokens for her mapping deposit
54
1
            (AccountId::from(ALICE), 210_000 * UNIT),
55
1
            (AccountId::from(BOB), 100_000 * UNIT),
56
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
57
1
            (AccountId::from(DAVE), 100_000 * UNIT),
58
1
        ])
59
1
        .with_sudo(AccountId::from(ALICE))
60
1
        .build()
61
1
        .execute_with(|| {
62
1
            run_to_block(2);
63
            // Alice should not be able to execute extrinsic
64
1
            assert_noop!(
65
1
                MaintenanceMode::enter_maintenance_mode(origin_of(ALICE.into())),
66
1
                crate::DispatchError::BadOrigin
67
            );
68
1
        });
69
1
}
70

            
71
#[test]
72
1
fn test_filtered_calls_maintenance_mode() {
73
1
    ExtBuilder::default()
74
1
        .with_balances(vec![
75
1
            // Alice gets 10k extra tokens for her mapping deposit
76
1
            (AccountId::from(ALICE), 210_000 * UNIT),
77
1
            (AccountId::from(BOB), 100_000 * UNIT),
78
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
79
1
            (AccountId::from(DAVE), 100_000 * UNIT),
80
1
        ])
81
1
        .with_sudo(AccountId::from(ALICE))
82
1
        .build()
83
1
        .execute_with(|| {
84
1
            run_to_block(2);
85
1
            assert_ok!(MaintenanceMode::enter_maintenance_mode(root_origin()));
86

            
87
1
            assert_call_filtered(RuntimeCall::Balances(
88
1
                pallet_balances::Call::<Runtime>::transfer_allow_death {
89
1
                    dest: AccountId::from(BOB).into(),
90
1
                    value: 1 * UNIT,
91
1
                },
92
1
            ));
93

            
94
1
            assert_call_filtered(RuntimeCall::Registrar(
95
1
                runtime_common::paras_registrar::Call::<Runtime>::reserve {},
96
1
            ));
97

            
98
1
            let alice_keys = get_authority_keys_from_seed(&AccountId::from(ALICE).to_string());
99
1
            assert_call_filtered(RuntimeCall::Session(
100
1
                pallet_session::Call::<Runtime>::set_keys {
101
1
                    keys: SessionKeys {
102
1
                        babe: alice_keys.babe.clone(),
103
1
                        grandpa: alice_keys.grandpa.clone(),
104
1
                        para_validator: alice_keys.para_validator.clone(),
105
1
                        para_assignment: alice_keys.para_assignment.clone(),
106
1
                        authority_discovery: alice_keys.authority_discovery.clone(),
107
1
                        beefy: alice_keys.beefy.clone(),
108
1
                        nimbus: alice_keys.nimbus.clone(),
109
1
                    },
110
1
                    proof: vec![],
111
1
                },
112
1
            ));
113
1
            assert_call_filtered(RuntimeCall::System(frame_system::Call::<Runtime>::remark {
114
1
                remark: vec![],
115
1
            }));
116

            
117
1
            assert_call_filtered(RuntimeCall::PooledStaking(pallet_pooled_staking::Call::<
118
1
                Runtime,
119
1
            >::update_candidate_position {
120
1
                candidates: vec![],
121
1
            }));
122
1
            assert_call_filtered(RuntimeCall::Identity(
123
1
                pallet_identity::Call::<Runtime>::clear_identity {},
124
1
            ));
125
1
            assert_call_filtered(RuntimeCall::XcmPallet(
126
1
                pallet_xcm::Call::<Runtime>::remove_all_authorized_aliases {},
127
1
            ));
128
1
            assert_call_filtered(RuntimeCall::EthereumTokenTransfers(
129
1
                pallet_ethereum_token_transfers::Call::<Runtime>::transfer_native_token {
130
1
                    amount: 1 * UNIT,
131
1
                    recipient: sp_core::H160::random(),
132
1
                },
133
1
            ));
134
1
            assert_call_filtered(RuntimeCall::OnDemandAssignmentProvider(
135
1
                runtime_parachains::on_demand::Call::<Runtime>::place_order_allow_death {
136
1
                    max_amount: 1 * UNIT,
137
1
                    para_id: 1u32.into(),
138
1
                },
139
1
            ));
140
1
            assert_call_filtered(RuntimeCall::ContainerRegistrar(pallet_registrar::Call::<
141
1
                Runtime,
142
1
            >::deregister {
143
1
                para_id: 1u32.into(),
144
1
            }));
145
1
            assert_call_filtered(RuntimeCall::ServicesPayment(
146
1
                pallet_services_payment::Call::<Runtime>::purchase_credits {
147
1
                    para_id: 1u32.into(),
148
1
                    credit: 100,
149
1
                },
150
1
            ));
151
1
            assert_call_filtered(RuntimeCall::DataPreservers(pallet_data_preservers::Call::<
152
1
                Runtime,
153
1
            >::delete_profile {
154
1
                profile_id: 1u32.into(),
155
1
            }));
156
1
            assert_call_filtered(RuntimeCall::Hrmp(
157
1
                runtime_parachains::hrmp::Call::<Runtime>::hrmp_accept_open_channel {
158
1
                    sender: 1u32.into(),
159
1
                },
160
1
            ));
161
1
            assert_call_filtered(RuntimeCall::AssetRate(
162
1
                pallet_asset_rate::Call::<Runtime>::create {
163
1
                    asset_kind: Box::new(()),
164
1
                    rate: 1u128.into(),
165
1
                },
166
1
            ));
167
1
            assert_call_filtered(RuntimeCall::StreamPayment(pallet_stream_payment::Call::<
168
1
                Runtime,
169
1
            >::close_stream {
170
1
                stream_id: 1u64,
171
1
            }));
172
1
            assert_call_filtered(RuntimeCall::Treasury(
173
1
                pallet_treasury::Call::<Runtime>::check_status { index: 1u32 },
174
1
            ));
175
1
            assert_call_filtered(RuntimeCall::EthereumSystemV2(
176
1
                snowbridge_pallet_system_v2::Call::<Runtime>::set_operating_mode {
177
1
                    mode: snowbridge_outbound_queue_primitives::OperatingMode::Normal,
178
1
                },
179
1
            ));
180
1
            assert_call_filtered(RuntimeCall::ForeignAssets(pallet_assets::Call::<
181
1
                Runtime,
182
1
                Instance1,
183
1
            >::touch {
184
1
                id: 1u16,
185
1
            }));
186
1
            assert_call_filtered(RuntimeCall::ForeignAssetsCreator(
187
1
                pallet_foreign_asset_creator::Call::<Runtime>::destroy_foreign_asset {
188
1
                    asset_id: 1u16,
189
1
                },
190
1
            ));
191
1
            assert_call_filtered(RuntimeCall::BridgeRelayers(pallet_bridge_relayers::Call::<
192
1
                Runtime,
193
1
            >::deregister {}));
194
1
        });
195
1
}
196

            
197
#[test]
198
1
fn test_non_filtered_calls_maintenance_mode() {
199
1
    ExtBuilder::default()
200
1
        .with_balances(vec![
201
1
            // Alice gets 10k extra tokens for her mapping deposit
202
1
            (AccountId::from(ALICE), 210_000 * UNIT),
203
1
            (AccountId::from(BOB), 100_000 * UNIT),
204
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
205
1
            (AccountId::from(DAVE), 100_000 * UNIT),
206
1
        ])
207
1
        .with_sudo(AccountId::from(ALICE))
208
1
        .build()
209
1
        .execute_with(|| {
210
1
            run_to_block(2);
211
1
            assert_ok!(MaintenanceMode::enter_maintenance_mode(root_origin()));
212
1
            let babe_key = get_pair_from_seed::<babe_primitives::AuthorityId>(
213
1
                &AccountId::from(ALICE).to_string(),
214
            );
215
1
            let grandpa_key = get_pair_from_seed::<grandpa_primitives::AuthorityId>(
216
1
                &AccountId::from(ALICE).to_string(),
217
            );
218

            
219
1
            let babe_equivocation_proof = generate_babe_equivocation_proof(&babe_key);
220

            
221
1
            let set_id = Grandpa::current_set_id();
222

            
223
1
            let grandpa_equivocation_proof = generate_grandpa_equivocation_proof(
224
1
                set_id,
225
1
                (1, sp_core::H256::random(), 1, &grandpa_key),
226
1
                (1, sp_core::H256::random(), 1, &grandpa_key),
227
            );
228

            
229
1
            let grandpa_key_owner_proof =
230
1
                Historical::prove((grandpa_primitives::KEY_TYPE, grandpa_key.public())).unwrap();
231

            
232
1
            let babe_key_owner_proof =
233
1
                Historical::prove((babe_primitives::KEY_TYPE, babe_key.public())).unwrap();
234

            
235
1
            assert_call_not_filtered(RuntimeCall::Babe(
236
1
                pallet_babe::Call::<Runtime>::report_equivocation_unsigned {
237
1
                    equivocation_proof: Box::new(babe_equivocation_proof),
238
1
                    key_owner_proof: babe_key_owner_proof,
239
1
                },
240
1
            ));
241

            
242
1
            assert_call_not_filtered(RuntimeCall::Timestamp(
243
1
                pallet_timestamp::Call::<Runtime>::set { now: 1u64 },
244
1
            ));
245

            
246
1
            assert_call_not_filtered(RuntimeCall::CollatorConfiguration(
247
1
                pallet_configuration::Call::<Runtime>::set_max_collators { new: 1u32 },
248
1
            ));
249

            
250
1
            assert_call_not_filtered(RuntimeCall::TanssiInvulnerables(
251
1
                pallet_invulnerables::Call::<Runtime>::add_invulnerable {
252
1
                    who: AccountId::from(ALICE),
253
1
                },
254
1
            ));
255

            
256
1
            assert_call_not_filtered(RuntimeCall::AuthorNoting(pallet_author_noting::Call::<
257
1
                Runtime,
258
1
            >::kill_author_data {
259
1
                para_id: 2000u32.into(),
260
1
            }));
261

            
262
1
            assert_call_not_filtered(RuntimeCall::ExternalValidators(
263
1
                pallet_external_validators::Call::<Runtime>::skip_external_validators {
264
1
                    skip: true,
265
1
                },
266
1
            ));
267

            
268
1
            assert_call_not_filtered(RuntimeCall::EthereumInboundQueue(
269
1
                snowbridge_pallet_inbound_queue::Call::<Runtime>::set_operating_mode {
270
1
                    mode: snowbridge_core::BasicOperatingMode::Normal,
271
1
                },
272
1
            ));
273

            
274
1
            assert_call_not_filtered(RuntimeCall::Grandpa(
275
1
                pallet_grandpa::Call::<Runtime>::report_equivocation_unsigned {
276
1
                    equivocation_proof: Box::new(grandpa_equivocation_proof),
277
1
                    key_owner_proof: grandpa_key_owner_proof,
278
1
                },
279
1
            ));
280

            
281
1
            assert_call_not_filtered(RuntimeCall::EthereumBeaconClient(
282
1
                snowbridge_pallet_ethereum_client::Call::<Runtime>::force_checkpoint {
283
1
                    update: Box::new(snowbridge_pallet_ethereum_client::mock_electra::load_checkpoint_update_fixture()),
284
1
                },
285
1
            ));
286

            
287
1
            assert_call_not_filtered(RuntimeCall::Sudo(
288
1
                pallet_sudo::Call::<Runtime>::sudo {
289
1
                    call: Box::new(RuntimeCall::System(frame_system::Call::<Runtime>::remark{
290
1
                        remark: vec![]
291
1
                    })),
292
1
                },
293
1
            ));
294

            
295
1
            assert_call_not_filtered(RuntimeCall::Multisig(
296
1
                pallet_multisig::Call::<Runtime>::as_multi_threshold_1 {
297
1
                    other_signatories: vec![AccountId::from(BOB)],
298
1
                    call: Box::new(RuntimeCall::ExternalValidators(
299
1
                        pallet_external_validators::Call::<Runtime>::skip_external_validators {
300
1
                            skip: true,
301
1
                        },
302
1
                    )),
303
1
                },
304
1
            ));
305
1
        });
306
1
}
307

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

            
324
            // In this case, the call is not filtered
325
1
            assert_call_not_filtered(RuntimeCall::Utility(
326
1
                pallet_utility::Call::<Runtime>::batch {
327
1
                    calls: vec![RuntimeCall::Balances(
328
1
                        pallet_balances::Call::<Runtime>::transfer_allow_death {
329
1
                            dest: AccountId::from(BOB).into(),
330
1
                            value: 1 * UNIT,
331
1
                        },
332
1
                    )],
333
1
                },
334
1
            ));
335

            
336
1
            let batch_interrupt_events = System::events()
337
1
                .iter()
338
2
                .filter(|r| {
339
1
                    matches!(
340
1
                        r.event,
341
                        RuntimeEvent::Utility(pallet_utility::Event::BatchInterrupted { .. },)
342
                    )
343
2
                })
344
1
                .count();
345

            
346
1
            assert_eq!(
347
                batch_interrupt_events, 1,
348
                "batchInterrupted event should be emitted!"
349
            );
350
1
        });
351
1
}
352

            
353
20
fn assert_call_filtered(call: RuntimeCall) {
354
20
    assert_noop!(
355
20
        call.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::signed(
356
20
            AccountId::from(ALICE)
357
        )),
358
20
        frame_system::Error::<Runtime>::CallFiltered
359
    );
360
20
}
361

            
362
12
fn assert_call_not_filtered(call: RuntimeCall) {
363
12
    let res = call.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::signed(
364
12
        AccountId::from(ALICE),
365
    ));
366

            
367
12
    assert!(
368
12
        res != Err(frame_system::Error::<Runtime>::CallFiltered.into()),
369
        "Call is filtered"
370
    );
371
12
}