topsoil_core/system/extensions/
weight_reclaim.rs1use 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#[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 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
161 (),
162 &info,
163 &post_info,
164 len,
165 &Ok(())
166 ));
167
168 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
204 (),
205 &info,
206 &post_info,
207 len,
208 &Ok(())
209 ));
210
211 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
245 (),
246 &info,
247 &post_info,
248 len,
249 &Ok(())
250 ));
251
252 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
283 (),
284 &info,
285 &post_info,
286 len,
287 &Ok(())
288 ));
289
290 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
328 (),
329 &info,
330 &post_info,
331 len,
332 &Ok(())
333 ));
334
335 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 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 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 assert_ok!(WeightReclaim::<Test>::bare_post_dispatch(
373 &info,
374 &mut post_info.clone(),
375 len,
376 &Ok(())
377 ));
378
379 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}