use crate::Config;
use alloc::vec;
use codec::{Decode, Encode};
use frame_support::dispatch::DispatchInfo;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{DispatchInfoOf, Dispatchable, One, SignedExtension, Zero},
transaction_validity::{
InvalidTransaction, TransactionLongevity, TransactionValidity, TransactionValidityError,
ValidTransaction,
},
};
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct CheckNonce<T: Config>(#[codec(compact)] pub T::Nonce);
impl<T: Config> CheckNonce<T> {
pub fn from(nonce: T::Nonce) -> Self {
Self(nonce)
}
}
impl<T: Config> core::fmt::Debug for CheckNonce<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "CheckNonce({})", self.0)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
Ok(())
}
}
impl<T: Config> SignedExtension for CheckNonce<T>
where
T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
{
type AccountId = T::AccountId;
type Call = T::RuntimeCall;
type AdditionalSigned = ();
type Pre = ();
const IDENTIFIER: &'static str = "CheckNonce";
fn additional_signed(&self) -> core::result::Result<(), TransactionValidityError> {
Ok(())
}
fn pre_dispatch(
self,
who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<(), TransactionValidityError> {
let mut account = crate::Account::<T>::get(who);
if account.providers.is_zero() && account.sufficients.is_zero() {
return Err(InvalidTransaction::Payment.into())
}
if self.0 != account.nonce {
return Err(if self.0 < account.nonce {
InvalidTransaction::Stale
} else {
InvalidTransaction::Future
}
.into())
}
account.nonce += T::Nonce::one();
crate::Account::<T>::insert(who, account);
Ok(())
}
fn validate(
&self,
who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
let account = crate::Account::<T>::get(who);
if account.providers.is_zero() && account.sufficients.is_zero() {
return InvalidTransaction::Payment.into()
}
if self.0 < account.nonce {
return InvalidTransaction::Stale.into()
}
let provides = vec![Encode::encode(&(who, self.0))];
let requires = if account.nonce < self.0 {
vec![Encode::encode(&(who, self.0 - One::one()))]
} else {
vec![]
};
Ok(ValidTransaction {
priority: 0,
requires,
provides,
longevity: TransactionLongevity::max_value(),
propagate: true,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{new_test_ext, Test, CALL};
use frame_support::{assert_noop, assert_ok};
#[test]
fn signed_ext_check_nonce_works() {
new_test_ext().execute_with(|| {
crate::Account::<Test>::insert(
1,
crate::AccountInfo {
nonce: 1u64.into(),
consumers: 0,
providers: 1,
sufficients: 0,
data: 0,
},
);
let info = DispatchInfo::default();
let len = 0_usize;
assert_noop!(
CheckNonce::<Test>(0u64.into()).validate(&1, CALL, &info, len),
InvalidTransaction::Stale
);
assert_noop!(
CheckNonce::<Test>(0u64.into()).pre_dispatch(&1, CALL, &info, len),
InvalidTransaction::Stale
);
assert_ok!(CheckNonce::<Test>(1u64.into()).validate(&1, CALL, &info, len));
assert_ok!(CheckNonce::<Test>(1u64.into()).pre_dispatch(&1, CALL, &info, len));
assert_ok!(CheckNonce::<Test>(5u64.into()).validate(&1, CALL, &info, len));
assert_noop!(
CheckNonce::<Test>(5u64.into()).pre_dispatch(&1, CALL, &info, len),
InvalidTransaction::Future
);
})
}
#[test]
fn signed_ext_check_nonce_requires_provider() {
new_test_ext().execute_with(|| {
crate::Account::<Test>::insert(
2,
crate::AccountInfo {
nonce: 1u64.into(),
consumers: 0,
providers: 1,
sufficients: 0,
data: 0,
},
);
crate::Account::<Test>::insert(
3,
crate::AccountInfo {
nonce: 1u64.into(),
consumers: 0,
providers: 0,
sufficients: 1,
data: 0,
},
);
let info = DispatchInfo::default();
let len = 0_usize;
assert_noop!(
CheckNonce::<Test>(1u64.into()).validate(&1, CALL, &info, len),
InvalidTransaction::Payment
);
assert_noop!(
CheckNonce::<Test>(1u64.into()).pre_dispatch(&1, CALL, &info, len),
InvalidTransaction::Payment
);
assert_ok!(CheckNonce::<Test>(1u64.into()).validate(&2, CALL, &info, len));
assert_ok!(CheckNonce::<Test>(1u64.into()).pre_dispatch(&2, CALL, &info, len));
assert_ok!(CheckNonce::<Test>(1u64.into()).validate(&3, CALL, &info, len));
assert_ok!(CheckNonce::<Test>(1u64.into()).pre_dispatch(&3, CALL, &info, len));
})
}
}