frame_system/extensions/
check_mortality.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::{pallet_prelude::BlockNumberFor, BlockHash, Config, Pallet};
19use codec::{Decode, Encode};
20use scale_info::TypeInfo;
21use sp_runtime::{
22	generic::Era,
23	traits::{DispatchInfoOf, SaturatedConversion, SignedExtension},
24	transaction_validity::{
25		InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
26	},
27};
28
29/// Check for transaction mortality.
30///
31/// The extension adds [`Era`] to every signed extrinsic. It also contributes to the signed data, by
32/// including the hash of the block at [`Era::birth`].
33///
34/// # Transaction Validity
35///
36/// The extension affects `longevity` of the transaction according to the [`Era`] definition.
37#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
38#[scale_info(skip_type_params(T))]
39pub struct CheckMortality<T: Config + Send + Sync>(pub Era, core::marker::PhantomData<T>);
40
41impl<T: Config + Send + Sync> CheckMortality<T> {
42	/// utility constructor. Used only in client/factory code.
43	pub fn from(era: Era) -> Self {
44		Self(era, core::marker::PhantomData)
45	}
46}
47
48impl<T: Config + Send + Sync> core::fmt::Debug for CheckMortality<T> {
49	#[cfg(feature = "std")]
50	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
51		write!(f, "CheckMortality({:?})", self.0)
52	}
53
54	#[cfg(not(feature = "std"))]
55	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
56		Ok(())
57	}
58}
59
60impl<T: Config + Send + Sync> SignedExtension for CheckMortality<T> {
61	type AccountId = T::AccountId;
62	type Call = T::RuntimeCall;
63	type AdditionalSigned = T::Hash;
64	type Pre = ();
65	const IDENTIFIER: &'static str = "CheckMortality";
66
67	fn validate(
68		&self,
69		_who: &Self::AccountId,
70		_call: &Self::Call,
71		_info: &DispatchInfoOf<Self::Call>,
72		_len: usize,
73	) -> TransactionValidity {
74		let current_u64 = <Pallet<T>>::block_number().saturated_into::<u64>();
75		let valid_till = self.0.death(current_u64);
76		Ok(ValidTransaction {
77			longevity: valid_till.saturating_sub(current_u64),
78			..Default::default()
79		})
80	}
81
82	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
83		let current_u64 = <Pallet<T>>::block_number().saturated_into::<u64>();
84		let n = self.0.birth(current_u64).saturated_into::<BlockNumberFor<T>>();
85		if !<BlockHash<T>>::contains_key(n) {
86			Err(InvalidTransaction::AncientBirthBlock.into())
87		} else {
88			Ok(<Pallet<T>>::block_hash(n))
89		}
90	}
91
92	fn pre_dispatch(
93		self,
94		who: &Self::AccountId,
95		call: &Self::Call,
96		info: &DispatchInfoOf<Self::Call>,
97		len: usize,
98	) -> Result<Self::Pre, TransactionValidityError> {
99		self.validate(who, call, info, len).map(|_| ())
100	}
101}
102
103#[cfg(test)]
104mod tests {
105	use super::*;
106	use crate::mock::{new_test_ext, System, Test, CALL};
107	use frame_support::{
108		dispatch::{DispatchClass, DispatchInfo, Pays},
109		weights::Weight,
110	};
111	use sp_core::H256;
112
113	#[test]
114	fn signed_ext_check_era_should_work() {
115		new_test_ext().execute_with(|| {
116			// future
117			assert_eq!(
118				CheckMortality::<Test>::from(Era::mortal(4, 2))
119					.additional_signed()
120					.err()
121					.unwrap(),
122				InvalidTransaction::AncientBirthBlock.into(),
123			);
124
125			// correct
126			System::set_block_number(13);
127			<BlockHash<Test>>::insert(12, H256::repeat_byte(1));
128			assert!(CheckMortality::<Test>::from(Era::mortal(4, 12)).additional_signed().is_ok());
129		})
130	}
131
132	#[test]
133	fn signed_ext_check_era_should_change_longevity() {
134		new_test_ext().execute_with(|| {
135			let normal = DispatchInfo {
136				weight: Weight::from_parts(100, 0),
137				class: DispatchClass::Normal,
138				pays_fee: Pays::Yes,
139			};
140			let len = 0_usize;
141			let ext = (
142				crate::CheckWeight::<Test>::new(),
143				CheckMortality::<Test>::from(Era::mortal(16, 256)),
144			);
145			System::set_block_number(17);
146			<BlockHash<Test>>::insert(16, H256::repeat_byte(1));
147
148			assert_eq!(ext.validate(&1, CALL, &normal, len).unwrap().longevity, 15);
149		})
150	}
151}