// 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. //! # Whitelist Pallet //! //! - [`Config`] //! - [`Call`] //! //! ## Overview //! //! Allow some configurable origin: [`Config::WhitelistOrigin`] to whitelist some hash of a call, //! and allow another configurable origin: [`Config::DispatchWhitelistedOrigin`] to dispatch them //! with the root origin. //! //! In the meantime the call corresponding to the hash must have been submitted to the pre-image //! handler [`pallet::Config::Preimages`]. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; pub mod weights; pub use weights::WeightInfo; use codec::{DecodeLimit, Encode, FullCodec}; use frame_support::{ dispatch::{GetDispatchInfo, PostDispatchInfo}, ensure, traits::{Hash as PreimageHash, QueryPreimage, StorePreimage}, weights::Weight, Hashable, }; use scale_info::TypeInfo; use sp_runtime::traits::Dispatchable; use sp_std::prelude::*; pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The overarching call type. type RuntimeCall: IsType<::RuntimeCall> + Dispatchable + GetDispatchInfo + FullCodec + TypeInfo + From> + Parameter; /// Required origin for whitelisting a call. type WhitelistOrigin: EnsureOrigin; /// Required origin for dispatching whitelisted call with root origin. type DispatchWhitelistedOrigin: EnsureOrigin; /// The handler of pre-images. type Preimages: QueryPreimage + StorePreimage; /// The weight information for this pallet. type WeightInfo: WeightInfo; } #[pallet::pallet] pub struct Pallet(_); #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { CallWhitelisted { call_hash: PreimageHash }, WhitelistedCallRemoved { call_hash: PreimageHash }, WhitelistedCallDispatched { call_hash: PreimageHash, result: DispatchResultWithPostInfo }, } #[pallet::error] pub enum Error { /// The preimage of the call hash could not be loaded. UnavailablePreImage, /// The call could not be decoded. UndecodableCall, /// The weight of the decoded call was higher than the witness. InvalidCallWeightWitness, /// The call was not whitelisted. CallIsNotWhitelisted, /// The call was already whitelisted; No-Op. CallAlreadyWhitelisted, } #[pallet::storage] pub type WhitelistedCall = StorageMap<_, Twox64Concat, PreimageHash, (), OptionQuery>; #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::whitelist_call())] pub fn whitelist_call(origin: OriginFor, call_hash: PreimageHash) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; ensure!( !WhitelistedCall::::contains_key(call_hash), Error::::CallAlreadyWhitelisted, ); WhitelistedCall::::insert(call_hash, ()); T::Preimages::request(&call_hash); Self::deposit_event(Event::::CallWhitelisted { call_hash }); Ok(()) } #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] pub fn remove_whitelisted_call( origin: OriginFor, call_hash: PreimageHash, ) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; WhitelistedCall::::take(call_hash).ok_or(Error::::CallIsNotWhitelisted)?; T::Preimages::unrequest(&call_hash); Self::deposit_event(Event::::WhitelistedCallRemoved { call_hash }); Ok(()) } #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len) .saturating_add(*call_weight_witness) )] pub fn dispatch_whitelisted_call( origin: OriginFor, call_hash: PreimageHash, call_encoded_len: u32, call_weight_witness: Weight, ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; ensure!( WhitelistedCall::::contains_key(call_hash), Error::::CallIsNotWhitelisted, ); let call = T::Preimages::fetch(&call_hash, Some(call_encoded_len)) .map_err(|_| Error::::UnavailablePreImage)?; let call = ::RuntimeCall::decode_all_with_depth_limit( sp_api::MAX_EXTRINSIC_DEPTH, &mut &call[..], ) .map_err(|_| Error::::UndecodableCall)?; ensure!( call.get_dispatch_info().weight.all_lte(call_weight_witness), Error::::InvalidCallWeightWitness ); let actual_weight = Self::clean_and_dispatch(call_hash, call).map(|w| { w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len)) }); Ok(actual_weight.into()) } #[pallet::call_index(3)] #[pallet::weight({ let call_weight = call.get_dispatch_info().weight; let call_len = call.encoded_size() as u32; T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len) .saturating_add(call_weight) })] pub fn dispatch_whitelisted_call_with_preimage( origin: OriginFor, call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; let call_hash = call.blake2_256().into(); ensure!( WhitelistedCall::::contains_key(call_hash), Error::::CallIsNotWhitelisted, ); let call_len = call.encoded_size() as u32; let actual_weight = Self::clean_and_dispatch(call_hash, *call).map(|w| { w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)) }); Ok(actual_weight.into()) } } } impl Pallet { /// Clean whitelisting/preimage and dispatch call. /// /// Return the call actual weight of the dispatched call if there is some. fn clean_and_dispatch( call_hash: PreimageHash, call: ::RuntimeCall, ) -> Option { WhitelistedCall::::remove(call_hash); T::Preimages::unrequest(&call_hash); let result = call.dispatch(frame_system::Origin::::Root.into()); let call_actual_weight = match result { Ok(call_post_info) => call_post_info.actual_weight, Err(call_err) => call_err.post_info.actual_weight, }; Self::deposit_event(Event::::WhitelistedCallDispatched { call_hash, result }); call_actual_weight } }