pezpallet_sudo/
lib.rs

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