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::StreamPayment(pallet_stream_payment::Call::<
162
1
                Runtime,
163
1
            >::close_stream {
164
1
                stream_id: 1u64,
165
1
            }));
166
1
            assert_call_filtered(RuntimeCall::Treasury(
167
1
                pallet_treasury::Call::<Runtime>::check_status { index: 1u32 },
168
1
            ));
169
1
            assert_call_filtered(RuntimeCall::EthereumSystemV2(
170
1
                snowbridge_pallet_system_v2::Call::<Runtime>::set_operating_mode {
171
1
                    mode: snowbridge_outbound_queue_primitives::OperatingMode::Normal,
172
1
                },
173
1
            ));
174
1
            assert_call_filtered(RuntimeCall::ForeignAssets(pallet_assets::Call::<
175
1
                Runtime,
176
1
                Instance1,
177
1
            >::touch {
178
1
                id: 1u16,
179
1
            }));
180
1
            assert_call_filtered(RuntimeCall::ForeignAssetsCreator(
181
1
                pallet_foreign_asset_creator::Call::<Runtime>::destroy_foreign_asset {
182
1
                    asset_id: 1u16,
183
1
                },
184
1
            ));
185
1
            assert_call_filtered(RuntimeCall::BridgeRelayers(pallet_bridge_relayers::Call::<
186
1
                Runtime,
187
1
            >::deregister {}));
188
1
        });
189
1
}
190

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

            
213
1
            let babe_equivocation_proof = generate_babe_equivocation_proof(&babe_key);
214

            
215
1
            let set_id = Grandpa::current_set_id();
216

            
217
1
            let grandpa_equivocation_proof = generate_grandpa_equivocation_proof(
218
1
                set_id,
219
1
                (1, sp_core::H256::random(), 1, &grandpa_key),
220
1
                (1, sp_core::H256::random(), 1, &grandpa_key),
221
            );
222

            
223
1
            let grandpa_key_owner_proof =
224
1
                Historical::prove((grandpa_primitives::KEY_TYPE, grandpa_key.public())).unwrap();
225

            
226
1
            let babe_key_owner_proof =
227
1
                Historical::prove((babe_primitives::KEY_TYPE, babe_key.public())).unwrap();
228

            
229
1
            assert_call_not_filtered(RuntimeCall::Babe(
230
1
                pallet_babe::Call::<Runtime>::report_equivocation_unsigned {
231
1
                    equivocation_proof: Box::new(babe_equivocation_proof),
232
1
                    key_owner_proof: babe_key_owner_proof,
233
1
                },
234
1
            ));
235

            
236
1
            assert_call_not_filtered(RuntimeCall::Timestamp(
237
1
                pallet_timestamp::Call::<Runtime>::set { now: 1u64 },
238
1
            ));
239

            
240
1
            assert_call_not_filtered(RuntimeCall::CollatorConfiguration(
241
1
                pallet_configuration::Call::<Runtime>::set_max_collators { new: 1u32 },
242
1
            ));
243

            
244
1
            assert_call_not_filtered(RuntimeCall::TanssiInvulnerables(
245
1
                pallet_invulnerables::Call::<Runtime>::add_invulnerable {
246
1
                    who: AccountId::from(ALICE),
247
1
                },
248
1
            ));
249

            
250
1
            assert_call_not_filtered(RuntimeCall::AuthorNoting(pallet_author_noting::Call::<
251
1
                Runtime,
252
1
            >::kill_author_data {
253
1
                para_id: 2000u32.into(),
254
1
            }));
255

            
256
1
            assert_call_not_filtered(RuntimeCall::ExternalValidators(
257
1
                pallet_external_validators::Call::<Runtime>::skip_external_validators {
258
1
                    skip: true,
259
1
                },
260
1
            ));
261

            
262
1
            assert_call_not_filtered(RuntimeCall::EthereumInboundQueue(
263
1
                snowbridge_pallet_inbound_queue::Call::<Runtime>::set_operating_mode {
264
1
                    mode: snowbridge_core::BasicOperatingMode::Normal,
265
1
                },
266
1
            ));
267

            
268
1
            assert_call_not_filtered(RuntimeCall::Grandpa(
269
1
                pallet_grandpa::Call::<Runtime>::report_equivocation_unsigned {
270
1
                    equivocation_proof: Box::new(grandpa_equivocation_proof),
271
1
                    key_owner_proof: grandpa_key_owner_proof,
272
1
                },
273
1
            ));
274

            
275
1
            assert_call_not_filtered(RuntimeCall::EthereumBeaconClient(
276
1
                snowbridge_pallet_ethereum_client::Call::<Runtime>::force_checkpoint {
277
1
                    update: Box::new(snowbridge_pallet_ethereum_client::mock_electra::load_checkpoint_update_fixture()),
278
1
                },
279
1
            ));
280

            
281
1
            assert_call_not_filtered(RuntimeCall::Sudo(
282
1
                pallet_sudo::Call::<Runtime>::sudo {
283
1
                    call: Box::new(RuntimeCall::System(frame_system::Call::<Runtime>::remark{
284
1
                        remark: vec![]
285
1
                    })),
286
1
                },
287
1
            ));
288

            
289
1
            assert_call_not_filtered(RuntimeCall::Multisig(
290
1
                pallet_multisig::Call::<Runtime>::as_multi_threshold_1 {
291
1
                    other_signatories: vec![AccountId::from(BOB)],
292
1
                    call: Box::new(RuntimeCall::ExternalValidators(
293
1
                        pallet_external_validators::Call::<Runtime>::skip_external_validators {
294
1
                            skip: true,
295
1
                        },
296
1
                    )),
297
1
                },
298
1
            ));
299
1
        });
300
1
}
301

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

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

            
330
1
            let batch_interrupt_events = System::events()
331
1
                .iter()
332
2
                .filter(|r| {
333
1
                    matches!(
334
1
                        r.event,
335
                        RuntimeEvent::Utility(pallet_utility::Event::BatchInterrupted { .. },)
336
                    )
337
2
                })
338
1
                .count();
339

            
340
1
            assert_eq!(
341
                batch_interrupt_events, 1,
342
                "batchInterrupted event should be emitted!"
343
            );
344
1
        });
345
1
}
346

            
347
19
fn assert_call_filtered(call: RuntimeCall) {
348
19
    assert_noop!(
349
19
        call.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::signed(
350
19
            AccountId::from(ALICE)
351
        )),
352
19
        frame_system::Error::<Runtime>::CallFiltered
353
    );
354
19
}
355

            
356
12
fn assert_call_not_filtered(call: RuntimeCall) {
357
12
    let res = call.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::signed(
358
12
        AccountId::from(ALICE),
359
    ));
360

            
361
12
    assert!(
362
12
        res != Err(frame_system::Error::<Runtime>::CallFiltered.into()),
363
        "Call is filtered"
364
    );
365
12
}