topsoil_core/system/extensions/
authorize_call.rs1use 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#[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}