pallet_sudo/lib.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! > Made with *Substrate*, for *Polkadot*.
19//!
20//! [![github]](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/sudo)
21//! [![polkadot]](https://polkadot.com)
22//!
23//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
24//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
25//!
26//! # Sudo Pallet
27//!
28//! A pallet to provide a way to execute privileged runtime calls using a specified sudo ("superuser
29//! do") account.
30//!
31//! ## Pallet API
32//!
33//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
34//! including its configuration trait, dispatchables, storage items, events and errors.
35//!
36//! ## Overview
37//!
38//! In Substrate blockchains, pallets may contain dispatchable calls that can only be called at
39//! the system level of the chain (i.e. dispatchables that require a `Root` origin).
40//! Setting a privileged account, called the _sudo key_, allows you to make such calls as an
41//! extrinsic.
42//!
43//! Here's an example of a privileged function in another pallet:
44//!
45//! ```
46//! #[frame_support::pallet]
47//! pub mod pallet {
48//! use super::*;
49//! use frame_support::pallet_prelude::*;
50//! use frame_system::pallet_prelude::*;
51//!
52//! #[pallet::pallet]
53//! pub struct Pallet<T>(_);
54//!
55//! #[pallet::config]
56//! pub trait Config: frame_system::Config {}
57//!
58//! #[pallet::call]
59//! impl<T: Config> Pallet<T> {
60//! #[pallet::weight(0)]
61//! pub fn privileged_function(origin: OriginFor<T>) -> DispatchResult {
62//! ensure_root(origin)?;
63//!
64//! // do something...
65//!
66//! Ok(())
67//! }
68//! }
69//! }
70//! ```
71//!
72//! With the Sudo pallet configured in your chain's runtime you can execute this privileged
73//! function by constructing a call using the [`sudo`](Pallet::sudo) dispatchable.
74//!
75//! To use this pallet in your runtime, a sudo key must be specified in the [`GenesisConfig`] of
76//! the pallet. You can change this key at anytime once your chain is live using the
77//! [`set_key`](Pallet::set_key) dispatchable, however <strong>only one sudo key can be set at a
78//! time</strong>. The pallet also allows you to make a call using
79//! [`sudo_unchecked_weight`](Pallet::sudo_unchecked_weight), which allows the sudo account to
80//! execute a call with a custom weight.
81//!
82//! <div class="example-wrap" style="display:inline-block"><pre class="compile_fail"
83//! style="white-space:normal;font:inherit;">
84//! <strong>Note:</strong> this pallet is not meant to be used inside other pallets. It is only
85//! meant to be used by constructing runtime calls from outside the runtime.
86//! </pre></div>
87//!
88//! This pallet also defines a [`TransactionExtension`](sp_runtime::traits::TransactionExtension)
89//! called [`CheckOnlySudoAccount`] to ensure that only signed transactions by the sudo account are
90//! accepted by the transaction pool. The intended use of this signed extension is to prevent other
91//! accounts from spamming the transaction pool for the initial phase of a chain, during which
92//! developers may only want a sudo account to be able to make transactions.
93//!
94//! Learn more about the `Root` origin in the [`RawOrigin`](frame_system::RawOrigin) type
95//! documentation.
96//!
97//! ### Examples
98//!
99//! 1. You can make a privileged runtime call using `sudo` with an account that matches the sudo
100//! key.
101#![doc = docify::embed!("src/tests.rs", sudo_basics)]
102//!
103//! 2. Only an existing sudo key can set a new one.
104#![doc = docify::embed!("src/tests.rs", set_key_basics)]
105//!
106//! 3. You can also make non-privileged calls using `sudo_as`.
107#![doc = docify::embed!("src/tests.rs", sudo_as_emits_events_correctly)]
108//!
109//! ## Low Level / Implementation Details
110//!
111//! This pallet checks that the caller of its dispatchables is a signed account and ensures that the
112//! caller matches the sudo key in storage.
113//! A caller of this pallet's dispatchables does not pay any fees to dispatch a call. If the account
114//! making one of these calls is not the sudo key, the pallet returns a [`Error::RequireSudo`]
115//! error.
116//!
117//! Once an origin is verified, sudo calls use `dispatch_bypass_filter` from the
118//! [`UnfilteredDispatchable`](frame_support::traits::UnfilteredDispatchable) trait to allow call
119//! execution without enforcing any further origin checks.
120
121#![deny(missing_docs)]
122#![cfg_attr(not(feature = "std"), no_std)]
123
124extern crate alloc;
125
126use alloc::boxed::Box;
127
128use sp_runtime::{traits::StaticLookup, DispatchResult};
129
130use frame_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable};
131
132mod extension;
133#[cfg(test)]
134mod mock;
135#[cfg(test)]
136mod tests;
137
138#[cfg(feature = "runtime-benchmarks")]
139mod benchmarking;
140pub mod weights;
141pub use weights::WeightInfo;
142
143pub use extension::CheckOnlySudoAccount;
144pub use pallet::*;
145
146type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
147
148#[frame_support::pallet]
149pub mod pallet {
150 use super::{DispatchResult, *};
151 use frame_support::pallet_prelude::*;
152 use frame_system::{pallet_prelude::*, RawOrigin};
153
154 /// Default preludes for [`Config`].
155 pub mod config_preludes {
156 use super::*;
157 use frame_support::derive_impl;
158
159 /// Default prelude sensible to be used in a testing environment.
160 pub struct TestDefaultConfig;
161
162 #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
163 impl frame_system::DefaultConfig for TestDefaultConfig {}
164
165 #[frame_support::register_default_impl(TestDefaultConfig)]
166 impl DefaultConfig for TestDefaultConfig {
167 type WeightInfo = ();
168 #[inject_runtime_type]
169 type RuntimeEvent = ();
170 #[inject_runtime_type]
171 type RuntimeCall = ();
172 }
173 }
174 #[pallet::config(with_default)]
175 pub trait Config: frame_system::Config {
176 /// The overarching event type.
177 #[pallet::no_default_bounds]
178 #[allow(deprecated)]
179 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
180
181 /// A sudo-able call.
182 #[pallet::no_default_bounds]
183 type RuntimeCall: Parameter
184 + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
185 + GetDispatchInfo;
186
187 /// Type representing the weight of this pallet
188 type WeightInfo: WeightInfo;
189 }
190
191 #[pallet::pallet]
192 pub struct Pallet<T>(_);
193
194 #[pallet::call]
195 impl<T: Config> Pallet<T> {
196 /// Authenticates the sudo key and dispatches a function call with `Root` origin.
197 #[pallet::call_index(0)]
198 #[pallet::weight({
199 let dispatch_info = call.get_dispatch_info();
200 (
201 T::WeightInfo::sudo().saturating_add(dispatch_info.call_weight),
202 dispatch_info.class
203 )
204 })]
205 pub fn sudo(
206 origin: OriginFor<T>,
207 call: Box<<T as Config>::RuntimeCall>,
208 ) -> DispatchResultWithPostInfo {
209 Self::ensure_sudo(origin)?;
210
211 let res = call.dispatch_bypass_filter(RawOrigin::Root.into());
212 Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
213
214 // Sudo user does not pay a fee.
215 Ok(Pays::No.into())
216 }
217
218 /// Authenticates the sudo key and dispatches a function call with `Root` origin.
219 /// This function does not check the weight of the call, and instead allows the
220 /// Sudo user to specify the weight of the call.
221 ///
222 /// The dispatch origin for this call must be _Signed_.
223 #[pallet::call_index(1)]
224 #[pallet::weight((*weight, call.get_dispatch_info().class))]
225 pub fn sudo_unchecked_weight(
226 origin: OriginFor<T>,
227 call: Box<<T as Config>::RuntimeCall>,
228 weight: Weight,
229 ) -> DispatchResultWithPostInfo {
230 Self::ensure_sudo(origin)?;
231 let _ = weight; // We don't check the weight witness since it is a root call.
232
233 let res = call.dispatch_bypass_filter(RawOrigin::Root.into());
234 Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
235
236 // Sudo user does not pay a fee.
237 Ok(Pays::No.into())
238 }
239
240 /// Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo
241 /// key.
242 #[pallet::call_index(2)]
243 #[pallet::weight(T::WeightInfo::set_key())]
244 pub fn set_key(
245 origin: OriginFor<T>,
246 new: AccountIdLookupOf<T>,
247 ) -> DispatchResultWithPostInfo {
248 Self::ensure_sudo(origin)?;
249
250 let new = T::Lookup::lookup(new)?;
251 Self::deposit_event(Event::KeyChanged { old: Key::<T>::get(), new: new.clone() });
252 Key::<T>::put(new);
253
254 // Sudo user does not pay a fee.
255 Ok(Pays::No.into())
256 }
257
258 /// Authenticates the sudo key and dispatches a function call with `Signed` origin from
259 /// a given account.
260 ///
261 /// The dispatch origin for this call must be _Signed_.
262 #[pallet::call_index(3)]
263 #[pallet::weight({
264 let dispatch_info = call.get_dispatch_info();
265 (
266 T::WeightInfo::sudo_as().saturating_add(dispatch_info.call_weight),
267 dispatch_info.class,
268 )
269 })]
270 pub fn sudo_as(
271 origin: OriginFor<T>,
272 who: AccountIdLookupOf<T>,
273 call: Box<<T as Config>::RuntimeCall>,
274 ) -> DispatchResultWithPostInfo {
275 Self::ensure_sudo(origin)?;
276
277 let who = T::Lookup::lookup(who)?;
278 let res = call.dispatch_bypass_filter(RawOrigin::Signed(who).into());
279 Self::deposit_event(Event::SudoAsDone {
280 sudo_result: res.map(|_| ()).map_err(|e| e.error),
281 });
282
283 // Sudo user does not pay a fee.
284 Ok(Pays::No.into())
285 }
286
287 /// Permanently removes the sudo key.
288 ///
289 /// **This cannot be un-done.**
290 #[pallet::call_index(4)]
291 #[pallet::weight(T::WeightInfo::remove_key())]
292 pub fn remove_key(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
293 Self::ensure_sudo(origin)?;
294
295 Self::deposit_event(Event::KeyRemoved {});
296 Key::<T>::kill();
297
298 // Sudo user does not pay a fee.
299 Ok(Pays::No.into())
300 }
301 }
302
303 #[pallet::event]
304 #[pallet::generate_deposit(pub(super) fn deposit_event)]
305 pub enum Event<T: Config> {
306 /// A sudo call just took place.
307 Sudid {
308 /// The result of the call made by the sudo user.
309 sudo_result: DispatchResult,
310 },
311 /// The sudo key has been updated.
312 KeyChanged {
313 /// The old sudo key (if one was previously set).
314 old: Option<T::AccountId>,
315 /// The new sudo key (if one was set).
316 new: T::AccountId,
317 },
318 /// The key was permanently removed.
319 KeyRemoved,
320 /// A [sudo_as](Pallet::sudo_as) call just took place.
321 SudoAsDone {
322 /// The result of the call made by the sudo user.
323 sudo_result: DispatchResult,
324 },
325 }
326
327 #[pallet::error]
328 /// Error for the Sudo pallet.
329 pub enum Error<T> {
330 /// Sender must be the Sudo account.
331 RequireSudo,
332 }
333
334 /// The `AccountId` of the sudo key.
335 #[pallet::storage]
336 pub type Key<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
337
338 #[pallet::genesis_config]
339 #[derive(frame_support::DefaultNoBound)]
340 pub struct GenesisConfig<T: Config> {
341 /// The `AccountId` of the sudo key.
342 pub key: Option<T::AccountId>,
343 }
344
345 #[pallet::genesis_build]
346 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
347 fn build(&self) {
348 Key::<T>::set(self.key.clone());
349 }
350 }
351
352 impl<T: Config> Pallet<T> {
353 /// Ensure that the caller is the sudo key.
354 pub(crate) fn ensure_sudo(origin: OriginFor<T>) -> DispatchResult {
355 let sender = ensure_signed_or_root(origin)?;
356
357 if let Some(sender) = sender {
358 if Key::<T>::get().map_or(false, |k| k == sender) {
359 Ok(())
360 } else {
361 Err(Error::<T>::RequireSudo.into())
362 }
363 } else {
364 Ok(())
365 }
366 }
367 }
368}