1#![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
55pub type ProvidedBlockNumberFor<T> =
61 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
62
63#[frame::pallet]
64pub mod pallet {
65 use super::*;
66
67 #[pallet::config]
68 pub trait Config: frame_system::Config {
69 #[allow(deprecated)]
71 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
72
73 type RuntimeCall: IsType<<Self as frame_system::Config>::RuntimeCall>
75 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
76 + GetDispatchInfo
77 + FullCodec
78 + TypeInfo
79 + From<frame_system::Call<Self>>
80 + Parameter;
81
82 type WhitelistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
84
85 type DispatchWhitelistedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
87
88 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
90
91 type DeferredDispatchExpiration: Get<ProvidedBlockNumberFor<Self>>;
93
94 type BlockNumberProvider: BlockNumberProvider;
96
97 type WeightInfo: WeightInfo;
99 }
100
101 #[pallet::pallet]
102 pub struct Pallet<T>(_);
103
104 #[pallet::event]
105 #[pallet::generate_deposit(pub(super) fn deposit_event)]
106 pub enum Event<T: Config> {
107 CallWhitelisted {
108 call_hash: T::Hash,
109 },
110 WhitelistedCallRemoved {
111 call_hash: T::Hash,
112 },
113 WhitelistedCallDispatched {
114 call_hash: T::Hash,
115 result: DispatchResultWithPostInfo,
116 },
117 DispatchDeferred {
119 call_hash: T::Hash,
120 },
121 DeferredDispatchRemoved {
123 call_hash: T::Hash,
124 },
125 DeferredDispatchExecuted {
131 call_hash: T::Hash,
132 who: T::AccountId,
133 },
134 }
135
136 #[pallet::error]
137 pub enum Error<T> {
138 UnavailablePreImage,
140 UndecodableCall,
142 InvalidCallWeightWitness,
144 CallIsNotWhitelisted,
146 CallAlreadyWhitelisted,
148 DeferredDispatchNotFound,
150 DeferredDispatchNotExpired,
152 AlreadyDeferred,
154 DeferredDispatchExpired,
156 }
157
158 #[pallet::storage]
159 pub type WhitelistedCall<T: Config> = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>;
160
161 #[pallet::storage]
164 pub type DeferredDispatch<T: Config> =
165 StorageMap<_, Twox64Concat, T::Hash, ProvidedBlockNumberFor<T>, OptionQuery>;
166
167 #[pallet::call]
168 impl<T: Config> Pallet<T> {
169 #[pallet::call_index(0)]
170 #[pallet::weight(T::WeightInfo::whitelist_call())]
171 pub fn whitelist_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
172 T::WhitelistOrigin::ensure_origin(origin)?;
173
174 ensure!(
175 !WhitelistedCall::<T>::contains_key(call_hash),
176 Error::<T>::CallAlreadyWhitelisted,
177 );
178
179 WhitelistedCall::<T>::insert(call_hash, ());
180 T::Preimages::request(&call_hash);
181
182 Self::deposit_event(Event::<T>::CallWhitelisted { call_hash });
183 Ok(())
184 }
185
186 #[pallet::call_index(1)]
187 #[pallet::weight(T::WeightInfo::remove_whitelisted_call())]
188 pub fn remove_whitelisted_call(origin: OriginFor<T>, call_hash: T::Hash) -> DispatchResult {
189 T::WhitelistOrigin::ensure_origin(origin)?;
190
191 WhitelistedCall::<T>::take(call_hash).ok_or(Error::<T>::CallIsNotWhitelisted)?;
192
193 T::Preimages::unrequest(&call_hash);
194
195 Self::deposit_event(Event::<T>::WhitelistedCallRemoved { call_hash });
196
197 Ok(())
198 }
199
200 #[pallet::call_index(2)]
201 #[pallet::weight(
202 T::WeightInfo::dispatch_whitelisted_call(*call_encoded_len)
203 .saturating_add(*call_weight_witness)
204 )]
205 pub fn dispatch_whitelisted_call(
206 origin: OriginFor<T>,
207 call_hash: T::Hash,
208 call_encoded_len: u32,
209 call_weight_witness: Weight,
210 ) -> DispatchResultWithPostInfo {
211 let relayer = match T::DispatchWhitelistedOrigin::try_origin(origin) {
212 Ok(_) if WhitelistedCall::<T>::contains_key(call_hash) => None,
213 Ok(_) => {
214 Self::defer_dispatch(call_hash)?;
215 return Ok(Some(T::WeightInfo::defer_dispatch(0)).into());
216 },
217 Err(dispatch_origin) => {
218 Some(Self::ensure_signed_deferred_dispatch(dispatch_origin, call_hash)?)
219 },
220 };
221
222 let call_data = T::Preimages::fetch(&call_hash, Some(call_encoded_len))
223 .map_err(|_| Error::<T>::UnavailablePreImage)?;
224
225 let call = <T as Config>::RuntimeCall::decode_all_with_depth_limit(
226 frame::deps::frame_support::MAX_EXTRINSIC_DEPTH,
227 &mut &call_data[..],
228 )
229 .map_err(|_| Error::<T>::UndecodableCall)?;
230
231 ensure!(
232 call.get_dispatch_info().call_weight.all_lte(call_weight_witness),
233 Error::<T>::InvalidCallWeightWitness
234 );
235
236 let pays_fee = if relayer.is_some() { Pays::No } else { Pays::Yes };
238
239 let call_actual_weight = Self::clean_and_dispatch(call_hash, call);
240 if let Some(who) = relayer {
241 Self::deposit_event(Event::<T>::DeferredDispatchExecuted { call_hash, who });
242 }
243
244 let actual_weight = call_actual_weight.map(|w| {
245 w.saturating_add(T::WeightInfo::dispatch_whitelisted_call(call_encoded_len))
246 });
247 Ok(PostDispatchInfo { actual_weight, pays_fee })
248 }
249
250 #[pallet::call_index(3)]
251 #[pallet::weight({
252 let call_weight = call.get_dispatch_info().call_weight;
253 let call_len = call.encoded_size() as u32;
254 T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)
255 .saturating_add(call_weight)
256 })]
257 pub fn dispatch_whitelisted_call_with_preimage(
258 origin: OriginFor<T>,
259 call: Box<<T as Config>::RuntimeCall>,
260 ) -> DispatchResultWithPostInfo {
261 let call_hash = T::Hashing::hash_of(&call).into();
262 let call_len = call.encoded_size() as u32;
263
264 let relayer = match T::DispatchWhitelistedOrigin::try_origin(origin) {
265 Ok(_) if WhitelistedCall::<T>::contains_key(call_hash) => None,
266 Ok(_) => {
267 Self::defer_dispatch(call_hash)?;
268 return Ok(Some(T::WeightInfo::defer_dispatch(call_len)).into());
269 },
270 Err(dispatch_origin) => {
271 Some(Self::ensure_signed_deferred_dispatch(dispatch_origin, call_hash)?)
272 },
273 };
274
275 let pays_fee = if relayer.is_some() { Pays::No } else { Pays::Yes };
277
278 let call_actual_weight = Self::clean_and_dispatch(call_hash, *call);
279 if let Some(who) = relayer {
280 Self::deposit_event(Event::<T>::DeferredDispatchExecuted { call_hash, who });
281 }
282
283 let actual_weight = call_actual_weight.map(|w| {
284 w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len))
285 });
286 Ok(PostDispatchInfo { actual_weight, pays_fee })
287 }
288
289 #[pallet::call_index(4)]
290 #[pallet::weight(T::WeightInfo::remove_deferred_dispatch())]
291 pub fn remove_deferred_dispatch(
292 origin: OriginFor<T>,
293 call_hash: T::Hash,
294 ) -> DispatchResultWithPostInfo {
295 ensure_signed(origin)?;
296
297 let expire_at = DeferredDispatch::<T>::get(call_hash)
298 .ok_or(Error::<T>::DeferredDispatchNotFound)?;
299
300 let now = T::BlockNumberProvider::current_block_number();
301
302 ensure!(now >= expire_at, Error::<T>::DeferredDispatchNotExpired);
303
304 DeferredDispatch::<T>::remove(call_hash);
305
306 Self::deposit_event(Event::<T>::DeferredDispatchRemoved { call_hash });
307
308 Ok(Pays::No.into())
309 }
310 }
311}
312
313impl<T: Config> Pallet<T> {
314 fn defer_dispatch(call_hash: T::Hash) -> DispatchResult {
319 let now = T::BlockNumberProvider::current_block_number();
320
321 let expire_at = now.saturating_add(T::DeferredDispatchExpiration::get());
322
323 ensure!(!DeferredDispatch::<T>::contains_key(call_hash), Error::<T>::AlreadyDeferred);
324
325 DeferredDispatch::<T>::insert(call_hash, expire_at);
326
327 Self::deposit_event(Event::<T>::DispatchDeferred { call_hash });
328
329 Ok(())
330 }
331
332 fn ensure_signed_deferred_dispatch(
346 origin: T::RuntimeOrigin,
347 call_hash: T::Hash,
348 ) -> Result<T::AccountId, DispatchError> {
349 let who = ensure_signed(origin)?;
350
351 let expire_at =
352 DeferredDispatch::<T>::get(call_hash).ok_or(Error::<T>::DeferredDispatchNotFound)?;
353
354 ensure!(
355 T::BlockNumberProvider::current_block_number() < expire_at,
356 Error::<T>::DeferredDispatchExpired
357 );
358
359 ensure!(WhitelistedCall::<T>::contains_key(call_hash), Error::<T>::CallIsNotWhitelisted);
360
361 Ok(who)
362 }
363
364 fn clean_and_dispatch(call_hash: T::Hash, call: <T as Config>::RuntimeCall) -> Option<Weight> {
368 WhitelistedCall::<T>::remove(call_hash);
369 T::Preimages::unrequest(&call_hash);
370 DeferredDispatch::<T>::remove(call_hash);
371
372 let result = call.dispatch(frame_system::Origin::<T>::Root.into());
373
374 let call_actual_weight = match result {
375 Ok(call_post_info) => call_post_info.actual_weight,
376 Err(call_err) => call_err.post_info.actual_weight,
377 };
378 Self::deposit_event(Event::<T>::WhitelistedCallDispatched { call_hash, result });
379
380 call_actual_weight
381 }
382}