pallet_evm_tracker/
check_nonce.rs

1//! EVM nonce checker extension for the EVM domain runtime.
2
3use crate::Config;
4use core::cmp::max;
5use core::fmt;
6use core::result::Result;
7use domain_runtime_primitives::ERR_EVM_NONCE_OVERFLOW;
8use frame_support::RuntimeDebugNoBound;
9use frame_support::dispatch::DispatchInfo;
10use frame_support::pallet_prelude::{
11    CheckedAdd, InvalidTransaction, TransactionLongevity, TransactionValidityError, TypeInfo,
12    ValidTransaction, Weight,
13};
14use frame_support::sp_runtime::traits::{DispatchInfoOf, One, TransactionExtension};
15use parity_scale_codec::{Decode, Encode};
16use scale_info::prelude::vec;
17use sp_runtime::traits::{
18    AsSystemOriginSigner, Dispatchable, PostDispatchInfoOf, ValidateResult, Zero,
19};
20use sp_runtime::transaction_validity::TransactionSource;
21use sp_runtime::{DispatchResult, Saturating};
22
23#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
24#[scale_info(skip_type_params(T))]
25pub struct CheckNonce<T: Config>(#[codec(compact)] pub T::Nonce);
26
27impl<T: Config> CheckNonce<T> {
28    /// utility constructor. Used only in client/factory code.
29    pub fn from(nonce: T::Nonce) -> Self {
30        Self(nonce)
31    }
32}
33
34impl<T: Config> fmt::Debug for CheckNonce<T> {
35    #[cfg(feature = "std")]
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "CheckNonce({})", self.0)
38    }
39
40    #[cfg(not(feature = "std"))]
41    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
42        Ok(())
43    }
44}
45
46/// Operation to perform from `validate` to `prepare` in [`frame_system::CheckNonce`] transaction extension.
47#[derive(RuntimeDebugNoBound)]
48pub enum Val<T: frame_system::Config> {
49    /// Account and its nonce to check for.
50    CheckNonce((T::AccountId, T::Nonce)),
51    /// Weight to refund.
52    Refund(Weight),
53}
54
55/// Operation to perform from `prepare` to `post_dispatch_details` in [`frame_system::CheckNonce`] transaction
56/// extension.
57#[derive(RuntimeDebugNoBound)]
58pub enum Pre {
59    /// The transaction extension weight should not be refunded.
60    NonceChecked,
61    /// The transaction extension weight should be refunded.
62    Refund(Weight),
63}
64
65impl<T: Config> TransactionExtension<T::RuntimeCall> for CheckNonce<T>
66where
67    T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
68    <T::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
69{
70    const IDENTIFIER: &'static str = "CheckNonce";
71    type Implicit = ();
72    type Val = Val<T>;
73    type Pre = Pre;
74
75    fn weight(&self, _: &T::RuntimeCall) -> sp_weights::Weight {
76        // Currently we use the benchmarked weight from `frame_system::CheckNonce`, which does
77        // slightly less work. This extension does one more `max()` and two extra comparisons.
78        // TODO: benchmark evm_tracker::check_nonce() directly and use that weight here.
79        <T::ExtensionsWeightInfo as frame_system::ExtensionsWeightInfo>::check_nonce()
80    }
81
82    fn validate(
83        &self,
84        origin: <T as frame_system::Config>::RuntimeOrigin,
85        call: &T::RuntimeCall,
86        _info: &DispatchInfoOf<T::RuntimeCall>,
87        _len: usize,
88        _self_implicit: Self::Implicit,
89        _inherited_implication: &impl Encode,
90        _source: TransactionSource,
91    ) -> ValidateResult<Self::Val, T::RuntimeCall> {
92        let Some(who) = origin.as_system_origin_signer() else {
93            return Ok((Default::default(), Val::Refund(self.weight(call)), origin));
94        };
95
96        let account = frame_system::Account::<T>::get(who);
97        if account.providers.is_zero() && account.sufficients.is_zero() {
98            // Nonce storage not paid for
99            return Err(InvalidTransaction::Payment.into());
100        }
101        if self.0 < account.nonce {
102            return Err(InvalidTransaction::Stale.into());
103        }
104
105        let provides = vec![Encode::encode(&(who, self.0))];
106        let requires = if account.nonce < self.0 {
107            vec![Encode::encode(&(who, self.0.saturating_sub(One::one())))]
108        } else {
109            vec![]
110        };
111
112        let validity = ValidTransaction {
113            priority: 0,
114            requires,
115            provides,
116            longevity: TransactionLongevity::MAX,
117            propagate: true,
118        };
119
120        Ok((
121            validity,
122            Val::CheckNonce((who.clone(), account.nonce)),
123            origin,
124        ))
125    }
126
127    fn prepare(
128        self,
129        val: Self::Val,
130        _origin: &T::RuntimeOrigin,
131        _call: &T::RuntimeCall,
132        _info: &DispatchInfoOf<T::RuntimeCall>,
133        _len: usize,
134    ) -> Result<Self::Pre, TransactionValidityError> {
135        let (who, mut nonce) = match val {
136            Val::CheckNonce((who, nonce)) => (who, nonce),
137            Val::Refund(weight) => return Ok(Pre::Refund(weight)),
138        };
139
140        // if a sender sends an evm transaction first and substrate transaction
141        // after with same nonce, then reject the second transaction
142        // if sender reverse the transaction types, substrate first and evm second,
143        // evm transaction will be rejected, since substrate updates nonce in pre_dispatch.
144        let account_nonce = if let Some(tracked_nonce) = crate::AccountNonce::<T>::get(who.clone())
145        {
146            max(tracked_nonce.as_u32().into(), nonce)
147        } else {
148            nonce
149        };
150
151        if self.0 != account_nonce {
152            return Err(if self.0 < nonce {
153                InvalidTransaction::Stale
154            } else {
155                InvalidTransaction::Future
156            }
157            .into());
158        }
159        nonce = nonce
160            .checked_add(&T::Nonce::one())
161            .ok_or(InvalidTransaction::Custom(ERR_EVM_NONCE_OVERFLOW))?;
162        frame_system::Account::<T>::mutate(who, |account| account.nonce = nonce);
163        Ok(Pre::NonceChecked)
164    }
165
166    fn post_dispatch_details(
167        pre: Self::Pre,
168        _info: &DispatchInfo,
169        _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
170        _len: usize,
171        _result: &DispatchResult,
172    ) -> Result<Weight, TransactionValidityError> {
173        match pre {
174            Pre::NonceChecked => Ok(Weight::zero()),
175            Pre::Refund(weight) => Ok(weight),
176        }
177    }
178}