Files
pezkuwi-sdk/bizinikiwi/pezframe/sudo/src/lib.rs
T
pezkuwichain 3139ffa25e fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- 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
2025-12-16 09:57:23 +03:00

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(())
}
}
}
}