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