referrerpolicy=no-referrer-when-downgrade

frame_system/extensions/
weight_reclaim.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 codec::{Decode, DecodeWithMemTracking, Encode};
20use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
21use scale_info::TypeInfo;
22use sp_runtime::{
23	traits::{
24		DispatchInfoOf, Dispatchable, PostDispatchInfoOf, TransactionExtension, ValidateResult,
25	},
26	transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction},
27	DispatchResult,
28};
29use sp_weights::Weight;
30
31/// Reclaim the unused weight using the post dispatch information
32///
33/// After the dispatch of the extrinsic, calculate the unused weight using the post dispatch
34/// information and update the block consumed weight according to the new calculated extrinsic
35/// weight.
36#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Default, TypeInfo)]
37#[scale_info(skip_type_params(T))]
38pub struct WeightReclaim<T: Config + Send + Sync>(core::marker::PhantomData<T>);
39
40impl<T: Config + Send + Sync> WeightReclaim<T>
41where
42	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
43{
44	/// Creates new `TransactionExtension` to recalculate the extrinsic weight after dispatch.
45	pub fn new() -> Self {
46		Self(Default::default())
47	}
48}
49
50impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for WeightReclaim<T>
51where
52	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
53{
54	const IDENTIFIER: &'static str = "WeightReclaim";
55	type Implicit = ();
56	type Pre = ();
57	type Val = ();
58
59	fn weight(&self, _: &T::RuntimeCall) -> Weight {
60		<T::ExtensionsWeightInfo as super::WeightInfo>::weight_reclaim()
61	}
62
63	fn validate(
64		&self,
65		origin: T::RuntimeOrigin,
66		_call: &T::RuntimeCall,
67		_info: &DispatchInfoOf<T::RuntimeCall>,
68		_len: usize,
69		_self_implicit: Self::Implicit,
70		_inherited_implication: &impl Encode,
71		_source: TransactionSource,
72	) -> ValidateResult<Self::Val, T::RuntimeCall> {
73		Ok((ValidTransaction::default(), (), origin))
74	}
75
76	fn prepare(
77		self,
78		_val: Self::Val,
79		_origin: &T::RuntimeOrigin,
80		_call: &T::RuntimeCall,
81		_info: &DispatchInfoOf<T::RuntimeCall>,
82		_len: usize,
83	) -> Result<Self::Pre, TransactionValidityError> {
84		Ok(())
85	}
86
87	fn post_dispatch_details(
88		_pre: Self::Pre,
89		info: &DispatchInfoOf<T::RuntimeCall>,
90		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
91		_len: usize,
92		_result: &DispatchResult,
93	) -> Result<Weight, TransactionValidityError> {
94		crate::Pallet::<T>::reclaim_weight(info, post_info).map(|()| Weight::zero())
95	}
96
97	fn bare_validate(
98		_call: &T::RuntimeCall,
99		_info: &DispatchInfoOf<T::RuntimeCall>,
100		_len: usize,
101	) -> frame_support::pallet_prelude::TransactionValidity {
102		Ok(ValidTransaction::default())
103	}
104
105	fn bare_validate_and_prepare(
106		_call: &T::RuntimeCall,
107		_info: &DispatchInfoOf<T::RuntimeCall>,
108		_len: usize,
109	) -> Result<(), TransactionValidityError> {
110		Ok(())
111	}
112
113	fn bare_post_dispatch(
114		info: &DispatchInfoOf<T::RuntimeCall>,
115		post_info: &mut PostDispatchInfoOf<T::RuntimeCall>,
116		_len: usize,
117		_result: &DispatchResult,
118	) -> Result<(), TransactionValidityError> {
119		crate::Pallet::<T>::reclaim_weight(info, post_info)
120	}
121}
122
123impl<T: Config + Send + Sync> core::fmt::Debug for WeightReclaim<T>
124where
125	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
126{
127	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
128		write!(f, "{}", Self::IDENTIFIER)
129	}
130}
131
132#[cfg(test)]
133mod tests {
134	use super::*;
135	use crate::{
136		mock::{new_test_ext, Test},
137		BlockWeight, DispatchClass,
138	};
139	use frame_support::{assert_ok, weights::Weight};
140
141	fn block_weights() -> crate::limits::BlockWeights {
142		<Test as crate::Config>::BlockWeights::get()
143	}
144
145	#[test]
146	fn extrinsic_already_refunded_more_precisely() {
147		new_test_ext().execute_with(|| {
148			// This is half of the max block weight
149			let info =
150				DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
151			let post_info = PostDispatchInfo {
152				actual_weight: Some(Weight::from_parts(128, 0)),
153				pays_fee: Default::default(),
154			};
155			let prior_block_weight = Weight::from_parts(64, 0);
156			let accurate_refund = Weight::from_parts(510, 0);
157			let len = 0_usize;
158			let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
159
160			// Set initial info
161			BlockWeight::<Test>::mutate(|current_weight| {
162				current_weight.set(prior_block_weight, DispatchClass::Normal);
163				current_weight.accrue(
164					base_extrinsic + info.total_weight() - accurate_refund,
165					DispatchClass::Normal,
166				);
167			});
168			crate::ExtrinsicWeightReclaimed::<Test>::put(accurate_refund);
169
170			// Do the post dispatch
171			assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
172				(),
173				&info,
174				&post_info,
175				len,
176				&Ok(())
177			));
178
179			// Ensure the accurate refund is used
180			assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), accurate_refund);
181			assert_eq!(
182				*BlockWeight::<Test>::get().get(DispatchClass::Normal),
183				info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic
184			);
185		})
186	}
187
188	#[test]
189	fn extrinsic_already_refunded_less_precisely() {
190		new_test_ext().execute_with(|| {
191			// This is half of the max block weight
192			let info =
193				DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
194			let post_info = PostDispatchInfo {
195				actual_weight: Some(Weight::from_parts(128, 0)),
196				pays_fee: Default::default(),
197			};
198			let prior_block_weight = Weight::from_parts(64, 0);
199			let inaccurate_refund = Weight::from_parts(110, 0);
200			let len = 0_usize;
201			let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
202
203			// Set initial info
204			BlockWeight::<Test>::mutate(|current_weight| {
205				current_weight.set(prior_block_weight, DispatchClass::Normal);
206				current_weight.accrue(
207					base_extrinsic + info.total_weight() - inaccurate_refund,
208					DispatchClass::Normal,
209				);
210			});
211			crate::ExtrinsicWeightReclaimed::<Test>::put(inaccurate_refund);
212
213			// Do the post dispatch
214			assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
215				(),
216				&info,
217				&post_info,
218				len,
219				&Ok(())
220			));
221
222			// Ensure the accurate refund from benchmark is used
223			assert_eq!(
224				crate::ExtrinsicWeightReclaimed::<Test>::get(),
225				post_info.calc_unspent(&info)
226			);
227			assert_eq!(
228				*BlockWeight::<Test>::get().get(DispatchClass::Normal),
229				post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
230			);
231		})
232	}
233
234	#[test]
235	fn extrinsic_not_refunded_before() {
236		new_test_ext().execute_with(|| {
237			// This is half of the max block weight
238			let info =
239				DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
240			let post_info = PostDispatchInfo {
241				actual_weight: Some(Weight::from_parts(128, 0)),
242				pays_fee: Default::default(),
243			};
244			let prior_block_weight = Weight::from_parts(64, 0);
245			let len = 0_usize;
246			let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
247
248			// Set initial info
249			BlockWeight::<Test>::mutate(|current_weight| {
250				current_weight.set(prior_block_weight, DispatchClass::Normal);
251				current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal);
252			});
253
254			// Do the post dispatch
255			assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
256				(),
257				&info,
258				&post_info,
259				len,
260				&Ok(())
261			));
262
263			// Ensure the accurate refund from benchmark is used
264			assert_eq!(
265				crate::ExtrinsicWeightReclaimed::<Test>::get(),
266				post_info.calc_unspent(&info)
267			);
268			assert_eq!(
269				*BlockWeight::<Test>::get().get(DispatchClass::Normal),
270				post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
271			);
272		})
273	}
274
275	#[test]
276	fn no_actual_post_dispatch_weight() {
277		new_test_ext().execute_with(|| {
278			// This is half of the max block weight
279			let info =
280				DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
281			let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() };
282			let prior_block_weight = Weight::from_parts(64, 0);
283			let len = 0_usize;
284			let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
285
286			// Set initial info
287			BlockWeight::<Test>::mutate(|current_weight| {
288				current_weight.set(prior_block_weight, DispatchClass::Normal);
289				current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal);
290			});
291
292			// Do the post dispatch
293			assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
294				(),
295				&info,
296				&post_info,
297				len,
298				&Ok(())
299			));
300
301			// Ensure the accurate refund from benchmark is used
302			assert_eq!(
303				crate::ExtrinsicWeightReclaimed::<Test>::get(),
304				post_info.calc_unspent(&info)
305			);
306			assert_eq!(
307				*BlockWeight::<Test>::get().get(DispatchClass::Normal),
308				info.total_weight() + prior_block_weight + base_extrinsic
309			);
310		})
311	}
312
313	#[test]
314	fn different_dispatch_class() {
315		new_test_ext().execute_with(|| {
316			// This is half of the max block weight
317			let info = DispatchInfo {
318				call_weight: Weight::from_parts(512, 0),
319				class: DispatchClass::Operational,
320				..Default::default()
321			};
322			let post_info = PostDispatchInfo {
323				actual_weight: Some(Weight::from_parts(128, 0)),
324				pays_fee: Default::default(),
325			};
326			let prior_block_weight = Weight::from_parts(64, 0);
327			let len = 0_usize;
328			let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic;
329
330			// Set initial info
331			BlockWeight::<Test>::mutate(|current_weight| {
332				current_weight.set(prior_block_weight, DispatchClass::Operational);
333				current_weight
334					.accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational);
335			});
336
337			// Do the post dispatch
338			assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
339				(),
340				&info,
341				&post_info,
342				len,
343				&Ok(())
344			));
345
346			// Ensure the accurate refund from benchmark is used
347			assert_eq!(
348				crate::ExtrinsicWeightReclaimed::<Test>::get(),
349				post_info.calc_unspent(&info)
350			);
351			assert_eq!(
352				*BlockWeight::<Test>::get().get(DispatchClass::Operational),
353				post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
354			);
355		})
356	}
357
358	#[test]
359	fn bare_also_works() {
360		new_test_ext().execute_with(|| {
361			// This is half of the max block weight
362			let info = DispatchInfo {
363				call_weight: Weight::from_parts(512, 0),
364				class: DispatchClass::Operational,
365				..Default::default()
366			};
367			let post_info = PostDispatchInfo {
368				actual_weight: Some(Weight::from_parts(128, 0)),
369				pays_fee: Default::default(),
370			};
371			let prior_block_weight = Weight::from_parts(64, 0);
372			let len = 0_usize;
373			let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic;
374
375			// Set initial info
376			BlockWeight::<Test>::mutate(|current_weight| {
377				current_weight.set(prior_block_weight, DispatchClass::Operational);
378				current_weight
379					.accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational);
380			});
381
382			// Do the bare post dispatch
383			assert_ok!(WeightReclaim::<Test>::bare_post_dispatch(
384				&info,
385				&mut post_info.clone(),
386				len,
387				&Ok(())
388			));
389
390			// Ensure the accurate refund from benchmark is used
391			assert_eq!(
392				crate::ExtrinsicWeightReclaimed::<Test>::get(),
393				post_info.calc_unspent(&info)
394			);
395			assert_eq!(
396				*BlockWeight::<Test>::get().get(DispatchClass::Operational),
397				post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
398			);
399		})
400	}
401}