1extern crate alloc;
8
9use alloc::{vec, vec::Vec};
10
11use crate::system::Config;
12use codec::{Decode, DecodeWithMemTracking, Encode};
13use scale_info::TypeInfo;
14use subsoil::runtime::{
15 traits::{
16 AsSystemOriginSigner, CheckedAdd, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf,
17 TransactionExtension, ValidateResult, Zero,
18 },
19 transaction_validity::{
20 InvalidTransaction, TransactionLongevity, TransactionValidityError, ValidTransaction,
21 },
22 DispatchResult, Saturating,
23};
24use subsoil::weights::Weight;
25use topsoil_core::{dispatch::DispatchInfo, pallet_prelude::TransactionSource, DebugNoBound};
26
27#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
40#[scale_info(skip_type_params(T))]
41pub struct CheckNonce<T: Config>(#[codec(compact)] pub T::Nonce);
42
43pub struct ValidNonceInfo {
45 pub provides: Vec<Vec<u8>>,
47 pub requires: Vec<Vec<u8>>,
49}
50
51impl<T: Config> CheckNonce<T> {
52 pub fn from(nonce: T::Nonce) -> Self {
54 Self(nonce)
55 }
56
57 pub fn validate_nonce_for_account(
60 who: &T::AccountId,
61 nonce: T::Nonce,
62 ) -> Result<ValidNonceInfo, TransactionValidityError> {
63 let account = crate::system::Account::<T>::get(who);
64 if account.providers.is_zero() && account.sufficients.is_zero() {
65 return Err(InvalidTransaction::Payment.into());
67 }
68 if nonce < account.nonce {
69 return Err(InvalidTransaction::Stale.into());
70 }
71
72 let provides = vec![Encode::encode(&(who.clone(), nonce))];
73 let requires = if account.nonce < nonce {
74 vec![Encode::encode(&(who.clone(), nonce.saturating_sub(One::one())))]
75 } else {
76 vec![]
77 };
78
79 Ok(ValidNonceInfo { provides, requires })
80 }
81
82 pub fn prepare_nonce_for_account(
84 who: &T::AccountId,
85 mut nonce: T::Nonce,
86 ) -> Result<(), TransactionValidityError> {
87 let account = crate::system::Account::<T>::get(who);
88 if nonce > account.nonce {
89 return Err(InvalidTransaction::Future.into());
90 }
91 nonce = nonce.checked_add(&T::Nonce::one()).unwrap_or(T::Nonce::zero());
92 crate::system::Account::<T>::mutate(who, |account| account.nonce = nonce);
93 Ok(())
94 }
95}
96
97impl<T: Config> core::fmt::Debug for CheckNonce<T> {
98 #[cfg(feature = "std")]
99 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
100 write!(f, "CheckNonce({})", self.0)
101 }
102
103 #[cfg(not(feature = "std"))]
104 fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
105 Ok(())
106 }
107}
108
109#[derive(DebugNoBound)]
111pub enum Val<T: Config> {
112 CheckNonce(T::AccountId),
114 Refund(Weight),
116}
117
118#[derive(DebugNoBound)]
121pub enum Pre {
122 NonceChecked,
124 Refund(Weight),
126}
127
128impl<T: Config> TransactionExtension<T::RuntimeCall> for CheckNonce<T>
129where
130 T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
131 <T::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
132{
133 const IDENTIFIER: &'static str = "CheckNonce";
134 type Implicit = ();
135 type Val = Val<T>;
136 type Pre = Pre;
137
138 fn weight(&self, _: &T::RuntimeCall) -> subsoil::weights::Weight {
139 <T::ExtensionsWeightInfo as super::WeightInfo>::check_nonce()
140 }
141
142 fn validate(
143 &self,
144 origin: <T as Config>::RuntimeOrigin,
145 call: &T::RuntimeCall,
146 _info: &DispatchInfoOf<T::RuntimeCall>,
147 _len: usize,
148 _self_implicit: Self::Implicit,
149 _inherited_implication: &impl Encode,
150 _source: TransactionSource,
151 ) -> ValidateResult<Self::Val, T::RuntimeCall> {
152 let Some(who) = origin.as_system_origin_signer() else {
153 return Ok((Default::default(), Val::Refund(self.weight(call)), origin));
154 };
155 let ValidNonceInfo { provides, requires } = Self::validate_nonce_for_account(who, self.0)?;
156
157 let validity = ValidTransaction {
158 priority: 0,
159 requires,
160 provides,
161 longevity: TransactionLongevity::max_value(),
162 propagate: true,
163 };
164
165 Ok((validity, Val::CheckNonce(who.clone()), origin))
166 }
167
168 fn prepare(
169 self,
170 val: Self::Val,
171 _origin: &T::RuntimeOrigin,
172 _call: &T::RuntimeCall,
173 _info: &DispatchInfoOf<T::RuntimeCall>,
174 _len: usize,
175 ) -> Result<Self::Pre, TransactionValidityError> {
176 let (who, nonce) = match val {
177 Val::CheckNonce(who) => (who, self.0),
178 Val::Refund(weight) => return Ok(Pre::Refund(weight)),
179 };
180 Self::prepare_nonce_for_account(&who, nonce).map(|_| Pre::NonceChecked)
181 }
182
183 fn post_dispatch_details(
184 pre: Self::Pre,
185 _info: &DispatchInfo,
186 _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
187 _len: usize,
188 _result: &DispatchResult,
189 ) -> Result<Weight, TransactionValidityError> {
190 match pre {
191 Pre::NonceChecked => Ok(Weight::zero()),
192 Pre::Refund(weight) => Ok(weight),
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::system::mock::{new_test_ext, RuntimeCall, Test, CALL};
201 use subsoil::runtime::{
202 traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, TxBaseImplication},
203 transaction_validity::TransactionSource::External,
204 };
205 use topsoil_core::{
206 assert_ok, assert_storage_noop, dispatch::GetDispatchInfo, traits::OriginTrait,
207 };
208
209 #[test]
210 fn signed_ext_check_nonce_works() {
211 new_test_ext().execute_with(|| {
212 crate::system::Account::<Test>::insert(
213 1,
214 crate::system::AccountInfo {
215 nonce: 1u64.into(),
216 consumers: 0,
217 providers: 1,
218 sufficients: 0,
219 data: 0,
220 },
221 );
222 let info = DispatchInfo::default();
223 let len = 0_usize;
224 assert_storage_noop!({
226 assert_eq!(
227 CheckNonce::<Test>(0u64.into())
228 .validate_only(Some(1).into(), CALL, &info, len, External, 0)
229 .unwrap_err(),
230 TransactionValidityError::Invalid(InvalidTransaction::Stale)
231 );
232 assert_eq!(
233 CheckNonce::<Test>(0u64.into())
234 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
235 .unwrap_err(),
236 TransactionValidityError::Invalid(InvalidTransaction::Stale)
237 );
238 });
239 assert_ok!(CheckNonce::<Test>(1u64.into()).validate_only(
241 Some(1).into(),
242 CALL,
243 &info,
244 len,
245 External,
246 0,
247 ));
248 assert_ok!(CheckNonce::<Test>(1u64.into()).validate_and_prepare(
249 Some(1).into(),
250 CALL,
251 &info,
252 len,
253 0,
254 ));
255 assert_ok!(CheckNonce::<Test>(5u64.into()).validate_only(
257 Some(1).into(),
258 CALL,
259 &info,
260 len,
261 External,
262 0,
263 ));
264 assert_eq!(
265 CheckNonce::<Test>(5u64.into())
266 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
267 .unwrap_err(),
268 TransactionValidityError::Invalid(InvalidTransaction::Future)
269 );
270 })
271 }
272
273 #[test]
274 fn signed_ext_check_nonce_requires_provider() {
275 new_test_ext().execute_with(|| {
276 crate::system::Account::<Test>::insert(
277 2,
278 crate::system::AccountInfo {
279 nonce: 1u64.into(),
280 consumers: 0,
281 providers: 1,
282 sufficients: 0,
283 data: 0,
284 },
285 );
286 crate::system::Account::<Test>::insert(
287 3,
288 crate::system::AccountInfo {
289 nonce: 1u64.into(),
290 consumers: 0,
291 providers: 0,
292 sufficients: 1,
293 data: 0,
294 },
295 );
296 let info = DispatchInfo::default();
297 let len = 0_usize;
298 assert_storage_noop!({
300 assert_eq!(
301 CheckNonce::<Test>(1u64.into())
302 .validate_only(Some(1).into(), CALL, &info, len, External, 0)
303 .unwrap_err(),
304 TransactionValidityError::Invalid(InvalidTransaction::Payment)
305 );
306 assert_eq!(
307 CheckNonce::<Test>(1u64.into())
308 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
309 .unwrap_err(),
310 TransactionValidityError::Invalid(InvalidTransaction::Payment)
311 );
312 });
313 assert_ok!(CheckNonce::<Test>(1u64.into()).validate_only(
315 Some(2).into(),
316 CALL,
317 &info,
318 len,
319 External,
320 0,
321 ));
322 assert_ok!(CheckNonce::<Test>(1u64.into()).validate_and_prepare(
323 Some(2).into(),
324 CALL,
325 &info,
326 len,
327 0,
328 ));
329 assert_ok!(CheckNonce::<Test>(1u64.into()).validate_only(
331 Some(3).into(),
332 CALL,
333 &info,
334 len,
335 External,
336 0,
337 ));
338 assert_ok!(CheckNonce::<Test>(1u64.into()).validate_and_prepare(
339 Some(3).into(),
340 CALL,
341 &info,
342 len,
343 0,
344 ));
345 })
346 }
347
348 #[test]
349 fn unsigned_check_nonce_works() {
350 new_test_ext().execute_with(|| {
351 let info = DispatchInfo::default();
352 let len = 0_usize;
353 let (_, val, origin) = CheckNonce::<Test>(1u64.into())
354 .validate(None.into(), CALL, &info, len, (), &TxBaseImplication(CALL), External)
355 .unwrap();
356 assert!(!origin.is_transaction_authorized());
357 assert_ok!(CheckNonce::<Test>(1u64.into()).prepare(val, &origin, CALL, &info, len));
358 })
359 }
360
361 #[test]
362 fn check_nonce_preserves_account_data() {
363 new_test_ext().execute_with(|| {
364 crate::system::Account::<Test>::insert(
365 1,
366 crate::system::AccountInfo {
367 nonce: 1u64.into(),
368 consumers: 0,
369 providers: 1,
370 sufficients: 0,
371 data: 0,
372 },
373 );
374 let info = DispatchInfo::default();
375 let len = 0_usize;
376 let (_, val, origin) = CheckNonce::<Test>(1u64.into())
378 .validate(Some(1).into(), CALL, &info, len, (), &TxBaseImplication(CALL), External)
379 .unwrap();
380 crate::system::Account::<Test>::mutate(1, |info| {
382 info.data = 42;
383 });
384 assert_ok!(CheckNonce::<Test>(1u64.into()).prepare(val, &origin, CALL, &info, len));
386 let expected_info = crate::system::AccountInfo {
388 nonce: 2u64.into(),
389 consumers: 0,
390 providers: 1,
391 sufficients: 0,
392 data: 42,
393 };
394 assert_eq!(crate::system::Account::<Test>::get(1), expected_info);
395 })
396 }
397
398 #[test]
399 fn check_nonce_skipped_and_refund_for_other_origins() {
400 new_test_ext().execute_with(|| {
401 let ext = CheckNonce::<Test>(1u64.into());
402
403 let mut info = CALL.get_dispatch_info();
404 info.extension_weight = ext.weight(CALL);
405
406 assert!(info.extension_weight != Weight::zero());
408
409 let len = CALL.encoded_size();
410
411 let origin = crate::system::RawOrigin::Root.into();
412 let (pre, origin) = ext.validate_and_prepare(origin, CALL, &info, len, 0).unwrap();
413
414 assert!(origin.as_system_ref().unwrap().is_root());
415
416 let pd_res = Ok(());
417 let mut post_info = topsoil_core::dispatch::PostDispatchInfo {
418 actual_weight: Some(info.total_weight()),
419 pays_fee: Default::default(),
420 };
421
422 <CheckNonce<Test> as TransactionExtension<RuntimeCall>>::post_dispatch(
423 pre,
424 &info,
425 &mut post_info,
426 len,
427 &pd_res,
428 )
429 .unwrap();
430
431 assert_eq!(post_info.actual_weight, Some(info.call_weight));
432 })
433 }
434}