Skip to main content

topsoil_core/system/extensions/
weight_reclaim.rs

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