referrerpolicy=no-referrer-when-downgrade

frame_system/extensions/
authorize_call.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use 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/// A transaction extension that authorizes some calls (i.e. dispatchable functions) to be
36/// included in the block.
37///
38/// This transaction extension use the runtime implementation of the trait
39/// [`Authorize`](frame_support::traits::Authorize) to set the validity of the transaction.
40#[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		// The authorize callback did not run, so its benchmarked weight is unspent.
87		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			// The origin was already authorized, so the authorize weight is refunded.
310			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			// `weight()` books the authorize weight whatever the origin is.
355			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			// The origin is already authorized, so the callback is skipped and none of the
370			// weight booked for it is spent.
371			assert_eq!(unspent, pallet1::AUTH_WEIGHT);
372		});
373	}
374}