referrerpolicy=no-referrer-when-downgrade

pallet_whitelist/
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//! # Whitelist Pallet
19//!
20//! - [`Config`]
21//! - [`Call`]
22//!
23//! ## Overview
24//!
25//! Allow some configurable origin: [`Config::WhitelistOrigin`] to whitelist some hash of a call,
26//! and allow another configurable origin: [`Config::DispatchWhitelistedOrigin`] to dispatch them
27//! with the root origin.
28//!
29//! In the meantime the call corresponding to the hash must have been submitted to the pre-image
30//! handler [`pallet::Config::Preimages`].
31
32#![cfg_attr(not(feature = "std"), no_std)]
33
34#[cfg(feature = "runtime-benchmarks")]
35mod benchmarking;
36#[cfg(test)]
37mod mock;
38#[cfg(test)]
39mod tests;
40pub mod weights;
41pub use weights::WeightInfo;
42
43extern crate alloc;
44
45use alloc::boxed::Box;
46use codec::{DecodeLimit, Encode, FullCodec};
47use frame::{
48	prelude::*,
49	traits::{QueryPreimage, StorePreimage},
50};
51use scale_info::TypeInfo;
52
53pub use pallet::*;
54
55#[frame::pallet]
56pub mod pallet {
57	use super::*;
58
59	#[pallet::config]
60	pub trait Config: frame_system::Config {
61		/// The overarching event type.
62		#[allow(deprecated)]
63		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
64
65		/// The overarching call type.
66		type RuntimeCall: IsType<<Self as frame_system::Config>::RuntimeCall>
67			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
68			+ GetDispatchInfo
69			+ FullCodec
70			+ TypeInfo
71			+ From<frame_system::Call<Self>>
72			+ Parameter;
73
74		/// Required origin for whitelisting a call.
75		type WhitelistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
76
77		/// Required origin for dispatching whitelisted call with root origin.
78		type DispatchWhitelistedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
79
80		/// The handler of pre-images.
81		type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
82
83		/// The weight information for this pallet.
84		type WeightInfo: WeightInfo;
85	}
86
87	#[pallet::pallet]
88	pub struct Pallet<T>(_);
89
90	#[pallet::event]
91	#[pallet::generate_deposit(pub(super) fn deposit_event)]
92	pub enum Event<T: Config> {
93		CallWhitelisted { call_hash: T::Hash },
94		WhitelistedCallRemoved { call_hash: T::Hash },
95		WhitelistedCallDispatched { call_hash: T::Hash, result: DispatchResultWithPostInfo },
96	}
97
98	#[pallet::error]
99	pub enum Error<T> {
100		/// The preimage of the call hash could not be loaded.
101		UnavailablePreImage,
102		/// The call could not be decoded.
103		UndecodableCall,
104		/// The weight of the decoded call was higher than the witness.
105		InvalidCallWeightWitness,
106		/// The call was not whitelisted.
107		CallIsNotWhitelisted,
108		/// The call was already whitelisted; No-Op.
109		CallAlreadyWhitelisted,
110	}
111
112	#[pallet::storage]
113	pub type WhitelistedCall<T: Config> = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>;
114
115	#[pallet::call]
116	impl<T: Config> Pallet<T> {
117		#[pallet::call_index(0)]
118		#[pallet::weight(T::WeightInfo::whitelist_call())]
119		pub fn whitelist_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
120			T::WhitelistOrigin::ensure_origin(origin)?;
121
122			ensure!(
123				!WhitelistedCall::<T>::contains_key(call_hash),
124				Error::<T>::CallAlreadyWhitelisted,
125			);
126
127			WhitelistedCall::<T>::insert(call_hash, ());
128			T::Preimages::request(&call_hash);
129
130			Self::deposit_event(Event::<T>::CallWhitelisted { call_hash });
131
132			Ok(())
133		}
134
135		#[pallet::call_index(1)]
136		#[pallet::weight(T::WeightInfo::remove_whitelisted_call())]
137		pub fn remove_whitelisted_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
138			T::WhitelistOrigin::ensure_origin(origin)?;
139
140			WhitelistedCall::<T>::take(call_hash).ok_or(Error::<T>::CallIsNotWhitelisted)?;
141
142			T::Preimages::unrequest(&call_hash);
143
144			Self::deposit_event(Event::<T>::WhitelistedCallRemoved { call_hash });
145
146			Ok(())
147		}
148
149		#[pallet::call_index(2)]
150		#[pallet::weight(
151			T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len)
152				.saturating_add(*call_weight_witness)
153		)]
154		pub fn dispatch_whitelisted_call(
155			origin: OriginFor<T>,
156			call_hash: T::Hash,
157			call_encoded_len: u32,
158			call_weight_witness: Weight,
159		) -> DispatchResultWithPostInfo {
160			T::DispatchWhitelistedOrigin::ensure_origin(origin)?;
161
162			ensure!(
163				WhitelistedCall::<T>::contains_key(call_hash),
164				Error::<T>::CallIsNotWhitelisted,
165			);
166
167			let call = T::Preimages::fetch(&call_hash, Some(call_encoded_len))
168				.map_err(|_| Error::<T>::UnavailablePreImage)?;
169
170			let call = <T as Config>::RuntimeCall::decode_all_with_depth_limit(
171				frame::deps::frame_support::MAX_EXTRINSIC_DEPTH,
172				&mut &call[..],
173			)
174			.map_err(|_| Error::<T>::UndecodableCall)?;
175
176			ensure!(
177				call.get_dispatch_info().call_weight.all_lte(call_weight_witness),
178				Error::<T>::InvalidCallWeightWitness
179			);
180
181			let actual_weight = Self::clean_and_dispatch(call_hash, call).map(|w| {
182				w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len))
183			});
184
185			Ok(actual_weight.into())
186		}
187
188		#[pallet::call_index(3)]
189		#[pallet::weight({
190			let call_weight = call.get_dispatch_info().call_weight;
191			let call_len = call.encoded_size() as u32;
192
193			T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)
194				.saturating_add(call_weight)
195		})]
196		pub fn dispatch_whitelisted_call_with_preimage(
197			origin: OriginFor<T>,
198			call: Box<<T as Config>::RuntimeCall>,
199		) -> DispatchResultWithPostInfo {
200			T::DispatchWhitelistedOrigin::ensure_origin(origin)?;
201
202			let call_hash = T::Hashing::hash_of(&call).into();
203
204			ensure!(
205				WhitelistedCall::<T>::contains_key(call_hash),
206				Error::<T>::CallIsNotWhitelisted,
207			);
208
209			let call_len = call.encoded_size() as u32;
210			let actual_weight = Self::clean_and_dispatch(call_hash, *call).map(|w| {
211				w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len))
212			});
213
214			Ok(actual_weight.into())
215		}
216	}
217}
218
219impl<T: Config> Pallet<T> {
220	/// Clean whitelisting/preimage and dispatch call.
221	///
222	/// Return the call actual weight of the dispatched call if there is some.
223	fn clean_and_dispatch(call_hash: T::Hash, call: <T as Config>::RuntimeCall) -> Option<Weight> {
224		WhitelistedCall::<T>::remove(call_hash);
225
226		T::Preimages::unrequest(&call_hash);
227
228		let result = call.dispatch(frame_system::Origin::<T>::Root.into());
229
230		let call_actual_weight = match result {
231			Ok(call_post_info) => call_post_info.actual_weight,
232			Err(call_err) => call_err.post_info.actual_weight,
233		};
234
235		Self::deposit_event(Event::<T>::WhitelistedCallDispatched { call_hash, result });
236
237		call_actual_weight
238	}
239}