//! # 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)
}
}
}
}