1use 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#[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 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
172 (),
173 &info,
174 &post_info,
175 len,
176 &Ok(())
177 ));
178
179 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
215 (),
216 &info,
217 &post_info,
218 len,
219 &Ok(())
220 ));
221
222 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
256 (),
257 &info,
258 &post_info,
259 len,
260 &Ok(())
261 ));
262
263 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
294 (),
295 &info,
296 &post_info,
297 len,
298 &Ok(())
299 ));
300
301 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 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 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 assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
339 (),
340 &info,
341 &post_info,
342 len,
343 &Ok(())
344 ));
345
346 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 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 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 assert_ok!(WeightReclaim::<Test>::bare_post_dispatch(
384 &info,
385 &mut post_info.clone(),
386 len,
387 &Ok(())
388 ));
389
390 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}