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
55#[frame::pallet]
56pub mod pallet {
57 use super::*;
58
59 #[pallet::config]
60 pub trait Config: frame_system::Config {
61 #[allow(deprecated)]
63 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
64
65 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 type WhitelistOrigin: EnsureOrigin<Self::RuntimeOrigin>;
76
77 type DispatchWhitelistedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
79
80 type Preimages: QueryPreimage<H = Self::Hashing> + StorePreimage;
82
83 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 UnavailablePreImage,
102 UndecodableCall,
104 InvalidCallWeightWitness,
106 CallIsNotWhitelisted,
108 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 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}