pezframe_system/extensions/
authorize_call.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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 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/// 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`](pezframe_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	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}