1#![cfg_attr(not(feature = "std"), no_std)]
44
45#[cfg(feature = "runtime-benchmarks")]
46mod benchmarking;
47#[cfg(test)]
48mod mock;
49#[cfg(test)]
50mod tests;
51pub mod weights;
52pub use weights::WeightInfo;
53
54extern crate alloc;
55
56use alloc::boxed::Box;
57use codec::{DecodeLimit, Encode, FullCodec};
58use frame::{
59 prelude::*,
60 traits::{QueryPreimage, StorePreimage},
61};
62use scale_info::TypeInfo;
63
64pub use pallet::*;
65
66pub type ProvidedBlockNumberFor<T> =
72 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
73
74#[frame::pallet]
75pub mod pallet {
76 use super::*;
77
78 #[pallet::config]
79 pub trait Config: frame_system::Config {
80 #[allow(deprecated)]
82 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
83
84 type RuntimeCall: IsType<<Self as frame_system::Config>::RuntimeCall>
86 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
87 + GetDispatchInfo
88 + FullCodec
89 + TypeInfo
90 + From<frame_system::Call<Self>>
91 + Parameter;
92
93 type WhitelistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
95
96 type DispatchWhitelistedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
98
99 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
101
102 type DeferredDispatchExpiration: Get<ProvidedBlockNumberFor<Self>>;
104
105 type BlockNumberProvider: BlockNumberProvider;
107
108 type WeightInfo: WeightInfo;
110 }
111
112 #[pallet::pallet]
113 pub struct Pallet<T>(_);
114
115 #[pallet::event]
116 #[pallet::generate_deposit(pub(super) fn deposit_event)]
117 pub enum Event<T: Config> {
118 CallWhitelisted {
119 call_hash: T::Hash,
120 },
121 WhitelistedCallRemoved {
122 call_hash: T::Hash,
123 },
124 WhitelistedCallDispatched {
125 call_hash: T::Hash,
126 result: DispatchResultWithPostInfo,
127 },
128 DispatchDeferred {
130 call_hash: T::Hash,
131 },
132 DeferredDispatchRemoved {
134 call_hash: T::Hash,
135 },
136 DeferredDispatchExecuted {
142 call_hash: T::Hash,
143 who: T::AccountId,
144 },
145 }
146
147 #[pallet::error]
148 pub enum Error<T> {
149 UnavailablePreImage,
151 UndecodableCall,
153 InvalidCallWeightWitness,
155 CallIsNotWhitelisted,
157 CallAlreadyWhitelisted,
159 DeferredDispatchNotFound,
161 DeferredDispatchNotExpired,
163 AlreadyDeferred,
165 DeferredDispatchExpired,
167 }
168
169 #[pallet::storage]
170 pub type WhitelistedCall<T: Config> = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>;
171
172 #[pallet::storage]
175 pub type DeferredDispatch<T: Config> =
176 StorageMap<_, Twox64Concat, T::Hash, ProvidedBlockNumberFor<T>, OptionQuery>;
177
178 #[pallet::call]
179 impl<T: Config> Pallet<T> {
180 #[pallet::call_index(0)]
181 #[pallet::weight(T::WeightInfo::whitelist_call())]
182 pub fn whitelist_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
183 T::WhitelistOrigin::ensure_origin(origin)?;
184
185 ensure!(
186 !WhitelistedCall::<T>::contains_key(call_hash),
187 Error::<T>::CallAlreadyWhitelisted,
188 );
189
190 WhitelistedCall::<T>::insert(call_hash, ());
191 T::Preimages::request(&call_hash);
192
193 Self::deposit_event(Event::<T>::CallWhitelisted { call_hash });
194 Ok(())
195 }
196
197 #[pallet::call_index(1)]
202 #[pallet::weight(T::WeightInfo::remove_whitelisted_call())]
203 pub fn remove_whitelisted_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
204 T::WhitelistOrigin::ensure_origin(origin)?;
205
206 WhitelistedCall::<T>::take(call_hash).ok_or(Error::<T>::CallIsNotWhitelisted)?;
207
208 T::Preimages::unrequest(&call_hash);
209
210 Self::deposit_event(Event::<T>::WhitelistedCallRemoved { call_hash });
211
212 Ok(())
213 }
214
215 #[pallet::call_index(2)]
216 #[pallet::weight(
217 T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len)
218 .saturating_add(*call_weight_witness)
219 )]
220 pub fn dispatch_whitelisted_call(
221 origin: OriginFor<T>,
222 call_hash: T::Hash,
223 call_encoded_len: u32,
224 call_weight_witness: Weight,
225 ) -> DispatchResultWithPostInfo {
226 let relayer = match T::DispatchWhitelistedOrigin::try_origin(origin) {
227 Ok(_) if WhitelistedCall::<T>::contains_key(call_hash) => None,
228 Ok(_) => {
229 Self::defer_dispatch(call_hash)?;
230 return Ok(Some(T::WeightInfo::defer_dispatch(0)).into());
231 },
232 Err(dispatch_origin) => {
233 Some(Self::ensure_signed_deferred_dispatch(dispatch_origin, call_hash)?)
234 },
235 };
236
237 let call_data = T::Preimages::fetch(&call_hash, Some(call_encoded_len))
238 .map_err(|_| Error::<T>::UnavailablePreImage)?;
239
240 let call = <T as Config>::RuntimeCall::decode_all_with_depth_limit(
241 frame::deps::frame_support::MAX_EXTRINSIC_DEPTH,
242 &mut &call_data[..],
243 )
244 .map_err(|_| Error::<T>::UndecodableCall)?;
245
246 ensure!(
247 call.get_dispatch_info().call_weight.all_lte(call_weight_witness),
248 Error::<T>::InvalidCallWeightWitness
249 );
250
251 let pays_fee = if relayer.is_some() { Pays::No } else { Pays::Yes };
253
254 let call_actual_weight = Self::clean_and_dispatch(call_hash, call);
255 if let Some(who) = relayer {
256 Self::deposit_event(Event::<T>::DeferredDispatchExecuted { call_hash, who });
257 }
258
259 let actual_weight = call_actual_weight.map(|w| {
260 w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len))
261 });
262 Ok(PostDispatchInfo { actual_weight, pays_fee })
263 }
264
265 #[pallet::call_index(3)]
266 #[pallet::weight({
267 let call_weight = call.get_dispatch_info().call_weight;
268 let call_len = call.encoded_size() as u32;
269 T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)
270 .saturating_add(call_weight)
271 })]
272 pub fn dispatch_whitelisted_call_with_preimage(
273 origin: OriginFor<T>,
274 call: Box<<T as Config>::RuntimeCall>,
275 ) -> DispatchResultWithPostInfo {
276 let call_hash = T::Hashing::hash_of(&call).into();
277 let call_len = call.encoded_size() as u32;
278
279 let relayer = match T::DispatchWhitelistedOrigin::try_origin(origin) {
280 Ok(_) if WhitelistedCall::<T>::contains_key(call_hash) => None,
281 Ok(_) => {
282 Self::defer_dispatch(call_hash)?;
283 return Ok(Some(T::WeightInfo::defer_dispatch(call_len)).into());
284 },
285 Err(dispatch_origin) => {
286 Some(Self::ensure_signed_deferred_dispatch(dispatch_origin, call_hash)?)
287 },
288 };
289
290 let pays_fee = if relayer.is_some() { Pays::No } else { Pays::Yes };
292
293 let call_actual_weight = Self::clean_and_dispatch(call_hash, *call);
294 if let Some(who) = relayer {
295 Self::deposit_event(Event::<T>::DeferredDispatchExecuted { call_hash, who });
296 }
297
298 let actual_weight = call_actual_weight.map(|w| {
299 w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len))
300 });
301 Ok(PostDispatchInfo { actual_weight, pays_fee })
302 }
303
304 #[pallet::call_index(4)]
305 #[pallet::weight(T::WeightInfo::remove_deferred_dispatch())]
306 pub fn remove_deferred_dispatch(
307 origin: OriginFor<T>,
308 call_hash: T::Hash,
309 ) -> DispatchResultWithPostInfo {
310 ensure_signed(origin)?;
311
312 let expire_at = DeferredDispatch::<T>::get(call_hash)
313 .ok_or(Error::<T>::DeferredDispatchNotFound)?;
314
315 let now = T::BlockNumberProvider::current_block_number();
316
317 ensure!(now >= expire_at, Error::<T>::DeferredDispatchNotExpired);
318
319 DeferredDispatch::<T>::remove(call_hash);
320
321 Self::deposit_event(Event::<T>::DeferredDispatchRemoved { call_hash });
322
323 Ok(Pays::No.into())
324 }
325 }
326}
327
328impl<T: Config> Pallet<T> {
329 fn defer_dispatch(call_hash: T::Hash) -> DispatchResult {
334 let now = T::BlockNumberProvider::current_block_number();
335
336 let expire_at = now.saturating_add(T::DeferredDispatchExpiration::get());
337
338 ensure!(!DeferredDispatch::<T>::contains_key(call_hash), Error::<T>::AlreadyDeferred);
339
340 DeferredDispatch::<T>::insert(call_hash, expire_at);
341
342 Self::deposit_event(Event::<T>::DispatchDeferred { call_hash });
343
344 Ok(())
345 }
346
347 fn ensure_signed_deferred_dispatch(
361 origin: T::RuntimeOrigin,
362 call_hash: T::Hash,
363 ) -> Result<T::AccountId, DispatchError> {
364 let who = ensure_signed(origin)?;
365
366 let expire_at =
367 DeferredDispatch::<T>::get(call_hash).ok_or(Error::<T>::DeferredDispatchNotFound)?;
368
369 ensure!(
370 T::BlockNumberProvider::current_block_number() < expire_at,
371 Error::<T>::DeferredDispatchExpired
372 );
373
374 ensure!(WhitelistedCall::<T>::contains_key(call_hash), Error::<T>::CallIsNotWhitelisted);
375
376 Ok(who)
377 }
378
379 fn clean_and_dispatch(call_hash: T::Hash, call: <T as Config>::RuntimeCall) -> Option<Weight> {
383 WhitelistedCall::<T>::remove(call_hash);
384 T::Preimages::unrequest(&call_hash);
385 DeferredDispatch::<T>::remove(call_hash);
386
387 let result = call.dispatch(frame_system::Origin::<T>::Root.into());
388
389 let call_actual_weight = match result {
390 Ok(call_post_info) => call_post_info.actual_weight,
391 Err(call_err) => call_err.post_info.actual_weight,
392 };
393 Self::deposit_event(Event::<T>::WhitelistedCallDispatched { call_hash, result });
394
395 call_actual_weight
396 }
397}