Skip to main content

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//! 2. Only an existing sudo key can set a new one.
103#![doc = docify::embed!("src/tests.rs", set_key_basics)]
104//! 3. You can also make non-privileged calls using `sudo_as`.
105#![doc = docify::embed!("src/tests.rs", sudo_as_emits_events_correctly)]
106//! ## Low Level / Implementation Details
107//!
108//! This pallet checks that the caller of its dispatchables is a signed account and ensures that the
109//! caller matches the sudo key in storage.
110//! A caller of this pallet's dispatchables does not pay any fees to dispatch a call. If the account
111//! making one of these calls is not the sudo key, the pallet returns a [`Error::RequireSudo`]
112//! error.
113//!
114//! Once an origin is verified, sudo calls use `dispatch_bypass_filter` from the
115//! [`UnfilteredDispatchable`](frame_support::traits::UnfilteredDispatchable) trait to allow call
116//! execution without enforcing any further origin checks.
117
118#![deny(missing_docs)]
119#![cfg_attr(not(feature = "std"), no_std)]
120
121extern crate alloc;
122
123use alloc::boxed::Box;
124
125use sp_runtime::{traits::StaticLookup, DispatchResult};
126
127use frame_support::{dispatch::GetDispatchInfo, traits::UnfilteredDispatchable};
128
129mod extension;
130#[cfg(test)]
131mod mock;
132#[cfg(test)]
133mod tests;
134
135#[cfg(feature = "runtime-benchmarks")]
136mod benchmarking;
137pub mod weights;
138pub use weights::WeightInfo;
139
140pub use extension::CheckOnlySudoAccount;
141pub use pallet::*;
142
143type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
144
145#[frame_support::pallet]
146pub mod pallet {
147	use super::{DispatchResult, *};
148	use frame_support::pallet_prelude::*;
149	use frame_system::{pallet_prelude::*, RawOrigin};
150
151	/// Default preludes for [`Config`].
152	pub mod config_preludes {
153		use super::*;
154		use frame_support::derive_impl;
155
156		/// Default prelude sensible to be used in a testing environment.
157		pub struct TestDefaultConfig;
158
159		#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
160		impl frame_system::DefaultConfig for TestDefaultConfig {}
161
162		#[frame_support::register_default_impl(TestDefaultConfig)]
163		impl DefaultConfig for TestDefaultConfig {
164			type WeightInfo = ();
165			#[inject_runtime_type]
166			type RuntimeEvent = ();
167			#[inject_runtime_type]
168			type RuntimeCall = ();
169		}
170	}
171	#[pallet::config(with_default)]
172	pub trait Config: frame_system::Config {
173		/// The overarching event type.
174		#[pallet::no_default_bounds]
175		#[allow(deprecated)]
176		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
177
178		/// A sudo-able call.
179		#[pallet::no_default_bounds]
180		type RuntimeCall: Parameter
181			+ UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
182			+ GetDispatchInfo;
183
184		/// Type representing the weight of this pallet
185		type WeightInfo: WeightInfo;
186	}
187
188	#[pallet::pallet]
189	pub struct Pallet<T>(_);
190
191	#[pallet::call]
192	impl<T: Config> Pallet<T> {
193		/// Authenticates the sudo key and dispatches a function call with `Root` origin.
194		#[pallet::call_index(0)]
195		#[pallet::weight({
196			let dispatch_info = call.get_dispatch_info();
197			(
198				T::WeightInfo::sudo().saturating_add(dispatch_info.call_weight),
199				dispatch_info.class
200			)
201		})]
202		pub fn sudo(
203			origin: OriginFor<T>,
204			call: Box<<T as Config>::RuntimeCall>,
205		) -> DispatchResultWithPostInfo {
206			Self::ensure_sudo(origin)?;
207
208			let res = call.dispatch_bypass_filter(RawOrigin::Root.into());
209			Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
210
211			// Sudo user does not pay a fee.
212			Ok(Pays::No.into())
213		}
214
215		/// Authenticates the sudo key and dispatches a function call with `Root` origin.
216		/// This function does not check the weight of the call, and instead allows the
217		/// Sudo user to specify the weight of the call.
218		///
219		/// The dispatch origin for this call must be _Signed_.
220		#[pallet::call_index(1)]
221		#[pallet::weight((*weight, call.get_dispatch_info().class))]
222		pub fn sudo_unchecked_weight(
223			origin: OriginFor<T>,
224			call: Box<<T as Config>::RuntimeCall>,
225			weight: Weight,
226		) -> DispatchResultWithPostInfo {
227			Self::ensure_sudo(origin)?;
228			let _ = weight; // We don't check the weight witness since it is a root call.
229
230			let res = call.dispatch_bypass_filter(RawOrigin::Root.into());
231			Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
232
233			// Sudo user does not pay a fee.
234			Ok(Pays::No.into())
235		}
236
237		/// Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo
238		/// key.
239		#[pallet::call_index(2)]
240		#[pallet::weight(T::WeightInfo::set_key())]
241		pub fn set_key(
242			origin: OriginFor<T>,
243			new: AccountIdLookupOf<T>,
244		) -> DispatchResultWithPostInfo {
245			Self::ensure_sudo(origin)?;
246
247			let new = T::Lookup::lookup(new)?;
248			Self::deposit_event(Event::KeyChanged { old: Key::<T>::get(), new: new.clone() });
249			Key::<T>::put(new);
250
251			// Sudo user does not pay a fee.
252			Ok(Pays::No.into())
253		}
254
255		/// Authenticates the sudo key and dispatches a function call with `Signed` origin from
256		/// a given account.
257		///
258		/// The dispatch origin for this call must be _Signed_.
259		#[pallet::call_index(3)]
260		#[pallet::weight({
261			let dispatch_info = call.get_dispatch_info();
262			(
263				T::WeightInfo::sudo_as().saturating_add(dispatch_info.call_weight),
264				dispatch_info.class,
265			)
266		})]
267		pub fn sudo_as(
268			origin: OriginFor<T>,
269			who: AccountIdLookupOf<T>,
270			call: Box<<T as Config>::RuntimeCall>,
271		) -> DispatchResultWithPostInfo {
272			Self::ensure_sudo(origin)?;
273
274			let who = T::Lookup::lookup(who)?;
275			let res = call.dispatch_bypass_filter(RawOrigin::Signed(who).into());
276			Self::deposit_event(Event::SudoAsDone {
277				sudo_result: res.map(|_| ()).map_err(|e| e.error),
278			});
279
280			// Sudo user does not pay a fee.
281			Ok(Pays::No.into())
282		}
283
284		/// Permanently removes the sudo key.
285		///
286		/// **This cannot be un-done.**
287		#[pallet::call_index(4)]
288		#[pallet::weight(T::WeightInfo::remove_key())]
289		pub fn remove_key(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
290			Self::ensure_sudo(origin)?;
291
292			Self::deposit_event(Event::KeyRemoved {});
293			Key::<T>::kill();
294
295			// Sudo user does not pay a fee.
296			Ok(Pays::No.into())
297		}
298	}
299
300	#[pallet::event]
301	#[pallet::generate_deposit(pub(super) fn deposit_event)]
302	pub enum Event<T: Config> {
303		/// A sudo call just took place.
304		Sudid {
305			/// The result of the call made by the sudo user.
306			sudo_result: DispatchResult,
307		},
308		/// The sudo key has been updated.
309		KeyChanged {
310			/// The old sudo key (if one was previously set).
311			old: Option<T::AccountId>,
312			/// The new sudo key (if one was set).
313			new: T::AccountId,
314		},
315		/// The key was permanently removed.
316		KeyRemoved,
317		/// A [sudo_as](Pallet::sudo_as) call just took place.
318		SudoAsDone {
319			/// The result of the call made by the sudo user.
320			sudo_result: DispatchResult,
321		},
322	}
323
324	#[pallet::error]
325	/// Error for the Sudo pallet.
326	pub enum Error<T> {
327		/// Sender must be the Sudo account.
328		RequireSudo,
329	}
330
331	/// The `AccountId` of the sudo key.
332	#[pallet::storage]
333	pub type Key<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
334
335	#[pallet::genesis_config]
336	#[derive(frame_support::DefaultNoBound)]
337	pub struct GenesisConfig<T: Config> {
338		/// The `AccountId` of the sudo key.
339		pub key: Option<T::AccountId>,
340	}
341
342	#[pallet::genesis_build]
343	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
344		fn build(&self) {
345			Key::<T>::set(self.key.clone());
346		}
347	}
348
349	impl<T: Config> Pallet<T> {
350		/// Ensure that the caller is the sudo key.
351		pub(crate) fn ensure_sudo(origin: OriginFor<T>) -> DispatchResult {
352			let sender = ensure_signed_or_root(origin)?;
353
354			if let Some(sender) = sender {
355				if Key::<T>::get().map_or(false, |k| k == sender) {
356					Ok(())
357				} else {
358					Err(Error::<T>::RequireSudo.into())
359				}
360			} else {
361				Ok(())
362			}
363		}
364	}
365}