1use crate::Config;
19use frame_support::{
20 dispatch::DispatchInfo,
21 pallet_prelude::{
22 Decode, DecodeWithMemTracking, DispatchResult, Encode, TransactionSource, TypeInfo, Weight,
23 },
24 traits::Authorize,
25 CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound,
26};
27use sp_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 DebugNoBound,
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(), call.weight_of_authorize(), origin))
88 }
89
90 fn prepare(
91 self,
92 val: Self::Val,
93 _origin: &T::RuntimeOrigin,
94 _call: &T::RuntimeCall,
95 _info: &DispatchInfo,
96 _len: usize,
97 ) -> Result<Self::Pre, TransactionValidityError> {
98 Ok(val)
99 }
100
101 fn post_dispatch_details(
102 pre: Self::Pre,
103 _info: &DispatchInfo,
104 _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
105 _len: usize,
106 _result: &DispatchResult,
107 ) -> Result<Weight, TransactionValidityError> {
108 Ok(pre)
109 }
110
111 fn weight(&self, call: &T::RuntimeCall) -> Weight {
112 call.weight_of_authorize()
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use crate as frame_system;
119 use codec::Encode;
120 use frame_support::{
121 derive_impl, dispatch::GetDispatchInfo, pallet_prelude::TransactionSource,
122 traits::OriginTrait,
123 };
124 use sp_runtime::{
125 testing::UintAuthorityId,
126 traits::{Applyable, Checkable, TransactionExtension as _, TxBaseImplication},
127 transaction_validity::{
128 InvalidTransaction, TransactionSource::External, TransactionValidityError,
129 },
130 BuildStorage, DispatchError,
131 };
132
133 #[frame_support::pallet]
134 pub mod pallet1 {
135 use crate as frame_system;
136 use frame_support::pallet_prelude::*;
137 use frame_system::pallet_prelude::*;
138
139 pub const CALL_WEIGHT: Weight = Weight::from_all(4);
140 pub const AUTH_WEIGHT: Weight = Weight::from_all(5);
141
142 pub fn valid_transaction() -> ValidTransaction {
143 ValidTransaction {
144 priority: 10,
145 provides: vec![1u8.encode()],
146 requires: vec![],
147 longevity: 1000,
148 propagate: true,
149 }
150 }
151
152 #[pallet::pallet]
153 pub struct Pallet<T>(_);
154
155 #[pallet::config]
156 pub trait Config: frame_system::Config {}
157
158 #[pallet::call]
159 impl<T: Config> Pallet<T> {
160 #[pallet::weight(CALL_WEIGHT)]
161 #[pallet::call_index(0)]
162 #[pallet::authorize(|_source, valid| if *valid {
163 Ok((valid_transaction(), Weight::zero()))
164 } else {
165 Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
166 })]
167 #[pallet::weight_of_authorize(AUTH_WEIGHT)]
168 pub fn call1(origin: OriginFor<T>, valid: bool) -> DispatchResult {
169 crate::ensure_authorized(origin)?;
170 let _ = valid;
171 Ok(())
172 }
173 }
174 }
175
176 #[frame_support::runtime]
177 mod runtime {
178 #[runtime::runtime]
179 #[runtime::derive(
180 RuntimeCall,
181 RuntimeEvent,
182 RuntimeError,
183 RuntimeOrigin,
184 RuntimeFreezeReason,
185 RuntimeHoldReason,
186 RuntimeSlashReason,
187 RuntimeLockId,
188 RuntimeTask
189 )]
190 pub struct Runtime;
191
192 #[runtime::pallet_index(0)]
193 pub type System = frame_system::Pallet<Runtime>;
194
195 #[runtime::pallet_index(1)]
196 pub type Pallet1 = pallet1::Pallet<Runtime>;
197 }
198
199 pub type TransactionExtension = (frame_system::AuthorizeCall<Runtime>,);
200
201 pub type Header = sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>;
202 pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;
203 pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic<
204 u64,
205 RuntimeCall,
206 UintAuthorityId,
207 TransactionExtension,
208 >;
209
210 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
211 impl frame_system::Config for Runtime {
212 type Block = Block;
213 }
214
215 impl pallet1::Config for Runtime {}
216
217 pub fn new_test_ext() -> sp_io::TestExternalities {
218 let t = RuntimeGenesisConfig { ..Default::default() }.build_storage().unwrap();
219 t.into()
220 }
221
222 #[test]
223 fn valid_transaction() {
224 let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true });
225
226 new_test_ext().execute_with(|| {
227 let tx_ext = (frame_system::AuthorizeCall::<Runtime>::new(),);
228
229 let tx = UncheckedExtrinsic::new_transaction(call, tx_ext);
230
231 let info = tx.get_dispatch_info();
232 let len = tx.using_encoded(|e| e.len());
233
234 let checked = Checkable::check(tx, &frame_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 = (frame_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 = Checkable::check(tx, &frame_system::ChainContext::<Runtime>::default())
268 .expect("Transaction is general so signature is good");
269
270 let validate_err = checked
271 .validate::<Runtime>(TransactionSource::External, &info, len)
272 .expect_err("call is invalid");
273
274 let apply_err =
275 checked.apply::<Runtime>(&info, len).expect_err("Transaction is invalid");
276
277 assert_eq!(validate_err, TransactionValidityError::Invalid(InvalidTransaction::Call));
278 assert_eq!(apply_err, TransactionValidityError::Invalid(InvalidTransaction::Call));
279 assert_eq!(info.call_weight, pallet1::CALL_WEIGHT);
280 assert_eq!(info.extension_weight, pallet1::AUTH_WEIGHT);
281 });
282 }
283
284 #[test]
285 fn failing_transaction_invalid_origin() {
286 let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true });
287
288 new_test_ext().execute_with(|| {
289 let tx_ext = (frame_system::AuthorizeCall::<Runtime>::new(),);
290
291 let tx = UncheckedExtrinsic::new_signed(call, 42, 42.into(), tx_ext);
292
293 let info = tx.get_dispatch_info();
294 let len = tx.using_encoded(|e| e.len());
295
296 let checked = Checkable::check(tx, &frame_system::ChainContext::<Runtime>::default())
297 .expect("Signature is good");
298
299 checked
300 .validate::<Runtime>(TransactionSource::External, &info, len)
301 .expect("Transaction is valid, tx ext doesn't deny none");
302
303 let dispatch_res = checked
304 .apply::<Runtime>(&info, len)
305 .expect("Transaction is valid")
306 .expect_err("Transaction is failing, because origin is wrong");
307
308 assert_eq!(dispatch_res.error, DispatchError::BadOrigin);
309 assert_eq!(dispatch_res.post_info.actual_weight, Some(pallet1::CALL_WEIGHT));
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 = frame_system::AuthorizeCall::<Runtime>::new();
320 let filtered_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] });
321
322 let origin = {
323 let mut o: RuntimeOrigin = crate::Origin::<Runtime>::Signed(42).into();
324 let filter_clone = filtered_call.clone();
325 o.add_filter(move |call| filter_clone != *call);
326 o
327 };
328
329 assert!(!origin.filter_call(&filtered_call));
330
331 let (_, _, new_origin) = ext
332 .validate(
333 origin,
334 &RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true }),
335 &crate::DispatchInfo::default(),
336 Default::default(),
337 (),
338 &TxBaseImplication(()),
339 External,
340 )
341 .expect("valid");
342
343 assert!(!new_origin.filter_call(&filtered_call));
344 });
345 }
346
347 #[test]
348 fn skipped_authorization_refunds_its_weight() {
349 new_test_ext().execute_with(|| {
350 let ext = frame_system::AuthorizeCall::<Runtime>::new();
351 let call = RuntimeCall::Pallet1(pallet1::Call::call1 { valid: true });
352 let origin: RuntimeOrigin = crate::Origin::<Runtime>::Signed(42).into();
353
354 assert_eq!(ext.weight(&call), pallet1::AUTH_WEIGHT);
356
357 let (_, unspent, _) = ext
358 .validate(
359 origin,
360 &call,
361 &crate::DispatchInfo::default(),
362 Default::default(),
363 (),
364 &TxBaseImplication(()),
365 External,
366 )
367 .expect("valid");
368
369 assert_eq!(unspent, pallet1::AUTH_WEIGHT);
372 });
373 }
374}