Skip to main content

Maintenance Mode

//! # Maintenance Mode Pallet
//!
//! The Maintenance Mode pallet allows the chain to be put into maintenance mode, where certain
//! pallets can be paused. This is useful for when the chain needs to be upgraded or when a
//! critical bug is found in a pallet.
//!
//! ## Overview
//!
//! The Maintenance Mode pallet provides the following functionality:
//!
//! - Pause and resume pallets.
//! - Set the chain mode to normal, only core, or no defi.
//!
//! ### Goals
//!
//! The Maintenance Mode pallet is designed to make it easy to pause and resume pallets and to
//! set the chain mode.
//!
//! ## Interface
//!
//! - `pause_pallet` - Pause a pallet.
//! - `resume_pallet` - Resume a pallet.
//! - `set_chain_mode` - Set the chain mode.
//!
//! All of these function can only be called by the `PauseOrigin`. Which in its simlest form is the
//! root origin.
//!
//!
//! Modification History:
//!
//! 2024/02/27 - Initial creation by Walquer Valles
//!
#![cfg_attr(not(feature = "std"), no_std)]

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;

use frame_support::pallet_prelude::*;

use scale_info::prelude::vec;

use traits::maintenance_mode::{CallFilter};

use frame_support::{
traits::{GetCallMetadata},
};

use sp_std::{prelude::*, vec::Vec};

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

pub mod weights;
pub use weights::WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, TypeInfo, Debug)]
pub enum ChainMode {
Normal,
OnlyCore,
NoDeFi,
Maintenance
}

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;

#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// The origin which may set filter.
type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;

/// Vector of pallet indexes that are not core to unit protocol.
type NonCorePallets: Get<Vec<u8>>;

/// Vector of pallet indexes that are defi related.
type DefiPallets: Get<Vec<u8>>;

/// Vector of pallet to pause in maintenance mode.
type MaintenancePallets: Get<Vec<u8>>;

/// Weight Information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}

/// Storage for pallets disabled in normal mode
#[pallet::storage]
#[pallet::getter(fn disabled_pallets)]
pub type DisabledPallets<T: Config> = StorageMap<_, Blake2_128Concat, u8, bool, ValueQuery>;

/// Storage for storing pallet current mode: normal, only core, no defi
#[pallet::storage]
#[pallet::getter(fn pallet_mode)]
pub type PalletMode<T: Config> = StorageValue<_, ChainMode>;

// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A palled was paused.
PalletPaused {
pallet_index: u8,
},
/// A palled was resumed.
PalletResumed {
pallet_index: u8,
},
/// Chain mode was changed.
ChainModeChanged {
mode: ChainMode,
},
}

// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// The pallet index is invalid.
PalletIndexInvalid,
/// The pallet is already paused.
PalletAlreadyPaused,
/// The pallet is not paused.
PalletNotPaused,
}

#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
/// Pause a pallet.
#[pallet::call_index(0)]
pub fn pause_pallet(
origin: OriginFor<T>,
pallet_index: u8,
) -> DispatchResult {

// Ensure the origin is authorized to pause the pallet.
T::PauseOrigin::ensure_origin(origin)?;

// check if the pallet index is valid. Here we suppose that maintenance pallets list all
// the pallets that can be paused in normal mode.
ensure!(T::MaintenancePallets::get().contains(&pallet_index), Error::<T>::PalletIndexInvalid);

// check if the pallet is already paused
ensure!(!DisabledPallets::<T>::contains_key(pallet_index), Error::<T>::PalletAlreadyPaused);

// add the pallet to the paused pallets
<DisabledPallets<T>>::insert(pallet_index, true);

// Emit an event.
Self::deposit_event(Event::<T>::PalletPaused { pallet_index });
Ok(())
}

#[pallet::call_index(1)]
pub fn resume_pallet(
origin: OriginFor<T>,
pallet_index: u8,
) -> DispatchResult {

// Ensure the origin is authorized to pause the pallet.
T::PauseOrigin::ensure_origin(origin)?;

// check if the pallet index is valid. Here we suppose that maintenance pallets list all
// the pallets that can be paused in normal mode.
ensure!(T::MaintenancePallets::get().contains(&pallet_index), Error::<T>::PalletIndexInvalid);

// check if the index is list in the disabled pallets
ensure!(DisabledPallets::<T>::contains_key(pallet_index), Error::<T>::PalletNotPaused);

<DisabledPallets<T>>::remove(pallet_index);

// Emit an event.
Self::deposit_event(Event::<T>::PalletResumed { pallet_index });
Ok(())
}

/// Set the chain mode.
#[pallet::call_index(2)]
pub fn set_chain_mode(
origin: OriginFor<T>,
mode: ChainMode,
) -> DispatchResult {
// Ensure the origin is authorized to pause the pallet.
T::PauseOrigin::ensure_origin(origin)?;

<PalletMode<T>>::put(mode);

// Emit an event.
Self::deposit_event(Event::<T>::ChainModeChanged { mode });

Ok(())
}
}

}

impl<T: Config> Pallet<T> {
}

pub struct PausedPalletFilter<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> CallFilter<T::RuntimeCall> for PausedPalletFilter<T>
where
<T as frame_system::Config>::RuntimeCall: GetCallMetadata,
{
fn contains(call: &T::RuntimeCall) -> bool {
let (pallet_index, _call_index): (u8, u8) = call
.using_encoded(|mut bytes| Decode::decode(&mut bytes))
.expect(
"decode input is output of Call encode; Call guaranteed to have two enums; qed",
);

// depending on the mode, check if the pallet is paused
match PalletMode::<T>::get().unwrap_or(ChainMode::Normal) {
ChainMode::Normal =>
DisabledPallets::<T>::contains_key(pallet_index),
ChainMode::OnlyCore => {
T::NonCorePallets::get().contains(&pallet_index)
},
ChainMode::NoDeFi => {
T::DefiPallets::get().contains(&pallet_index)
},
ChainMode::Maintenance => {
T::MaintenancePallets::get().contains(&pallet_index)
}
}
}
}