1#![cfg_attr(not(feature = "std"), no_std)]
53
54#[cfg(feature = "runtime-benchmarks")]
55mod benchmarking;
56#[cfg(test)]
57mod mock;
58#[cfg(test)]
59mod tests;
60pub mod weights;
61pub use weights::WeightInfo;
62
63extern crate alloc;
64
65use alloc::{boxed::Box, vec};
66use codec::{DecodeLimit, Encode, FullCodec};
67use frame::{
68 deps::sp_runtime::transaction_validity::{
69 TransactionSource, TransactionValidityError, TransactionValidityWithRefund,
70 },
71 prelude::*,
72 traits::{QueryPreimage, StorePreimage},
73};
74use scale_info::TypeInfo;
75
76pub use pallet::*;
77
78pub type ProvidedBlockNumberFor<T> =
84 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
85
86#[frame::pallet]
87pub mod pallet {
88 use super::*;
89
90 #[pallet::config]
91 pub trait Config: frame_system::Config {
92 #[allow(deprecated)]
94 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
95
96 type RuntimeCall: IsType<<Self as frame_system::Config>::RuntimeCall>
98 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
99 + GetDispatchInfo
100 + FullCodec
101 + TypeInfo
102 + From<frame_system::Call<Self>>
103 + Parameter;
104
105 type WhitelistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
107
108 type DispatchWhitelistedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
114
115 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
117
118 type DeferredDispatchExpiration: Get<ProvidedBlockNumberFor<Self>>;
120
121 type BlockNumberProvider: BlockNumberProvider;
123
124 type WeightInfo: WeightInfo;
126 }
127
128 #[pallet::pallet]
129 pub struct Pallet<T>(_);
130
131 #[pallet::event]
132 #[pallet::generate_deposit(pub(super) fn deposit_event)]
133 pub enum Event<T: Config> {
134 CallWhitelisted {
135 call_hash: T::Hash,
136 },
137 WhitelistedCallRemoved {
138 call_hash: T::Hash,
139 },
140 WhitelistedCallDispatched {
141 call_hash: T::Hash,
142 result: DispatchResultWithPostInfo,
143 },
144 DispatchDeferred {
146 call_hash: T::Hash,
147 },
148 DeferredDispatchRemoved {
150 call_hash: T::Hash,
151 },
152 DeferredDispatchExecuted {
158 call_hash: T::Hash,
159 who: T::AccountId,
160 },
161 }
162
163 #[pallet::error]
164 pub enum Error<T> {
165 UnavailablePreImage,
167 UndecodableCall,
169 InvalidCallWeightWitness,
171 CallIsNotWhitelisted,
173 CallAlreadyWhitelisted,
175 DeferredDispatchNotFound,
177 DeferredDispatchNotExpired,
179 AlreadyDeferred,
181 DeferredDispatchExpired,
183 }
184
185 #[pallet::storage]
186 pub type WhitelistedCall<T: Config> = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>;
187
188 #[pallet::storage]
191 pub type DeferredDispatch<T: Config> =
192 StorageMap<_, Twox64Concat, T::Hash, ProvidedBlockNumberFor<T>, OptionQuery>;
193
194 #[pallet::call]
195 impl<T: Config> Pallet<T> {
196 #[pallet::call_index(0)]
197 #[pallet::weight(T::WeightInfo::whitelist_call())]
198 pub fn whitelist_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
199 T::WhitelistOrigin::ensure_origin(origin)?;
200
201 ensure!(
202 !WhitelistedCall::<T>::contains_key(call_hash),
203 Error::<T>::CallAlreadyWhitelisted,
204 );
205
206 WhitelistedCall::<T>::insert(call_hash, ());
207 T::Preimages::request(&call_hash);
208
209 Self::deposit_event(Event::<T>::CallWhitelisted { call_hash });
210 Ok(())
211 }
212
213 #[pallet::call_index(1)]
218 #[pallet::weight(T::WeightInfo::remove_whitelisted_call())]
219 pub fn remove_whitelisted_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
220 T::WhitelistOrigin::ensure_origin(origin)?;
221
222 WhitelistedCall::<T>::take(call_hash).ok_or(Error::<T>::CallIsNotWhitelisted)?;
223
224 T::Preimages::unrequest(&call_hash);
225
226 Self::deposit_event(Event::<T>::WhitelistedCallRemoved { call_hash });
227
228 Ok(())
229 }
230
231 #[pallet::authorize(Self::authorize_dispatch_whitelisted_call)]
232 #[pallet::weight_of_authorize(
233 T::WeightInfo::authorize_dispatch_whitelisted_call(*call_encoded_len)
234 )]
235 #[pallet::call_index(2)]
236 #[pallet::weight(
237 T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len)
238 .saturating_add(*call_weight_witness)
239 )]
240 pub fn dispatch_whitelisted_call(
241 origin: OriginFor<T>,
242 call_hash: T::Hash,
243 call_encoded_len: u32,
244 call_weight_witness: Weight,
245 ) -> DispatchResultWithPostInfo {
246 let relayer = match T::DispatchWhitelistedOrigin::try_origin(origin) {
247 Ok(_) if WhitelistedCall::<T>::contains_key(call_hash) => None,
248 Ok(_) => {
249 Self::defer_dispatch(call_hash)?;
250 return Ok(Some(T::WeightInfo::defer_dispatch(0)).into());
251 },
252 Err(dispatch_origin) => {
253 Some(Self::ensure_signed_deferred_dispatch(dispatch_origin, call_hash)?)
254 },
255 };
256
257 let call_data = T::Preimages::fetch(&call_hash, Some(call_encoded_len))
258 .map_err(|_| Error::<T>::UnavailablePreImage)?;
259
260 let call = <T as Config>::RuntimeCall::decode_all_with_depth_limit(
261 frame::deps::frame_support::MAX_EXTRINSIC_DEPTH,
262 &mut &call_data[..],
263 )
264 .map_err(|_| Error::<T>::UndecodableCall)?;
265
266 ensure!(
267 call.get_dispatch_info().call_weight.all_lte(call_weight_witness),
268 Error::<T>::InvalidCallWeightWitness
269 );
270
271 let pays_fee = if relayer.is_some() { Pays::No } else { Pays::Yes };
273
274 let call_actual_weight = Self::clean_and_dispatch(call_hash, call);
275 if let Some(who) = relayer {
276 Self::deposit_event(Event::<T>::DeferredDispatchExecuted { call_hash, who });
277 }
278
279 let actual_weight = call_actual_weight.map(|w| {
280 w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len))
281 });
282 Ok(PostDispatchInfo { actual_weight, pays_fee })
283 }
284
285 #[pallet::authorize(Self::authorize_dispatch_whitelisted_call_with_preimage)]
286 #[pallet::weight_of_authorize(
287 T::WeightInfo::authorize_dispatch_whitelisted_call_with_preimage(
288 call.encoded_size() as u32
289 )
290 )]
291 #[pallet::call_index(3)]
292 #[pallet::weight({
293 let call_weight = call.get_dispatch_info().call_weight;
294 let call_len = call.encoded_size() as u32;
295 T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)
296 .saturating_add(call_weight)
297 })]
298 pub fn dispatch_whitelisted_call_with_preimage(
299 origin: OriginFor<T>,
300 call: Box<<T as Config>::RuntimeCall>,
301 ) -> DispatchResultWithPostInfo {
302 let call_hash = T::Hashing::hash_of(&call).into();
303 let call_len = call.encoded_size() as u32;
304
305 let relayer = match T::DispatchWhitelistedOrigin::try_origin(origin) {
306 Ok(_) if WhitelistedCall::<T>::contains_key(call_hash) => None,
307 Ok(_) => {
308 Self::defer_dispatch(call_hash)?;
309 return Ok(Some(T::WeightInfo::defer_dispatch(call_len)).into());
310 },
311 Err(dispatch_origin) => {
312 Some(Self::ensure_signed_deferred_dispatch(dispatch_origin, call_hash)?)
313 },
314 };
315
316 let pays_fee = if relayer.is_some() { Pays::No } else { Pays::Yes };
318
319 let call_actual_weight = Self::clean_and_dispatch(call_hash, *call);
320 if let Some(who) = relayer {
321 Self::deposit_event(Event::<T>::DeferredDispatchExecuted { call_hash, who });
322 }
323
324 let actual_weight = call_actual_weight.map(|w| {
325 w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len))
326 });
327 Ok(PostDispatchInfo { actual_weight, pays_fee })
328 }
329
330 #[pallet::call_index(4)]
331 #[pallet::weight(T::WeightInfo::remove_deferred_dispatch())]
332 pub fn remove_deferred_dispatch(
333 origin: OriginFor<T>,
334 call_hash: T::Hash,
335 ) -> DispatchResultWithPostInfo {
336 ensure_signed(origin)?;
337
338 let expire_at = DeferredDispatch::<T>::get(call_hash)
339 .ok_or(Error::<T>::DeferredDispatchNotFound)?;
340
341 let now = T::BlockNumberProvider::current_block_number();
342
343 ensure!(now >= expire_at, Error::<T>::DeferredDispatchNotExpired);
344
345 DeferredDispatch::<T>::remove(call_hash);
346
347 Self::deposit_event(Event::<T>::DeferredDispatchRemoved { call_hash });
348
349 Ok(Pays::No.into())
350 }
351 }
352}
353
354impl<T: Config> Pallet<T> {
355 fn ensure_authorized_origin() -> Result<(), TransactionValidityError> {
358 let authorized: T::RuntimeOrigin =
359 frame_system::RawOrigin::<T::AccountId>::Authorized.into();
360 T::DispatchWhitelistedOrigin::try_origin(authorized)
361 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?;
362
363 Ok(())
364 }
365
366 fn ensure_whitelisted(call_hash: T::Hash) -> Result<(), TransactionValidityError> {
368 if !WhitelistedCall::<T>::contains_key(call_hash) {
369 return Err(TransactionValidityError::Invalid(InvalidTransaction::Call));
370 }
371
372 Ok(())
373 }
374
375 fn ensure_authorized_whitelisted_dispatch(
379 call_hash: T::Hash,
380 ) -> Result<(), TransactionValidityError> {
381 Self::ensure_authorized_origin()?;
382
383 Self::ensure_whitelisted(call_hash)
384 }
385
386 fn whitelisted_dispatch_validity(
391 call_hash: T::Hash,
392 call_encoded_len: u32,
393 call_weight: Weight,
394 ) -> TransactionValidityWithRefund {
395 Ok((
396 ValidTransaction {
397 provides: vec![(call_hash, call_encoded_len, call_weight).encode()],
398 ..Default::default()
399 },
400 Weight::zero(),
401 ))
402 }
403
404 fn authorize_dispatch_whitelisted_call(
406 _source: TransactionSource,
407 call_hash: &T::Hash,
408 call_encoded_len: &u32,
409 call_weight_witness: &Weight,
410 ) -> TransactionValidityWithRefund {
411 Self::ensure_authorized_whitelisted_dispatch(*call_hash)?;
412
413 let call_data = T::Preimages::fetch(call_hash, Some(*call_encoded_len))
416 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?;
417
418 let call = <T as Config>::RuntimeCall::decode_all_with_depth_limit(
419 frame::deps::frame_support::MAX_EXTRINSIC_DEPTH,
420 &mut &call_data[..],
421 )
422 .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?;
423
424 let call_weight = call.get_dispatch_info().call_weight;
425 if call_weight != *call_weight_witness {
426 return Err(TransactionValidityError::Invalid(InvalidTransaction::Call));
427 }
428
429 Self::whitelisted_dispatch_validity(*call_hash, *call_encoded_len, call_weight)
430 }
431
432 fn authorize_dispatch_whitelisted_call_with_preimage(
434 _source: TransactionSource,
435 call: &Box<<T as Config>::RuntimeCall>,
436 ) -> TransactionValidityWithRefund {
437 Self::ensure_authorized_origin()?;
439
440 let call_hash = T::Hashing::hash_of(call).into();
441 Self::ensure_whitelisted(call_hash)?;
442
443 Self::whitelisted_dispatch_validity(
445 call_hash,
446 call.encoded_size() as u32,
447 call.get_dispatch_info().call_weight,
448 )
449 }
450
451 fn defer_dispatch(call_hash: T::Hash) -> DispatchResult {
456 let now = T::BlockNumberProvider::current_block_number();
457
458 let expire_at = now.saturating_add(T::DeferredDispatchExpiration::get());
459
460 ensure!(!DeferredDispatch::<T>::contains_key(call_hash), Error::<T>::AlreadyDeferred);
461
462 DeferredDispatch::<T>::insert(call_hash, expire_at);
463
464 Self::deposit_event(Event::<T>::DispatchDeferred { call_hash });
465
466 Ok(())
467 }
468
469 fn ensure_signed_deferred_dispatch(
483 origin: T::RuntimeOrigin,
484 call_hash: T::Hash,
485 ) -> Result<T::AccountId, DispatchError> {
486 let who = ensure_signed(origin)?;
487
488 let expire_at =
489 DeferredDispatch::<T>::get(call_hash).ok_or(Error::<T>::DeferredDispatchNotFound)?;
490
491 ensure!(
492 T::BlockNumberProvider::current_block_number() < expire_at,
493 Error::<T>::DeferredDispatchExpired
494 );
495
496 ensure!(WhitelistedCall::<T>::contains_key(call_hash), Error::<T>::CallIsNotWhitelisted);
497
498 Ok(who)
499 }
500
501 fn clean_and_dispatch(call_hash: T::Hash, call: <T as Config>::RuntimeCall) -> Option<Weight> {
505 WhitelistedCall::<T>::remove(call_hash);
506 T::Preimages::unrequest(&call_hash);
507 DeferredDispatch::<T>::remove(call_hash);
508
509 let result = call.dispatch(frame_system::Origin::<T>::Root.into());
510
511 let call_actual_weight = match result {
512 Ok(call_post_info) => call_post_info.actual_weight,
513 Err(call_err) => call_err.post_info.actual_weight,
514 };
515 Self::deposit_event(Event::<T>::WhitelistedCallDispatched { call_hash, result });
516
517 call_actual_weight
518 }
519}