Migrate pallet-sudo to pallet! (#8448)

* WIP convert sudo pallet to attribute macros

* Fix up tests and migrate mock

* Fix up genesis build

* Migrate doc comment example

* Update frame/sudo/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Update frame/sudo/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Update frame/sudo/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Update frame/sudo/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Allow unused metadata call_functions

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
Andrew Jones
2021-03-26 14:54:08 +00:00
committed by GitHub
parent 6adf24ca0c
commit e7cd48767a
4 changed files with 179 additions and 102 deletions
+112 -64
View File
@@ -15,14 +15,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! # Sudo Module //! # Sudo Pallet
//! //!
//! - [`Config`] //! - [`Config`]
//! - [`Call`] //! - [`Call`]
//! //!
//! ## Overview //! ## Overview
//! //!
//! The Sudo module allows for a single account (called the "sudo key") //! The Sudo pallet allows for a single account (called the "sudo key")
//! to execute dispatchable functions that require a `Root` call //! to execute dispatchable functions that require a `Root` call
//! or designate a new account to replace them as the sudo key. //! or designate a new account to replace them as the sudo key.
//! Only one account can be the sudo key at a time. //! Only one account can be the sudo key at a time.
@@ -31,7 +31,7 @@
//! //!
//! ### Dispatchable Functions //! ### Dispatchable Functions
//! //!
//! Only the sudo key can call the dispatchable functions from the Sudo module. //! Only the sudo key can call the dispatchable functions from the Sudo pallet.
//! //!
//! * `sudo` - Make a `Root` call to a dispatchable function. //! * `sudo` - Make a `Root` call to a dispatchable function.
//! * `set_key` - Assign a new account to be the sudo key. //! * `set_key` - Assign a new account to be the sudo key.
@@ -40,8 +40,8 @@
//! //!
//! ### Executing Privileged Functions //! ### Executing Privileged Functions
//! //!
//! The Sudo module itself is not intended to be used within other modules. //! The Sudo pallet itself is not intended to be used within other pallets.
//! Instead, you can build "privileged functions" (i.e. functions that require `Root` origin) in other modules. //! Instead, you can build "privileged functions" (i.e. functions that require `Root` origin) in other pallets.
//! You can execute these privileged functions by calling `sudo` with the sudo key account. //! You can execute these privileged functions by calling `sudo` with the sudo key account.
//! Privileged functions cannot be directly executed via an extrinsic. //! Privileged functions cannot be directly executed via an extrinsic.
//! //!
@@ -49,35 +49,46 @@
//! //!
//! ### Simple Code Snippet //! ### Simple Code Snippet
//! //!
//! This is an example of a module that exposes a privileged function: //! This is an example of a pallet that exposes a privileged function:
//! //!
//! ``` //! ```
//! use frame_support::{decl_module, dispatch};
//! use frame_system::ensure_root;
//! //!
//! pub trait Config: frame_system::Config {} //! #[frame_support::pallet]
//! pub mod logger {
//! use frame_support::pallet_prelude::*;
//! use frame_system::pallet_prelude::*;
//! use super::*;
//! //!
//! decl_module! { //! #[pallet::config]
//! pub struct Module<T: Config> for enum Call where origin: T::Origin { //! pub trait Config: frame_system::Config {}
//! #[weight = 0] //!
//! pub fn privileged_function(origin) -> dispatch::DispatchResult { //! #[pallet::pallet]
//! pub struct Pallet<T>(PhantomData<T>);
//!
//! #[pallet::hooks]
//! impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
//!
//! #[pallet::call]
//! impl<T: Config> Pallet<T> {
//! #[pallet::weight(0)]
//! pub fn privileged_function(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
//! ensure_root(origin)?; //! ensure_root(origin)?;
//! //!
//! // do something... //! // do something...
//! //!
//! Ok(()) //! Ok(().into())
//! } //! }
//! } //! }
//! } //! }
//! # fn main() {} //! # fn main() {}
//! ``` //! ```
//! //!
//! ## Genesis Config //! ## Genesis Config
//! //!
//! The Sudo module depends on the [`GenesisConfig`](./struct.GenesisConfig.html). //! The Sudo pallet depends on the [`GenesisConfig`].
//! You need to set an initial superuser account as the sudo `key`. //! You need to set an initial superuser account as the sudo `key`.
//! //!
//! ## Related Modules //! ## Related Pallets
//! //!
//! * [Democracy](../pallet_democracy/index.html) //! * [Democracy](../pallet_democracy/index.html)
//! //!
@@ -89,35 +100,41 @@ use sp_std::prelude::*;
use sp_runtime::{DispatchResult, traits::StaticLookup}; use sp_runtime::{DispatchResult, traits::StaticLookup};
use frame_support::{ use frame_support::{
Parameter, decl_module, decl_event, decl_storage, decl_error, ensure, weights::GetDispatchInfo,
traits::UnfilteredDispatchable,
}; };
use frame_support::{
weights::{Weight, GetDispatchInfo, Pays},
traits::{UnfilteredDispatchable, Get},
dispatch::DispatchResultWithPostInfo,
};
use frame_system::ensure_signed;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub trait Config: frame_system::Config { pub use pallet::*;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
/// A sudo-able call. #[frame_support::pallet]
type Call: Parameter + UnfilteredDispatchable<Origin=Self::Origin> + GetDispatchInfo; pub mod pallet {
} use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use super::{*, DispatchResult};
decl_module! { #[pallet::config]
/// Sudo module declaration. pub trait Config: frame_system::Config {
pub struct Module<T: Config> for enum Call where origin: T::Origin { /// The overarching event type.
type Error = Error<T>; type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
fn deposit_event() = default; /// A sudo-able call.
type Call: Parameter + UnfilteredDispatchable<Origin=Self::Origin> + GetDispatchInfo;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Authenticates the sudo key and dispatches a function call with `Root` origin. /// Authenticates the sudo key and dispatches a function call with `Root` origin.
/// ///
/// The dispatch origin for this call must be _Signed_. /// The dispatch origin for this call must be _Signed_.
@@ -128,17 +145,20 @@ decl_module! {
/// - One DB write (event). /// - One DB write (event).
/// - Weight of derivative `call` execution + 10,000. /// - Weight of derivative `call` execution + 10,000.
/// # </weight> /// # </weight>
#[weight = { #[pallet::weight({
let dispatch_info = call.get_dispatch_info(); let dispatch_info = call.get_dispatch_info();
(dispatch_info.weight.saturating_add(10_000), dispatch_info.class) (dispatch_info.weight.saturating_add(10_000), dispatch_info.class)
}] })]
fn sudo(origin, call: Box<<T as Config>::Call>) -> DispatchResultWithPostInfo { pub(crate) fn sudo(
origin: OriginFor<T>,
call: Box<<T as Config>::Call>,
) -> DispatchResultWithPostInfo {
// This is a public call, so we ensure that the origin is some signed account. // This is a public call, so we ensure that the origin is some signed account.
let sender = ensure_signed(origin)?; let sender = ensure_signed(origin)?;
ensure!(sender == Self::key(), Error::<T>::RequireSudo); ensure!(sender == Self::key(), Error::<T>::RequireSudo);
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
Self::deposit_event(RawEvent::Sudid(res.map(|_| ()).map_err(|e| e.error))); Self::deposit_event(Event::Sudid(res.map(|_| ()).map_err(|e| e.error)));
// Sudo user does not pay a fee. // Sudo user does not pay a fee.
Ok(Pays::No.into()) Ok(Pays::No.into())
} }
@@ -153,14 +173,18 @@ decl_module! {
/// - O(1). /// - O(1).
/// - The weight of this call is defined by the caller. /// - The weight of this call is defined by the caller.
/// # </weight> /// # </weight>
#[weight = (*_weight, call.get_dispatch_info().class)] #[pallet::weight((*_weight, call.get_dispatch_info().class))]
fn sudo_unchecked_weight(origin, call: Box<<T as Config>::Call>, _weight: Weight) -> DispatchResultWithPostInfo { pub(crate) fn sudo_unchecked_weight(
origin: OriginFor<T>,
call: Box<<T as Config>::Call>,
_weight: Weight,
) -> DispatchResultWithPostInfo {
// This is a public call, so we ensure that the origin is some signed account. // This is a public call, so we ensure that the origin is some signed account.
let sender = ensure_signed(origin)?; let sender = ensure_signed(origin)?;
ensure!(sender == Self::key(), Error::<T>::RequireSudo); ensure!(sender == Self::key(), Error::<T>::RequireSudo);
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
Self::deposit_event(RawEvent::Sudid(res.map(|_| ()).map_err(|e| e.error))); Self::deposit_event(Event::Sudid(res.map(|_| ()).map_err(|e| e.error)));
// Sudo user does not pay a fee. // Sudo user does not pay a fee.
Ok(Pays::No.into()) Ok(Pays::No.into())
} }
@@ -174,14 +198,17 @@ decl_module! {
/// - Limited storage reads. /// - Limited storage reads.
/// - One DB change. /// - One DB change.
/// # </weight> /// # </weight>
#[weight = 0] #[pallet::weight(0)]
fn set_key(origin, new: <T::Lookup as StaticLookup>::Source) -> DispatchResultWithPostInfo { pub(crate) fn set_key(
origin: OriginFor<T>,
new: <T::Lookup as StaticLookup>::Source,
) -> DispatchResultWithPostInfo {
// This is a public call, so we ensure that the origin is some signed account. // This is a public call, so we ensure that the origin is some signed account.
let sender = ensure_signed(origin)?; let sender = ensure_signed(origin)?;
ensure!(sender == Self::key(), Error::<T>::RequireSudo); ensure!(sender == Self::key(), Error::<T>::RequireSudo);
let new = T::Lookup::lookup(new)?; let new = T::Lookup::lookup(new)?;
Self::deposit_event(RawEvent::KeyChanged(Self::key())); Self::deposit_event(Event::KeyChanged(Self::key()));
<Key<T>>::put(new); <Key<T>>::put(new);
// Sudo user does not pay a fee. // Sudo user does not pay a fee.
Ok(Pays::No.into()) Ok(Pays::No.into())
@@ -198,7 +225,7 @@ decl_module! {
/// - One DB write (event). /// - One DB write (event).
/// - Weight of derivative `call` execution + 10,000. /// - Weight of derivative `call` execution + 10,000.
/// # </weight> /// # </weight>
#[weight = { #[pallet::weight({
let dispatch_info = call.get_dispatch_info(); let dispatch_info = call.get_dispatch_info();
( (
dispatch_info.weight dispatch_info.weight
@@ -207,8 +234,9 @@ decl_module! {
.saturating_add(T::DbWeight::get().reads_writes(1, 1)), .saturating_add(T::DbWeight::get().reads_writes(1, 1)),
dispatch_info.class, dispatch_info.class,
) )
}] })]
fn sudo_as(origin, pub(crate) fn sudo_as(
origin: OriginFor<T>,
who: <T::Lookup as StaticLookup>::Source, who: <T::Lookup as StaticLookup>::Source,
call: Box<<T as Config>::Call> call: Box<<T as Config>::Call>
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
@@ -220,35 +248,55 @@ decl_module! {
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Signed(who).into()); let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Signed(who).into());
Self::deposit_event(RawEvent::SudoAsDone(res.map(|_| ()).map_err(|e| e.error))); Self::deposit_event(Event::SudoAsDone(res.map(|_| ()).map_err(|e| e.error)));
// Sudo user does not pay a fee. // Sudo user does not pay a fee.
Ok(Pays::No.into()) Ok(Pays::No.into())
} }
} }
}
decl_event!( #[pallet::event]
pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId { #[pallet::generate_deposit(pub(super) fn deposit_event)]
#[pallet::metadata(T::AccountId = "AccountId")]
pub enum Event<T: Config> {
/// A sudo just took place. \[result\] /// A sudo just took place. \[result\]
Sudid(DispatchResult), Sudid(DispatchResult),
/// The \[sudoer\] just switched identity; the old key is supplied. /// The \[sudoer\] just switched identity; the old key is supplied.
KeyChanged(AccountId), KeyChanged(T::AccountId),
/// A sudo just took place. \[result\] /// A sudo just took place. \[result\]
SudoAsDone(DispatchResult), SudoAsDone(DispatchResult),
} }
);
decl_storage! { #[pallet::error]
trait Store for Module<T: Config> as Sudo { /// Error for the Sudo pallet
/// The `AccountId` of the sudo key. pub enum Error<T> {
Key get(fn key) config(): T::AccountId;
}
}
decl_error! {
/// Error for the Sudo module
pub enum Error for Module<T: Config> {
/// Sender must be the Sudo account /// Sender must be the Sudo account
RequireSudo, RequireSudo,
} }
/// The `AccountId` of the sudo key.
#[pallet::storage]
#[pallet::getter(fn key)]
pub(super) type Key<T: Config> = StorageValue<_, T::AccountId, ValueQuery>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
/// The `AccountId` of the sudo key.
pub key: T::AccountId,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
key: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
<Key<T>>::put(&self.key);
}
}
} }
+61 -33
View File
@@ -18,7 +18,7 @@
//! Test utilities //! Test utilities
use super::*; use super::*;
use frame_support::{parameter_types, weights::Weight}; use frame_support::{parameter_types, traits::GenesisBuild};
use sp_core::H256; use sp_core::H256;
use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
use sp_io; use sp_io;
@@ -27,52 +27,80 @@ use frame_support::traits::Filter;
use frame_system::limits; use frame_system::limits;
// Logger module to track execution. // Logger module to track execution.
#[frame_support::pallet]
pub mod logger { pub mod logger {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use super::*; use super::*;
use frame_system::ensure_root;
#[pallet::config]
pub trait Config: frame_system::Config { pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>; type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
} }
decl_storage! { #[pallet::pallet]
trait Store for Module<T: Config> as Logger { #[pallet::generate_store(pub(super) trait Store)]
AccountLog get(fn account_log): Vec<T::AccountId>; pub struct Pallet<T>(PhantomData<T>);
I32Log get(fn i32_log): Vec<i32>;
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(*weight)]
pub(crate) fn privileged_i32_log(
origin: OriginFor<T>,
i: i32,
weight: Weight
) -> DispatchResultWithPostInfo {
// Ensure that the `origin` is `Root`.
ensure_root(origin)?;
<I32Log<T>>::append(i);
Self::deposit_event(Event::AppendI32(i, weight));
Ok(().into())
}
#[pallet::weight(*weight)]
pub(crate) fn non_privileged_log(
origin: OriginFor<T>,
i: i32,
weight: Weight
) -> DispatchResultWithPostInfo {
// Ensure that the `origin` is some signed account.
let sender = ensure_signed(origin)?;
<I32Log<T>>::append(i);
<AccountLog<T>>::append(sender.clone());
Self::deposit_event(Event::AppendI32AndAccount(sender, i, weight));
Ok(().into())
} }
} }
decl_event! { #[pallet::event]
pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId { #[pallet::generate_deposit(pub(super) fn deposit_event)]
AppendI32(i32, Weight), #[pallet::metadata(T::AccountId = "AccountId")]
AppendI32AndAccount(AccountId, i32, Weight), pub enum Event<T: Config> {
} AppendI32(i32, Weight),
AppendI32AndAccount(T::AccountId, i32, Weight),
} }
decl_module! { #[pallet::storage]
pub struct Module<T: Config> for enum Call where origin: <T as frame_system::Config>::Origin { #[pallet::getter(fn account_log)]
fn deposit_event() = default; pub(super) type AccountLog<T: Config> = StorageValue<
_,
Vec<T::AccountId>,
ValueQuery
>;
#[weight = *weight] #[pallet::storage]
fn privileged_i32_log(origin, i: i32, weight: Weight){ #[pallet::getter(fn i32_log)]
// Ensure that the `origin` is `Root`. pub(super) type I32Log<T> = StorageValue<
ensure_root(origin)?; _,
<I32Log>::append(i); Vec<i32>,
Self::deposit_event(RawEvent::AppendI32(i, weight)); ValueQuery
} >;
#[weight = *weight]
fn non_privileged_log(origin, i: i32, weight: Weight){
// Ensure that the `origin` is some signed account.
let sender = ensure_signed(origin)?;
<I32Log>::append(i);
<AccountLog<T>>::append(sender.clone());
Self::deposit_event(RawEvent::AppendI32AndAccount(sender, i, weight));
}
}
}
} }
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
+5 -5
View File
@@ -58,7 +58,7 @@ fn sudo_emits_events_correctly() {
// Should emit event to indicate success when called with the root `key` and `call` is `Ok`. // Should emit event to indicate success when called with the root `key` and `call` is `Ok`.
let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log(42, 1))); let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log(42, 1)));
assert_ok!(Sudo::sudo(Origin::signed(1), call)); assert_ok!(Sudo::sudo(Origin::signed(1), call));
let expected_event = TestEvent::sudo(RawEvent::Sudid(Ok(()))); let expected_event = TestEvent::sudo(Event::Sudid(Ok(())));
assert!(System::events().iter().any(|a| a.event == expected_event)); assert!(System::events().iter().any(|a| a.event == expected_event));
}) })
} }
@@ -97,7 +97,7 @@ fn sudo_unchecked_weight_emits_events_correctly() {
// Should emit event to indicate success when called with the root `key` and `call` is `Ok`. // Should emit event to indicate success when called with the root `key` and `call` is `Ok`.
let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log(42, 1))); let call = Box::new(Call::Logger(LoggerCall::privileged_i32_log(42, 1)));
assert_ok!(Sudo::sudo_unchecked_weight(Origin::signed(1), call, 1_000)); assert_ok!(Sudo::sudo_unchecked_weight(Origin::signed(1), call, 1_000));
let expected_event = TestEvent::sudo(RawEvent::Sudid(Ok(()))); let expected_event = TestEvent::sudo(Event::Sudid(Ok(())));
assert!(System::events().iter().any(|a| a.event == expected_event)); assert!(System::events().iter().any(|a| a.event == expected_event));
}) })
} }
@@ -124,11 +124,11 @@ fn set_key_emits_events_correctly() {
// A root `key` can change the root `key`. // A root `key` can change the root `key`.
assert_ok!(Sudo::set_key(Origin::signed(1), 2)); assert_ok!(Sudo::set_key(Origin::signed(1), 2));
let expected_event = TestEvent::sudo(RawEvent::KeyChanged(1)); let expected_event = TestEvent::sudo(Event::KeyChanged(1));
assert!(System::events().iter().any(|a| a.event == expected_event)); assert!(System::events().iter().any(|a| a.event == expected_event));
// Double check. // Double check.
assert_ok!(Sudo::set_key(Origin::signed(2), 4)); assert_ok!(Sudo::set_key(Origin::signed(2), 4));
let expected_event = TestEvent::sudo(RawEvent::KeyChanged(2)); let expected_event = TestEvent::sudo(Event::KeyChanged(2));
assert!(System::events().iter().any(|a| a.event == expected_event)); assert!(System::events().iter().any(|a| a.event == expected_event));
}); });
} }
@@ -164,7 +164,7 @@ fn sudo_as_emits_events_correctly() {
// A non-privileged function will work when passed to `sudo_as` with the root `key`. // A non-privileged function will work when passed to `sudo_as` with the root `key`.
let call = Box::new(Call::Logger(LoggerCall::non_privileged_log(42, 1))); let call = Box::new(Call::Logger(LoggerCall::non_privileged_log(42, 1)));
assert_ok!(Sudo::sudo_as(Origin::signed(1), 2, call)); assert_ok!(Sudo::sudo_as(Origin::signed(1), 2, call));
let expected_event = TestEvent::sudo(RawEvent::SudoAsDone(Ok(()))); let expected_event = TestEvent::sudo(Event::SudoAsDone(Ok(())));
assert!(System::events().iter().any(|a| a.event == expected_event)); assert!(System::events().iter().any(|a| a.event == expected_event));
}); });
} }
@@ -186,6 +186,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause { impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause {
#[doc(hidden)] #[doc(hidden)]
#[allow(dead_code)]
pub fn call_functions() -> &'static [#frame_support::dispatch::FunctionMetadata] { pub fn call_functions() -> &'static [#frame_support::dispatch::FunctionMetadata] {
&[ #( &[ #(
#frame_support::dispatch::FunctionMetadata { #frame_support::dispatch::FunctionMetadata {