// This file is part of Substrate. // Copyright (C) 2021 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 to the //! pre-image handler [`PreimageProvider`]. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; pub mod weights; use codec::{DecodeLimit, Encode, FullCodec}; use frame_support::{ ensure, traits::{PreimageProvider, PreimageRecipient}, weights::{GetDispatchInfo, PostDispatchInfo, Weight}, }; use scale_info::TypeInfo; use sp_runtime::traits::{Dispatchable, Hash}; use sp_std::prelude::*; pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; use crate::weights::WeightInfo; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type Event: From> + IsType<::Event>; /// The overarching call type. type Call: IsType<::Call> + 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. // NOTE: recipient is only needed for benchmarks. type PreimageProvider: PreimageProvider + PreimageRecipient; /// The weight information for this pallet. type WeightInfo: WeightInfo; } #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { CallWhitelisted { call_hash: T::Hash }, WhitelistedCallRemoved { call_hash: T::Hash }, WhitelistedCallDispatched { call_hash: T::Hash, 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, T::Hash, (), OptionQuery>; #[pallet::call] impl Pallet { #[pallet::weight(T::WeightInfo::whitelist_call())] pub fn whitelist_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; ensure!( !WhitelistedCall::::contains_key(call_hash), Error::::CallAlreadyWhitelisted, ); WhitelistedCall::::insert(call_hash, ()); T::PreimageProvider::request_preimage(&call_hash); Self::deposit_event(Event::::CallWhitelisted { call_hash }); Ok(()) } #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] pub fn remove_whitelisted_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { T::WhitelistOrigin::ensure_origin(origin)?; WhitelistedCall::::take(call_hash).ok_or(Error::::CallIsNotWhitelisted)?; T::PreimageProvider::unrequest_preimage(&call_hash); Self::deposit_event(Event::::WhitelistedCallRemoved { call_hash }); Ok(()) } #[pallet::weight( T::WeightInfo::dispatch_whitelisted_call().saturating_add(*call_weight_witness) )] pub fn dispatch_whitelisted_call( origin: OriginFor, call_hash: T::Hash, call_weight_witness: Weight, ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; ensure!( WhitelistedCall::::contains_key(call_hash), Error::::CallIsNotWhitelisted, ); let call = T::PreimageProvider::get_preimage(&call_hash) .ok_or(Error::::UnavailablePreImage)?; let call = ::Call::decode_all_with_depth_limit( sp_api::MAX_EXTRINSIC_DEPTH, &mut &call[..], ) .map_err(|_| Error::::UndecodableCall)?; ensure!( call.get_dispatch_info().weight <= 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())); Ok(actual_weight.into()) } #[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<::Call>, ) -> DispatchResultWithPostInfo { T::DispatchWhitelistedOrigin::ensure_origin(origin)?; let call_hash = ::Hashing::hash_of(&call); 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: T::Hash, call: ::Call) -> Option { WhitelistedCall::::remove(call_hash); T::PreimageProvider::unrequest_preimage(&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 } }