common_runtime/extensions/
check_nonce.rs

1// This file overrides the default Substrate CheckNonce for Frequency.
2// It only creates the token account for paid extrinsics.
3
4// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
6
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// 	http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18
19use frame_system::Config;
20use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
21
22use frame_support::{
23	dispatch::{DispatchInfo, Pays},
24	sp_runtime, RuntimeDebugNoBound,
25};
26use scale_info::TypeInfo;
27use sp_runtime::{
28	traits::{
29		AsSystemOriginSigner, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf,
30		TransactionExtension, ValidateResult,
31	},
32	transaction_validity::{
33		InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidityError,
34		ValidTransaction,
35	},
36	DispatchResult, Weight,
37};
38extern crate alloc;
39use alloc::vec;
40
41/// Nonce check and increment to give replay protection for transactions.
42///
43/// # Transaction Validity
44///
45/// This extension affects `requires` and `provides` tags of validity, but DOES NOT
46/// set the `priority` field. Make sure that AT LEAST one of the signed extension sets
47/// some kind of priority upon validating transactions.
48#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
49#[scale_info(skip_type_params(T))]
50pub struct CheckNonce<T: Config>(#[codec(compact)] pub T::Nonce);
51
52impl<T: Config> CheckNonce<T> {
53	/// utility constructor. Used only in client/factory code.
54	pub fn from(nonce: T::Nonce) -> Self {
55		Self(nonce)
56	}
57}
58
59impl<T: Config> core::fmt::Debug for CheckNonce<T> {
60	#[cfg(feature = "std")]
61	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
62		write!(f, "CheckNonce({})", self.0)
63	}
64
65	#[cfg(not(feature = "std"))]
66	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
67		Ok(())
68	}
69}
70
71/// Transaction operations from `validate` to `post_dispatch` for the `CheckNonce` extension.
72/// This is used to determine whether the transaction extension weight should be refunded or not.
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/// Transaction operations from `prepare` to `post_dispatch` for the `CheckNonce` extension.
82/// This is used to determine whether the transaction extension weight should be refunded or not.
83#[derive(RuntimeDebugNoBound)]
84pub enum Pre {
85	/// No nonce check was performed
86	NonceChecked,
87	/// Weight to refund.
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, _call: &T::RuntimeCall) -> Weight {
102		<T::ExtensionsWeightInfo as frame_system::ExtensionsWeightInfo>::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		// Only check for signed origin
116		let Some(who) = origin.as_system_origin_signer() else {
117			return Ok((ValidTransaction::default(), Val::Refund(self.weight(call)), origin));
118		};
119
120		let (valid_transaction, account_nonce) = validate_nonce::<T>(who, self.0)?;
121
122		Ok((valid_transaction, Val::CheckNonce((who.clone(), account_nonce)), origin))
123	}
124
125	fn prepare(
126		self,
127		val: Self::Val,
128		_origin: &<T::RuntimeCall as Dispatchable>::RuntimeOrigin,
129		_call: &T::RuntimeCall,
130		info: &DispatchInfoOf<T::RuntimeCall>,
131		_len: usize,
132	) -> Result<Self::Pre, TransactionValidityError> {
133		let (who, nonce) = match val {
134			Val::CheckNonce((who, nonce)) => (who, nonce),
135			Val::Refund(weight) => return Ok(Pre::Refund(weight)),
136		};
137
138		// Prepare the nonce for the account.
139		prepare_nonce::<T>(&who, nonce, info.pays_fee)?;
140
141		Ok(Pre::NonceChecked)
142	}
143
144	fn post_dispatch_details(
145		pre: Self::Pre,
146		_info: &DispatchInfo,
147		_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
148		_len: usize,
149		_result: &DispatchResult,
150	) -> Result<Weight, TransactionValidityError> {
151		match pre {
152			Pre::NonceChecked => Ok(Weight::zero()),
153			Pre::Refund(weight) => Ok(weight),
154		}
155	}
156}
157
158/// Helper function to prepare a nonce for a given account.
159pub fn prepare_nonce<T: Config>(
160	who: &T::AccountId,
161	nonce: T::Nonce,
162	pays_fee: Pays,
163) -> Result<(), TransactionValidityError> {
164	let mut account: frame_system::AccountInfo<<T as Config>::Nonce, <T as Config>::AccountData> =
165		frame_system::Account::<T>::get(who);
166	// The default account (no account) has a nonce of 0.
167	// If account nonce is not equal to the tx nonce (self.0), the tx is invalid.  Therefore, check if it is a stale or future tx.
168	if nonce != account.nonce {
169		return Err(if nonce < account.nonce {
170			InvalidTransaction::Stale
171		} else {
172			InvalidTransaction::Future
173		}
174		.into());
175	}
176
177	// Is this an existing account?
178	// extracted from the conditions in which an account gets reaped
179	// https://github.com/paritytech/polkadot-sdk/commit/e993f884fc00f359dd8bf9c81422c5161f3447b5#diff-dff2afa7433478e36eb66a9fe319efe28cfbdf95104b30b03afa0a1c4e3239f3R1082
180	// Relevant lines: https://github.com/paritytech/polkadot-sdk/blob/495d5a24c8078a0da1eb5e0fe8742a09f1f1bd5c/substrate/frame/system/src/lib.rs#L1642
181	let existing_account =
182		account.providers > 0 || account.consumers > 0 || account.sufficients > 0;
183
184	// Increment account nonce by 1
185	account.nonce += T::Nonce::one();
186
187	// Only create or update the token account if the caller is paying or
188	// account already exists
189	if pays_fee == Pays::Yes || existing_account {
190		frame_system::Account::<T>::insert(who, account);
191	}
192	Ok(())
193}
194
195/// Helper function to validate a nonce for a given account.
196pub fn validate_nonce<T: Config>(
197	who: &T::AccountId,
198	nonce: T::Nonce,
199) -> Result<(ValidTransaction, T::Nonce), TransactionValidityError> {
200	let account = frame_system::Account::<T>::get(who);
201
202	if nonce < account.nonce {
203		return Err(InvalidTransaction::Stale.into());
204	}
205	let provides = vec![Encode::encode(&(who.clone(), nonce))];
206	let requires = if account.nonce < nonce {
207		vec![Encode::encode(&(who.clone(), nonce - One::one()))]
208	} else {
209		vec![]
210	};
211	Ok((
212		ValidTransaction {
213			priority: 0,
214			requires,
215			provides,
216			longevity: TransactionLongevity::MAX,
217			propagate: true,
218		},
219		account.nonce,
220	))
221}