Skip to main content

topsoil_core/system/extensions/
authorize_call.rs

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