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