pezframe_system/extensions/
authorize_call.rs1use crate::Config;
19use pezframe_support::{
20 dispatch::DispatchInfo,
21 pezpallet_prelude::{
22 Decode, DecodeWithMemTracking, DispatchResult, Encode, TransactionSource, TypeInfo, Weight,
23 },
24 traits::Authorize,
25 CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
26};
27use pezsp_runtime::{
28 traits::{
29 AsTransactionAuthorizedOrigin, Dispatchable, Implication, PostDispatchInfoOf,
30 TransactionExtension, ValidateResult,
31 },
32 transaction_validity::TransactionValidityError,
33};
34
35#[derive(
41 Encode,
42 Decode,
43 CloneNoBound,
44 EqNoBound,
45 PartialEqNoBound,
46 TypeInfo,
47 RuntimeDebugNoBound,
48 DecodeWithMemTracking,
49)]
50#[scale_info(skip_type_params(T))]
51pub struct AuthorizeCall<T>(core::marker::PhantomData<T>);
52
53impl<T> AuthorizeCall<T> {
54 pub fn new() -> Self {
55 Self(Default::default())
56 }
57}
58
59impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for AuthorizeCall<T>
60where
61 T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
62{
63 const IDENTIFIER: &'static str = "AuthorizeCall";
64 type Implicit = ();
65 type Val = Weight;
66 type Pre = Weight;
67
68 fn validate(
69 &self,
70 origin: T::RuntimeOrigin,
71 call: &T::RuntimeCall,
72 _info: &DispatchInfo,
73 _len: usize,
74 _self_implicit: Self::Implicit,
75 _inherited_implication: &impl Implication,
76 source: TransactionSource,
77 ) -> ValidateResult<Self::Val, T::RuntimeCall> {
78 if !origin.is_transaction_authorized() {
79 if let Some(authorize) = call.authorize(source) {
80 return authorize.map(|(validity, unspent)| {
81 (validity, unspent, crate::Origin::<T>::Authorized.into())
82 });
83 }
84 }
85
86 Ok((Default::default(), Weight::zero(), origin))
87 }
88
89 fn prepare(
90 self,
91 val: Self::Val,
92 _origin: &T::RuntimeOrigin,
93 _call: &T::RuntimeCall,
94 _info: &DispatchInfo,
95 _len: usize,
96 ) -> Result<Self::Pre, TransactionValidityError> {
97 Ok(val)
98 }
99
100 fn post_dispatch_details(
101 pre: Self::Pre,
102 _info: &DispatchInfo,
103 _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
104 _len: usize,
105 _result: &DispatchResult,
106 ) -> Result<Weight, TransactionValidityError> {
107 Ok(pre)
108 }
109
110 fn weight(&self, call: &T::RuntimeCall) -> Weight {
111 call.weight_of_authorize()
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use crate as pezframe_system;
118 use codec::Encode;
119 use pezframe_support::{
120 derive_impl, dispatch::GetDispatchInfo, pezpallet_prelude::TransactionSource,
121 traits::OriginTrait,
122 };
123 use pezsp_runtime::{
124 testing::UintAuthorityId,
125 traits::{Applyable, Checkable, TransactionExtension as _, TxBaseImplication},
126 transaction_validity::{
127 InvalidTransaction, TransactionSource::External, TransactionValidityError,
128 },
129 BuildStorage, DispatchError,
130 };
131
132 #[pezframe_support::pezpallet]
133 pub mod pallet1 {
134 use crate as pezframe_system;
135 use pezframe_support::pezpallet_prelude::*;
136 use pezframe_system::pezpallet_prelude::*;
137
138 pub const CALL_WEIGHT: Weight = Weight::from_all(4);
139 pub const AUTH_WEIGHT: Weight = Weight::from_all(5);
140
141 pub fn valid_transaction() -> ValidTransaction {
142 ValidTransaction {
143 priority: 10,
144 provides: vec![1u8.encode()],
145 requires: vec![],
146 longevity: 1000,
147 propagate: true,
148 }
149 }
150
151 #[pezpallet::pezpallet]
152 pub struct Pezpallet<T>(_);
153
154 #[pezpallet::config]
155 pub trait Config: pezframe_system::Config {}
156
157 #[pezpallet::call]
158 impl<T: Config> Pezpallet<T> {
159 #[pezpallet::weight(CALL_WEIGHT)]
160 #[pezpallet::call_index(0)]
161 #[pezpallet::authorize(|_source, valid| if *valid {
162 Ok((valid_transaction(), Weight::zero()))
163 } else {
164 Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
165 })]
166 #[pezpallet::weight_of_authorize(AUTH_WEIGHT)]
167 pub fn call1(origin: OriginFor<T>, valid: bool) -> DispatchResult {
168 crate::ensure_authorized(origin)?;
169 let _ = valid;
170 Ok(())
171 }
172 }
173 }
174
175 #[pezframe_support::runtime]
176 mod runtime {
177 #[runtime::runtime]
178 #[runtime::derive(
179 RuntimeCall,
180 RuntimeEvent,
181 RuntimeError,
182 RuntimeOrigin,
183 RuntimeFreezeReason,
184 RuntimeHoldReason,
185 RuntimeSlashReason,
186 RuntimeLockId,
187 RuntimeTask
188 )]
189 pub struct Runtime;
190
191 #[runtime::pezpallet_index(0)]
192 pub type System = pezframe_system::Pezpallet<Runtime>;
193
194 #[runtime::pezpallet_index(1)]
195 pub type Pallet1 = pallet1::Pezpallet<Runtime>;
196 }
197
198 pub type TransactionExtension = (pezframe_system::AuthorizeCall<Runtime>,);
199
200 pub type Header = pezsp_runtime::generic::Header<u32, pezsp_runtime::traits::BlakeTwo256>;
201 pub type Block = pezsp_runtime::generic::Block<Header, UncheckedExtrinsic>;
202 pub type UncheckedExtrinsic = pezsp_runtime::generic::UncheckedExtrinsic<
203 u64,
204 RuntimeCall,
205 UintAuthorityId,
206 TransactionExtension,
207 >;
208
209 #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
210 impl pezframe_system::Config for Runtime {
211 type Block = Block;
212 }
213
214 impl pallet1::Config for Runtime {}
215
216 pub fn new_test_ext() -> pezsp_io::TestExternalities {
217 let t = RuntimeGenesisConfig { ..Default::default() }.build_storage().unwrap();
218 t.into()
219 }
220
221 #[test]
222 fn valid_transaction() {
223 let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true });
224
225 new_test_ext().execute_with(|| {
226 let tx_ext = (pezframe_system::AuthorizeCall::<Runtime>::new(),);
227
228 let tx = UncheckedExtrinsic::new_transaction(call, tx_ext);
229
230 let info = tx.get_dispatch_info();
231 let len = tx.using_encoded(|e| e.len());
232
233 let checked =
234 Checkable::check(tx, &pezframe_system::ChainContext::<Runtime>::default())
235 .expect("Transaction is general so signature is good");
236
237 let valid_tx = checked
238 .validate::<Runtime>(TransactionSource::External, &info, len)
239 .expect("call valid");
240
241 let dispatch_result =
242 checked.apply::<Runtime>(&info, len).expect("Transaction is valid");
243
244 assert!(dispatch_result.is_ok());
245
246 let post_info = dispatch_result.unwrap_or_else(|e| e.post_info);
247
248 assert_eq!(valid_tx, pallet1::valid_transaction());
249 assert_eq!(info.call_weight, pallet1::CALL_WEIGHT);
250 assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT);
251 assert_eq!(post_info.actual_weight, Some(pallet1::CALL_WEIGHT + pallet1::AUTH_WEIGHT));
252 });
253 }
254
255 #[test]
256 fn invalid_transaction_fail_authorization() {
257 let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: false });
258
259 new_test_ext().execute_with(|| {
260 let tx_ext = (pezframe_system::AuthorizeCall::<Runtime>::new(),);
261
262 let tx = UncheckedExtrinsic::new_transaction(call, tx_ext);
263
264 let info = tx.get_dispatch_info();
265 let len = tx.using_encoded(|e| e.len());
266
267 let checked =
268 Checkable::check(tx, &pezframe_system::ChainContext::<Runtime>::default())
269 .expect("Transaction is general so signature is good");
270
271 let validate_err = checked
272 .validate::<Runtime>(TransactionSource::External, &info, len)
273 .expect_err("call is invalid");
274
275 let apply_err =
276 checked.apply::<Runtime>(&info, len).expect_err("Transaction is invalid");
277
278 assert_eq!(validate_err, TransactionValidityError::Invalid(InvalidTransaction::Call));
279 assert_eq!(apply_err, TransactionValidityError::Invalid(InvalidTransaction::Call));
280 assert_eq!(info.call_weight, pallet1::CALL_WEIGHT);
281 assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT);
282 });
283 }
284
285 #[test]
286 fn failing_transaction_invalid_origin() {
287 let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true });
288
289 new_test_ext().execute_with(|| {
290 let tx_ext = (pezframe_system::AuthorizeCall::<Runtime>::new(),);
291
292 let tx = UncheckedExtrinsic::new_signed(call, 42, 42.into(), tx_ext);
293
294 let info = tx.get_dispatch_info();
295 let len = tx.using_encoded(|e| e.len());
296
297 let checked =
298 Checkable::check(tx, &pezframe_system::ChainContext::<Runtime>::default())
299 .expect("Signature is good");
300
301 checked
302 .validate::<Runtime>(TransactionSource::External, &info, len)
303 .expect("Transaction is valid, tx ext doesn't deny none");
304
305 let dispatch_res = checked
306 .apply::<Runtime>(&info, len)
307 .expect("Transaction is valid")
308 .expect_err("Transaction is failing, because origin is wrong");
309
310 assert_eq!(dispatch_res.error, DispatchError::BadOrigin);
311 assert_eq!(info.call_weight, pallet1::CALL_WEIGHT);
312 assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT);
313 });
314 }
315
316 #[test]
317 fn call_filter_preserved() {
318 new_test_ext().execute_with(|| {
319 let ext = pezframe_system::AuthorizeCall::<Runtime>::new();
320 let filtered_call =
321 RuntimeCall::System(pezframe_system::Call::remark { remark: vec![] });
322
323 let origin = {
324 let mut o: RuntimeOrigin = crate::Origin::<Runtime>::Signed(42).into();
325 let filter_clone = filtered_call.clone();
326 o.add_filter(move |call| filter_clone != *call);
327 o
328 };
329
330 assert!(!origin.filter_call(&filtered_call));
331
332 let (_, _, new_origin) = ext
333 .validate(
334 origin,
335 &RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true }),
336 &crate::DispatchInfo::default(),
337 Default::default(),
338 (),
339 &TxBaseImplication(()),
340 External,
341 )
342 .expect("valid");
343
344 assert!(!new_origin.filter_call(&filtered_call));
345 });
346 }
347}