3139ffa25e
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
369 lines
12 KiB
Rust
369 lines
12 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! > Made with *Bizinikiwi*, for *Pezkuwi*.
|
|
//!
|
|
//! [![github]](https://github.com/pezkuwichain/pezkuwi-sdk/tree/master/bizinikiwi/pezframe/sudo)
|
|
//! [![pezkuwi]](https://pezkuwichain.io)
|
|
//!
|
|
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
|
//! [pezkuwi]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
|
|
//!
|
|
//! # Sudo Pezpallet
|
|
//!
|
|
//! A pezpallet to provide a way to execute privileged runtime calls using a specified sudo ("superuser
|
|
//! do") account.
|
|
//!
|
|
//! ## Pezpallet API
|
|
//!
|
|
//! See the [`pezpallet`] module for more information about the interfaces this pezpallet exposes,
|
|
//! including its configuration trait, dispatchables, storage items, events and errors.
|
|
//!
|
|
//! ## Overview
|
|
//!
|
|
//! In Bizinikiwi blockchains, pallets may contain dispatchable calls that can only be called at
|
|
//! the system level of the chain (i.e. dispatchables that require a `Root` origin).
|
|
//! Setting a privileged account, called the _sudo key_, allows you to make such calls as an
|
|
//! extrinsic.
|
|
//!
|
|
//! Here's an example of a privileged function in another pezpallet:
|
|
//!
|
|
//! ```
|
|
//! #[pezframe_support::pezpallet]
|
|
//! pub mod pezpallet {
|
|
//! use super::*;
|
|
//! use pezframe_support::pezpallet_prelude::*;
|
|
//! use pezframe_system::pezpallet_prelude::*;
|
|
//!
|
|
//! #[pezpallet::pezpallet]
|
|
//! pub struct Pezpallet<T>(_);
|
|
//!
|
|
//! #[pezpallet::config]
|
|
//! pub trait Config: pezframe_system::Config {}
|
|
//!
|
|
//! #[pezpallet::call]
|
|
//! impl<T: Config> Pezpallet<T> {
|
|
//! #[pezpallet::weight(0)]
|
|
//! pub fn privileged_function(origin: OriginFor<T>) -> DispatchResult {
|
|
//! ensure_root(origin)?;
|
|
//!
|
|
//! // do something...
|
|
//!
|
|
//! Ok(())
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! With the Sudo pezpallet configured in your chain's runtime you can execute this privileged
|
|
//! function by constructing a call using the [`sudo`](Pezpallet::sudo) dispatchable.
|
|
//!
|
|
//! To use this pezpallet in your runtime, a sudo key must be specified in the [`GenesisConfig`] of
|
|
//! the pezpallet. You can change this key at anytime once your chain is live using the
|
|
//! [`set_key`](Pezpallet::set_key) dispatchable, however <strong>only one sudo key can be set at a
|
|
//! time</strong>. The pezpallet also allows you to make a call using
|
|
//! [`sudo_unchecked_weight`](Pezpallet::sudo_unchecked_weight), which allows the sudo account to
|
|
//! execute a call with a custom weight.
|
|
//!
|
|
//! <div class="example-wrap" style="display:inline-block"><pre class="compile_fail"
|
|
//! style="white-space:normal;font:inherit;">
|
|
//! <strong>Note:</strong> this pezpallet is not meant to be used inside other pallets. It is only
|
|
//! meant to be used by constructing runtime calls from outside the runtime.
|
|
//! </pre></div>
|
|
//!
|
|
//! This pezpallet also defines a [`TransactionExtension`](pezsp_runtime::traits::TransactionExtension)
|
|
//! called [`CheckOnlySudoAccount`] to ensure that only signed transactions by the sudo account are
|
|
//! accepted by the transaction pool. The intended use of this signed extension is to prevent other
|
|
//! accounts from spamming the transaction pool for the initial phase of a chain, during which
|
|
//! developers may only want a sudo account to be able to make transactions.
|
|
//!
|
|
//! Learn more about the `Root` origin in the [`RawOrigin`](pezframe_system::RawOrigin) type
|
|
//! documentation.
|
|
//!
|
|
//! ### Examples
|
|
//!
|
|
//! 1. You can make a privileged runtime call using `sudo` with an account that matches the sudo
|
|
//! key.
|
|
#![doc = docify::embed!("src/tests.rs", sudo_basics)]
|
|
//!
|
|
//! 2. Only an existing sudo key can set a new one.
|
|
#![doc = docify::embed!("src/tests.rs", set_key_basics)]
|
|
//!
|
|
//! 3. You can also make non-privileged calls using `sudo_as`.
|
|
#![doc = docify::embed!("src/tests.rs", sudo_as_emits_events_correctly)]
|
|
//!
|
|
//! ## Low Level / Implementation Details
|
|
//!
|
|
//! This pezpallet checks that the caller of its dispatchables is a signed account and ensures that the
|
|
//! caller matches the sudo key in storage.
|
|
//! A caller of this pezpallet's dispatchables does not pay any fees to dispatch a call. If the account
|
|
//! making one of these calls is not the sudo key, the pezpallet returns a [`Error::RequireSudo`]
|
|
//! error.
|
|
//!
|
|
//! Once an origin is verified, sudo calls use `dispatch_bypass_filter` from the
|
|
//! [`UnfilteredDispatchable`](pezframe_support::traits::UnfilteredDispatchable) trait to allow call
|
|
//! execution without enforcing any further origin checks.
|
|
|
|
#![deny(missing_docs)]
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
extern crate alloc;
|
|
|
|
use alloc::boxed::Box;
|
|
|
|
use pezsp_runtime::{traits::StaticLookup, DispatchResult};
|
|
|
|
use pezframe_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable};
|
|
|
|
mod extension;
|
|
#[cfg(test)]
|
|
mod mock;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
mod benchmarking;
|
|
pub mod weights;
|
|
pub use weights::WeightInfo;
|
|
|
|
pub use extension::CheckOnlySudoAccount;
|
|
pub use pezpallet::*;
|
|
|
|
type AccountIdLookupOf<T> = <<T as pezframe_system::Config>::Lookup as StaticLookup>::Source;
|
|
|
|
#[pezframe_support::pezpallet]
|
|
pub mod pezpallet {
|
|
use super::{DispatchResult, *};
|
|
use pezframe_support::pezpallet_prelude::*;
|
|
use pezframe_system::{pezpallet_prelude::*, RawOrigin};
|
|
|
|
/// Default preludes for [`Config`].
|
|
pub mod config_preludes {
|
|
use super::*;
|
|
use pezframe_support::derive_impl;
|
|
|
|
/// Default prelude sensible to be used in a testing environment.
|
|
pub struct TestDefaultConfig;
|
|
|
|
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
|
|
impl pezframe_system::DefaultConfig for TestDefaultConfig {}
|
|
|
|
#[pezframe_support::register_default_impl(TestDefaultConfig)]
|
|
impl DefaultConfig for TestDefaultConfig {
|
|
type WeightInfo = ();
|
|
#[inject_runtime_type]
|
|
type RuntimeEvent = ();
|
|
#[inject_runtime_type]
|
|
type RuntimeCall = ();
|
|
}
|
|
}
|
|
#[pezpallet::config(with_default)]
|
|
pub trait Config: pezframe_system::Config {
|
|
/// The overarching event type.
|
|
#[pezpallet::no_default_bounds]
|
|
#[allow(deprecated)]
|
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
|
|
|
/// A sudo-able call.
|
|
#[pezpallet::no_default_bounds]
|
|
type RuntimeCall: Parameter
|
|
+ UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
|
|
+ GetDispatchInfo;
|
|
|
|
/// Type representing the weight of this pezpallet
|
|
type WeightInfo: WeightInfo;
|
|
}
|
|
|
|
#[pezpallet::pezpallet]
|
|
pub struct Pezpallet<T>(_);
|
|
|
|
#[pezpallet::call]
|
|
impl<T: Config> Pezpallet<T> {
|
|
/// Authenticates the sudo key and dispatches a function call with `Root` origin.
|
|
#[pezpallet::call_index(0)]
|
|
#[pezpallet::weight({
|
|
let dispatch_info = call.get_dispatch_info();
|
|
(
|
|
T::WeightInfo::sudo().saturating_add(dispatch_info.call_weight),
|
|
dispatch_info.class
|
|
)
|
|
})]
|
|
pub fn sudo(
|
|
origin: OriginFor<T>,
|
|
call: Box<<T as Config>::RuntimeCall>,
|
|
) -> DispatchResultWithPostInfo {
|
|
Self::ensure_sudo(origin)?;
|
|
|
|
let res = call.dispatch_bypass_filter(RawOrigin::Root.into());
|
|
Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
|
|
|
|
// Sudo user does not pay a fee.
|
|
Ok(Pays::No.into())
|
|
}
|
|
|
|
/// Authenticates the sudo key and dispatches a function call with `Root` origin.
|
|
/// This function does not check the weight of the call, and instead allows the
|
|
/// Sudo user to specify the weight of the call.
|
|
///
|
|
/// The dispatch origin for this call must be _Signed_.
|
|
#[pezpallet::call_index(1)]
|
|
#[pezpallet::weight((*weight, call.get_dispatch_info().class))]
|
|
pub fn sudo_unchecked_weight(
|
|
origin: OriginFor<T>,
|
|
call: Box<<T as Config>::RuntimeCall>,
|
|
weight: Weight,
|
|
) -> DispatchResultWithPostInfo {
|
|
Self::ensure_sudo(origin)?;
|
|
let _ = weight; // We don't check the weight witness since it is a root call.
|
|
|
|
let res = call.dispatch_bypass_filter(RawOrigin::Root.into());
|
|
Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
|
|
|
|
// Sudo user does not pay a fee.
|
|
Ok(Pays::No.into())
|
|
}
|
|
|
|
/// Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo
|
|
/// key.
|
|
#[pezpallet::call_index(2)]
|
|
#[pezpallet::weight(T::WeightInfo::set_key())]
|
|
pub fn set_key(
|
|
origin: OriginFor<T>,
|
|
new: AccountIdLookupOf<T>,
|
|
) -> DispatchResultWithPostInfo {
|
|
Self::ensure_sudo(origin)?;
|
|
|
|
let new = T::Lookup::lookup(new)?;
|
|
Self::deposit_event(Event::KeyChanged { old: Key::<T>::get(), new: new.clone() });
|
|
Key::<T>::put(new);
|
|
|
|
// Sudo user does not pay a fee.
|
|
Ok(Pays::No.into())
|
|
}
|
|
|
|
/// Authenticates the sudo key and dispatches a function call with `Signed` origin from
|
|
/// a given account.
|
|
///
|
|
/// The dispatch origin for this call must be _Signed_.
|
|
#[pezpallet::call_index(3)]
|
|
#[pezpallet::weight({
|
|
let dispatch_info = call.get_dispatch_info();
|
|
(
|
|
T::WeightInfo::sudo_as().saturating_add(dispatch_info.call_weight),
|
|
dispatch_info.class,
|
|
)
|
|
})]
|
|
pub fn sudo_as(
|
|
origin: OriginFor<T>,
|
|
who: AccountIdLookupOf<T>,
|
|
call: Box<<T as Config>::RuntimeCall>,
|
|
) -> DispatchResultWithPostInfo {
|
|
Self::ensure_sudo(origin)?;
|
|
|
|
let who = T::Lookup::lookup(who)?;
|
|
let res = call.dispatch_bypass_filter(RawOrigin::Signed(who).into());
|
|
Self::deposit_event(Event::SudoAsDone {
|
|
sudo_result: res.map(|_| ()).map_err(|e| e.error),
|
|
});
|
|
|
|
// Sudo user does not pay a fee.
|
|
Ok(Pays::No.into())
|
|
}
|
|
|
|
/// Permanently removes the sudo key.
|
|
///
|
|
/// **This cannot be un-done.**
|
|
#[pezpallet::call_index(4)]
|
|
#[pezpallet::weight(T::WeightInfo::remove_key())]
|
|
pub fn remove_key(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
|
Self::ensure_sudo(origin)?;
|
|
|
|
Self::deposit_event(Event::KeyRemoved {});
|
|
Key::<T>::kill();
|
|
|
|
// Sudo user does not pay a fee.
|
|
Ok(Pays::No.into())
|
|
}
|
|
}
|
|
|
|
#[pezpallet::event]
|
|
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
|
|
pub enum Event<T: Config> {
|
|
/// A sudo call just took place.
|
|
Sudid {
|
|
/// The result of the call made by the sudo user.
|
|
sudo_result: DispatchResult,
|
|
},
|
|
/// The sudo key has been updated.
|
|
KeyChanged {
|
|
/// The old sudo key (if one was previously set).
|
|
old: Option<T::AccountId>,
|
|
/// The new sudo key (if one was set).
|
|
new: T::AccountId,
|
|
},
|
|
/// The key was permanently removed.
|
|
KeyRemoved,
|
|
/// A [sudo_as](Pezpallet::sudo_as) call just took place.
|
|
SudoAsDone {
|
|
/// The result of the call made by the sudo user.
|
|
sudo_result: DispatchResult,
|
|
},
|
|
}
|
|
|
|
#[pezpallet::error]
|
|
/// Error for the Sudo pezpallet.
|
|
pub enum Error<T> {
|
|
/// Sender must be the Sudo account.
|
|
RequireSudo,
|
|
}
|
|
|
|
/// The `AccountId` of the sudo key.
|
|
#[pezpallet::storage]
|
|
pub type Key<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
|
|
|
|
#[pezpallet::genesis_config]
|
|
#[derive(pezframe_support::DefaultNoBound)]
|
|
pub struct GenesisConfig<T: Config> {
|
|
/// The `AccountId` of the sudo key.
|
|
pub key: Option<T::AccountId>,
|
|
}
|
|
|
|
#[pezpallet::genesis_build]
|
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
|
fn build(&self) {
|
|
Key::<T>::set(self.key.clone());
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Pezpallet<T> {
|
|
/// Ensure that the caller is the sudo key.
|
|
pub(crate) fn ensure_sudo(origin: OriginFor<T>) -> DispatchResult {
|
|
let sender = ensure_signed_or_root(origin)?;
|
|
|
|
if let Some(sender) = sender {
|
|
if Key::<T>::get().map_or(false, |k| k == sender) {
|
|
Ok(())
|
|
} else {
|
|
Err(Error::<T>::RequireSudo.into())
|
|
}
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|