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 this 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::ForeignAssets(pallet_assets::Call::<
176
1
                Runtime,
177
1
                Instance1,
178
1
            >::touch {
179
1
                id: 1u16,
180
1
            }));
181
1
            assert_call_filtered(RuntimeCall::ForeignAssetsCreator(
182
1
                pallet_foreign_asset_creator::Call::<Runtime>::destroy_foreign_asset {
183
1
                    asset_id: 1u16,
184
1
                },
185
1
            ));
186
1
        });
187
1
}
188

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

            
211
1
            let babe_equivocation_proof = generate_babe_equivocation_proof(&babe_key);
212

            
213
1
            let set_id = Grandpa::current_set_id();
214

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
354
12
fn assert_call_not_filtered(call: RuntimeCall) {
355
12
    let res = call
356
12
        .clone()
357
12
        .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
        call
365
    );
366
12
}