frame_system/extensions/
check_nonce.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use 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/// Nonce check and increment to give replay protection for transactions.
38///
39/// # Transaction Validity
40///
41/// This extension affects `requires` and `provides` tags of validity, but DOES NOT
42/// set the `priority` field. Make sure that AT LEAST one of the transaction extension sets
43/// some kind of priority upon validating transactions.
44///
45/// The preparation step assumes that the nonce information has not changed since the validation
46/// step. This means that other extensions ahead of `CheckNonce` in the pipeline must not alter the
47/// nonce during their own preparation step, or else the transaction may be rejected during dispatch
48/// or lead to an inconsistent account state.
49#[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	/// utility constructor. Used only in client/factory code.
55	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/// Operation to perform from `validate` to `prepare` in [`CheckNonce`] transaction extension.
73#[derive(RuntimeDebugNoBound)]
74pub enum Val<T: Config> {
75	/// Account and its nonce to check for.
76	CheckNonce((T::AccountId, T::Nonce)),
77	/// Weight to refund.
78	Refund(Weight),
79}
80
81/// Operation to perform from `prepare` to `post_dispatch_details` in [`CheckNonce`] transaction
82/// extension.
83#[derive(RuntimeDebugNoBound)]
84pub enum Pre {
85	/// The transaction extension weight should not be refunded.
86	NonceChecked,
87	/// The transaction extension weight should be refunded.
88	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			// Nonce storage not paid for
121			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		// `self.0 < nonce` already checked in `validate`.
159		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			// stale
209			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			// correct
224			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			// future
240			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			// Both providers and sufficients zero
283			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			// Non-zero providers
298			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			// Non-zero sufficients
314			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			// run the validation step
361			let (_, val, origin) = CheckNonce::<Test>(1u64.into())
362				.validate(Some(1).into(), CALL, &info, len, (), &TxBaseImplication(CALL), External)
363				.unwrap();
364			// mutate `AccountData` for the caller
365			crate::Account::<Test>::mutate(1, |info| {
366				info.data = 42;
367			});
368			// run the preparation step
369			assert_ok!(CheckNonce::<Test>(1u64.into()).prepare(val, &origin, CALL, &info, len));
370			// only the nonce should be altered by the preparation step
371			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			// Ensure we test the refund.
391			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}