#![doc = docify::embed!("src/tests.rs", can_pause_specific_call)]
#![doc = docify::embed!("src/tests.rs", can_unpause_specific_call)]
#![doc = docify::embed!("src/tests.rs", can_pause_all_calls_in_pallet_except_on_whitelist)]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(rustdoc::broken_intra_doc_links)]
mod benchmarking;
pub mod mock;
mod tests;
pub mod weights;
extern crate alloc;
use alloc::vec::Vec;
use frame_support::{
dispatch::GetDispatchInfo,
pallet_prelude::*,
traits::{CallMetadata, Contains, GetCallMetadata, IsSubType, IsType},
DefaultNoBound,
};
use frame_system::pallet_prelude::*;
use sp_runtime::{traits::Dispatchable, DispatchResult};
pub use pallet::*;
pub use weights::*;
pub type PalletNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
pub type PalletCallNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
pub type RuntimeCallNameOf<T> = (PalletNameOf<T>, PalletCallNameOf<T>);
#[frame_support::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ GetDispatchInfo
+ GetCallMetadata
+ From<frame_system::Call<Self>>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type UnpauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type WhitelistedCalls: Contains<RuntimeCallNameOf<Self>>;
#[pallet::constant]
type MaxNameLen: Get<u32>;
type WeightInfo: WeightInfo;
}
#[pallet::storage]
pub type PausedCalls<T: Config> =
StorageMap<_, Blake2_128Concat, RuntimeCallNameOf<T>, (), OptionQuery>;
#[pallet::error]
pub enum Error<T> {
IsPaused,
IsUnpaused,
Unpausable,
NotFound,
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
CallPaused { full_name: RuntimeCallNameOf<T> },
CallUnpaused { full_name: RuntimeCallNameOf<T> },
}
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub paused: Vec<RuntimeCallNameOf<T>>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
for call in &self.paused {
Pallet::<T>::ensure_can_pause(&call).expect("Genesis data is known good; qed");
PausedCalls::<T>::insert(&call, ());
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::pause())]
pub fn pause(origin: OriginFor<T>, full_name: RuntimeCallNameOf<T>) -> DispatchResult {
T::PauseOrigin::ensure_origin(origin)?;
Self::do_pause(full_name).map_err(Into::into)
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::unpause())]
pub fn unpause(origin: OriginFor<T>, ident: RuntimeCallNameOf<T>) -> DispatchResult {
T::UnpauseOrigin::ensure_origin(origin)?;
Self::do_unpause(ident).map_err(Into::into)
}
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn do_pause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
Self::ensure_can_pause(&ident)?;
PausedCalls::<T>::insert(&ident, ());
Self::deposit_event(Event::CallPaused { full_name: ident });
Ok(())
}
pub(crate) fn do_unpause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
Self::ensure_can_unpause(&ident)?;
PausedCalls::<T>::remove(&ident);
Self::deposit_event(Event::CallUnpaused { full_name: ident });
Ok(())
}
pub fn is_paused(full_name: &RuntimeCallNameOf<T>) -> bool {
if T::WhitelistedCalls::contains(full_name) {
return false
}
<PausedCalls<T>>::contains_key(full_name)
}
pub fn is_paused_unbound(pallet: Vec<u8>, call: Vec<u8>) -> bool {
let pallet = PalletNameOf::<T>::try_from(pallet);
let call = PalletCallNameOf::<T>::try_from(call);
match (pallet, call) {
(Ok(pallet), Ok(call)) => Self::is_paused(&(pallet, call)),
_ => true,
}
}
pub fn ensure_can_pause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
if full_name.0.as_slice() == <Self as PalletInfoAccess>::name().as_bytes() {
return Err(Error::<T>::Unpausable)
}
if T::WhitelistedCalls::contains(&full_name) {
return Err(Error::<T>::Unpausable)
}
if Self::is_paused(&full_name) {
return Err(Error::<T>::IsPaused)
}
Ok(())
}
pub fn ensure_can_unpause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
if Self::is_paused(&full_name) {
Ok(())
} else {
Err(Error::IsUnpaused)
}
}
}
impl<T: pallet::Config> Contains<<T as frame_system::Config>::RuntimeCall> for Pallet<T>
where
<T as frame_system::Config>::RuntimeCall: GetCallMetadata,
{
fn contains(call: &<T as frame_system::Config>::RuntimeCall) -> bool {
let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
!Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
}
}
impl<T: Config> frame_support::traits::TransactionPause for Pallet<T> {
type CallIdentifier = RuntimeCallNameOf<T>;
fn is_paused(full_name: Self::CallIdentifier) -> bool {
Self::is_paused(&full_name)
}
fn can_pause(full_name: Self::CallIdentifier) -> bool {
Self::ensure_can_pause(&full_name).is_ok()
}
fn pause(
full_name: Self::CallIdentifier,
) -> Result<(), frame_support::traits::TransactionPauseError> {
Self::do_pause(full_name).map_err(Into::into)
}
fn unpause(
full_name: Self::CallIdentifier,
) -> Result<(), frame_support::traits::TransactionPauseError> {
Self::do_unpause(full_name).map_err(Into::into)
}
}
impl<T: Config> From<Error<T>> for frame_support::traits::TransactionPauseError {
fn from(err: Error<T>) -> Self {
match err {
Error::<T>::NotFound => Self::NotFound,
Error::<T>::Unpausable => Self::Unpausable,
Error::<T>::IsPaused => Self::AlreadyPaused,
Error::<T>::IsUnpaused => Self::AlreadyUnpaused,
_ => Self::Unknown,
}
}
}