// This file is part of Substrate. // 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 *Substrate*, for *Polkadot*. //! //! [![github]](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/sudo) //! [![polkadot]](https://polkadot.network) //! //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white //! //! # Sudo Pallet //! //! A pallet to provide a way to execute privileged runtime calls using a specified sudo ("superuser //! do") account. //! //! ## Pallet API //! //! See the [`pallet`] module for more information about the interfaces this pallet exposes, //! including its configuration trait, dispatchables, storage items, events and errors. //! //! ## Overview //! //! In Substrate 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 //! extrinisic. //! //! Here's an example of a privileged function in another pallet: //! //! ``` //! #[frame_support::pallet] //! pub mod pallet { //! use super::*; //! use frame_support::pallet_prelude::*; //! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] //! pub struct Pallet(_); //! //! #[pallet::config] //! pub trait Config: frame_system::Config {} //! //! #[pallet::call] //! impl Pallet { //! #[pallet::weight(0)] //! pub fn privileged_function(origin: OriginFor) -> DispatchResult { //! ensure_root(origin)?; //! //! // do something... //! //! Ok(()) //! } //! } //! } //! ``` //! //! With the Sudo pallet configured in your chain's runtime you can execute this privileged //! function by constructing a call using the [`sudo`](Pallet::sudo) dispatchable. //! //! To use this pallet in your runtime, a sudo key must be specified in the [`GenesisConfig`] of //! the pallet. You can change this key at anytime once your chain is live using the //! [`set_key`](Pallet::set_key) dispatchable, however only one sudo key can be set at a //! time. The pallet also allows you to make a call using //! [`sudo_unchecked_weight`](Pallet::sudo_unchecked_weight), which allows the sudo account to //! execute a call with a custom weight. //! //!
//! Note: this pallet is not meant to be used inside other pallets. It is only
//! meant to be used by constructing runtime calls from outside the runtime.
//! 
//! //! This pallet also defines a [`SignedExtension`](sp_runtime::traits::SignedExtension) 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`](frame_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 pallet 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 pallet'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 pallet returns a [`Error::RequireSudo`] //! error. //! //! Once an origin is verified, sudo calls use `dispatch_bypass_filter` from the //! [`UnfilteredDispatchable`](frame_support::traits::UnfilteredDispatchable) trait to allow call //! execution without enforcing any further origin checks. #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use sp_runtime::{traits::StaticLookup, DispatchResult}; use sp_std::prelude::*; use frame_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 pallet::*; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] pub mod pallet { use super::{DispatchResult, *}; use frame_support::pallet_prelude::*; use frame_system::{pallet_prelude::*, RawOrigin}; /// Default preludes for [`Config`]. pub mod config_preludes { use super::*; use frame_support::derive_impl; /// Default prelude sensible to be used in a testing environment. pub struct TestDefaultConfig; #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)] impl frame_system::DefaultConfig for TestDefaultConfig {} #[frame_support::register_default_impl(TestDefaultConfig)] impl DefaultConfig for TestDefaultConfig { type WeightInfo = (); #[inject_runtime_type] type RuntimeEvent = (); #[inject_runtime_type] type RuntimeCall = (); } } #[pallet::config(with_default)] pub trait Config: frame_system::Config { /// The overarching event type. #[pallet::no_default_bounds] type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// A sudo-able call. #[pallet::no_default_bounds] type RuntimeCall: Parameter + UnfilteredDispatchable + GetDispatchInfo; /// Type representing the weight of this pallet type WeightInfo: WeightInfo; } #[pallet::pallet] pub struct Pallet(_); #[pallet::call] impl Pallet { /// Authenticates the sudo key and dispatches a function call with `Root` origin. #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( T::WeightInfo::sudo().saturating_add(dispatch_info.weight), dispatch_info.class ) })] pub fn sudo( origin: OriginFor, call: Box<::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_. #[pallet::call_index(1)] #[pallet::weight((*weight, call.get_dispatch_info().class))] pub fn sudo_unchecked_weight( origin: OriginFor, call: Box<::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. #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::set_key())] pub fn set_key( origin: OriginFor, new: AccountIdLookupOf, ) -> DispatchResultWithPostInfo { Self::ensure_sudo(origin)?; let new = T::Lookup::lookup(new)?; Self::deposit_event(Event::KeyChanged { old: Key::::get(), new: new.clone() }); Key::::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_. #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( T::WeightInfo::sudo_as().saturating_add(dispatch_info.weight), dispatch_info.class, ) })] pub fn sudo_as( origin: OriginFor, who: AccountIdLookupOf, call: Box<::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.** #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::remove_key())] pub fn remove_key(origin: OriginFor) -> DispatchResultWithPostInfo { Self::ensure_sudo(origin)?; Self::deposit_event(Event::KeyRemoved {}); Key::::kill(); // Sudo user does not pay a fee. Ok(Pays::No.into()) } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// 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, /// The new sudo key (if one was set). new: T::AccountId, }, /// The key was permanently removed. KeyRemoved, /// A [sudo_as](Pallet::sudo_as) call just took place. SudoAsDone { /// The result of the call made by the sudo user. sudo_result: DispatchResult, }, } #[pallet::error] /// Error for the Sudo pallet. pub enum Error { /// Sender must be the Sudo account. RequireSudo, } /// The `AccountId` of the sudo key. #[pallet::storage] pub(super) type Key = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { /// The `AccountId` of the sudo key. pub key: Option, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Key::::set(self.key.clone()); } } impl Pallet { /// Ensure that the caller is the sudo key. pub(crate) fn ensure_sudo(origin: OriginFor) -> DispatchResult { let sender = ensure_signed_or_root(origin)?; if let Some(sender) = sender { if Key::::get().map_or(false, |k| k == sender) { Ok(()) } else { Err(Error::::RequireSudo.into()) } } else { Ok(()) } } } }