1use crate::system::{limits::BlockWeights, Config, Pallet, LOG_TARGET};
8use codec::{Decode, DecodeWithMemTracking, Encode};
9use scale_info::TypeInfo;
10use subsoil::runtime::{
11 traits::{
12 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, TransactionExtension, ValidateResult,
13 },
14 transaction_validity::{InvalidTransaction, TransactionValidityError, ValidTransaction},
15 DispatchResult,
16};
17use subsoil::weights::Weight;
18use topsoil_core::{
19 dispatch::{DispatchInfo, PostDispatchInfo},
20 pallet_prelude::TransactionSource,
21 traits::Get,
22};
23
24#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Default, TypeInfo)]
31#[scale_info(skip_type_params(T))]
32pub struct CheckWeight<T: Config + Send + Sync>(core::marker::PhantomData<T>);
33
34impl<T: Config + Send + Sync> CheckWeight<T>
35where
36 T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
37{
38 fn check_extrinsic_weight(
41 info: &DispatchInfoOf<T::RuntimeCall>,
42 len: usize,
43 ) -> Result<(), TransactionValidityError> {
44 let max = T::BlockWeights::get().get(info.class).max_extrinsic;
45 let total_weight_including_length =
46 info.total_weight().saturating_add_proof_size(len as u64);
47 match max {
48 Some(max) if total_weight_including_length.any_gt(max) => {
49 log::debug!(
50 target: LOG_TARGET,
51 "Extrinsic with length included {} is greater than the max extrinsic {}",
52 total_weight_including_length,
53 max,
54 );
55
56 Err(InvalidTransaction::ExhaustsResources.into())
57 },
58 _ => Ok(()),
59 }
60 }
61
62 fn check_block_length(
66 info: &DispatchInfoOf<T::RuntimeCall>,
67 len: usize,
68 ) -> Result<u32, TransactionValidityError> {
69 let length_limit = T::BlockLength::get();
70 let current_len = Pallet::<T>::block_size();
71 let added_len = len as u32;
72 let next_len = current_len.saturating_add(added_len);
73 if next_len > *length_limit.max.get(info.class) {
74 log::debug!(
75 target: LOG_TARGET,
76 "Exceeded block length limit: {} > {}",
77 next_len,
78 length_limit.max.get(info.class),
79 );
80
81 Err(InvalidTransaction::ExhaustsResources.into())
82 } else {
83 Ok(next_len)
84 }
85 }
86
87 pub fn new() -> Self {
89 Self(Default::default())
90 }
91
92 pub fn do_validate(
98 info: &DispatchInfoOf<T::RuntimeCall>,
99 len: usize,
100 ) -> Result<(ValidTransaction, u32), TransactionValidityError> {
101 let next_len = Self::check_block_length(info, len)?;
103 Self::check_extrinsic_weight(info, len)?;
107
108 Ok((Default::default(), next_len))
109 }
110
111 pub fn do_prepare(
115 info: &DispatchInfoOf<T::RuntimeCall>,
116 len: usize,
117 next_len: u32,
118 ) -> Result<(), TransactionValidityError> {
119 let all_weight = Pallet::<T>::block_weight();
120 let maximum_weight = T::BlockWeights::get();
121 let next_weight =
122 calculate_consumed_weight::<T::RuntimeCall>(&maximum_weight, all_weight, info, len)?;
123 crate::system::BlockSize::<T>::put(next_len);
126 crate::system::BlockWeight::<T>::put(next_weight);
127 Ok(())
128 }
129
130 #[deprecated(note = "Use `topsoil_system::Pallet::reclaim_weight` instead.")]
131 pub fn do_post_dispatch(
132 info: &DispatchInfoOf<T::RuntimeCall>,
133 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
134 ) -> Result<(), TransactionValidityError> {
135 crate::system::Pallet::<T>::reclaim_weight(info, post_info)
136 }
137}
138
139pub fn calculate_consumed_weight<Call>(
143 maximum_weight: &BlockWeights,
144 mut all_weight: crate::system::ConsumedWeight,
145 info: &DispatchInfoOf<Call>,
146 len: usize,
147) -> Result<crate::system::ConsumedWeight, TransactionValidityError>
148where
149 Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
150{
151 let extrinsic_weight = info
153 .total_weight()
154 .saturating_add(maximum_weight.get(info.class).base_extrinsic)
155 .saturating_add(Weight::from_parts(0, len as u64));
156 let limit_per_class = maximum_weight.get(info.class);
157
158 if limit_per_class.max_total.is_none() && limit_per_class.reserved.is_none() {
160 all_weight.accrue(extrinsic_weight, info.class)
161 } else {
162 all_weight.checked_accrue(extrinsic_weight, info.class).map_err(|_| {
163 log::debug!(
164 target: LOG_TARGET,
165 "All weight checked add overflow.",
166 );
167
168 InvalidTransaction::ExhaustsResources
169 })?;
170 }
171
172 let per_class = *all_weight.get(info.class);
173
174 match limit_per_class.max_total {
176 Some(max) if per_class.any_gt(max) => {
177 log::debug!(
178 target: LOG_TARGET,
179 "Exceeded the per-class allowance.",
180 );
181
182 return Err(InvalidTransaction::ExhaustsResources.into());
183 },
184 _ => {},
187 }
188
189 if all_weight.total().any_gt(maximum_weight.max_block) {
192 match limit_per_class.reserved {
193 Some(reserved) if per_class.any_gt(reserved) => {
195 log::debug!(
196 target: LOG_TARGET,
197 "Total block weight is exceeded.",
198 );
199
200 return Err(InvalidTransaction::ExhaustsResources.into());
201 },
202 _ => {},
205 }
206 }
207
208 Ok(all_weight)
209}
210
211impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for CheckWeight<T>
212where
213 T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
214{
215 const IDENTIFIER: &'static str = "CheckWeight";
216 type Implicit = ();
217 type Pre = ();
218 type Val = u32; fn weight(&self, _: &T::RuntimeCall) -> Weight {
221 <T::ExtensionsWeightInfo as super::WeightInfo>::check_weight()
222 }
223
224 fn validate(
225 &self,
226 origin: T::RuntimeOrigin,
227 _call: &T::RuntimeCall,
228 info: &DispatchInfoOf<T::RuntimeCall>,
229 len: usize,
230 _self_implicit: Self::Implicit,
231 _inherited_implication: &impl Encode,
232 _source: TransactionSource,
233 ) -> ValidateResult<Self::Val, T::RuntimeCall> {
234 let (validity, next_len) = Self::do_validate(info, len)?;
235 Ok((validity, next_len, origin))
236 }
237
238 fn prepare(
239 self,
240 val: Self::Val,
241 _origin: &T::RuntimeOrigin,
242 _call: &T::RuntimeCall,
243 info: &DispatchInfoOf<T::RuntimeCall>,
244 len: usize,
245 ) -> Result<Self::Pre, TransactionValidityError> {
246 Self::do_prepare(info, len, val)
247 }
248
249 fn post_dispatch_details(
250 _pre: Self::Pre,
251 info: &DispatchInfoOf<T::RuntimeCall>,
252 post_info: &PostDispatchInfoOf<T::RuntimeCall>,
253 _len: usize,
254 _result: &DispatchResult,
255 ) -> Result<Weight, TransactionValidityError> {
256 crate::system::Pallet::<T>::reclaim_weight(info, post_info).map(|()| Weight::zero())
257 }
258
259 fn bare_validate(
260 _call: &T::RuntimeCall,
261 info: &DispatchInfoOf<T::RuntimeCall>,
262 len: usize,
263 ) -> topsoil_core::pallet_prelude::TransactionValidity {
264 Ok(Self::do_validate(info, len)?.0)
265 }
266
267 fn bare_validate_and_prepare(
268 _call: &T::RuntimeCall,
269 info: &DispatchInfoOf<T::RuntimeCall>,
270 len: usize,
271 ) -> Result<(), TransactionValidityError> {
272 let (_, next_len) = Self::do_validate(info, len)?;
273 Self::do_prepare(info, len, next_len)
274 }
275
276 fn bare_post_dispatch(
277 info: &DispatchInfoOf<T::RuntimeCall>,
278 post_info: &mut PostDispatchInfoOf<T::RuntimeCall>,
279 _len: usize,
280 _result: &DispatchResult,
281 ) -> Result<(), TransactionValidityError> {
282 crate::system::Pallet::<T>::reclaim_weight(info, post_info)
283 }
284}
285
286impl<T: Config + Send + Sync> core::fmt::Debug for CheckWeight<T> {
287 #[cfg(feature = "std")]
288 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
289 write!(f, "CheckWeight")
290 }
291
292 #[cfg(not(feature = "std"))]
293 fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
294 Ok(())
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::system::{
302 mock::{new_test_ext, RuntimeBlockWeights, System, Test, CALL},
303 BlockSize, BlockWeight, DispatchClass,
304 };
305 use core::marker::PhantomData;
306 use subsoil::runtime::traits::DispatchTransaction;
307 use topsoil_core::{assert_err, assert_ok, dispatch::Pays, weights::Weight};
308
309 fn block_weights() -> crate::system::limits::BlockWeights {
310 <Test as crate::system::Config>::BlockWeights::get()
311 }
312
313 fn normal_weight_limit() -> Weight {
314 block_weights()
315 .get(DispatchClass::Normal)
316 .max_total
317 .unwrap_or_else(|| block_weights().max_block)
318 }
319
320 fn block_weight_limit() -> Weight {
321 block_weights().max_block
322 }
323
324 fn normal_length_limit() -> u32 {
325 *<Test as Config>::BlockLength::get().max.get(DispatchClass::Normal)
326 }
327
328 #[test]
329 fn mandatory_extrinsic_doesnt_care_about_limits() {
330 fn check(call: impl FnOnce(&DispatchInfo, usize)) {
331 new_test_ext().execute_with(|| {
332 let max = DispatchInfo {
333 call_weight: Weight::MAX,
334 class: DispatchClass::Mandatory,
335 ..Default::default()
336 };
337 let len = 0_usize;
338
339 call(&max, len);
340 });
341 }
342
343 check(|max, len| {
344 let next_len = CheckWeight::<Test>::check_block_length(max, len).unwrap();
345 assert_ok!(CheckWeight::<Test>::do_prepare(max, len, next_len));
346 assert_eq!(System::block_weight().total(), Weight::MAX);
347 assert!(System::block_weight().total().ref_time() > block_weight_limit().ref_time());
348 });
349 check(|max, len| {
350 assert_ok!(CheckWeight::<Test>::do_validate(max, len));
351 });
352 }
353
354 #[test]
355 fn normal_extrinsic_limited_by_maximum_extrinsic_weight() {
356 new_test_ext().execute_with(|| {
357 let max = DispatchInfo {
358 call_weight: block_weights().get(DispatchClass::Normal).max_extrinsic.unwrap()
359 + Weight::from_parts(1, 0),
360 class: DispatchClass::Normal,
361 ..Default::default()
362 };
363 let len = 0_usize;
364 assert_err!(
365 CheckWeight::<Test>::do_validate(&max, len),
366 InvalidTransaction::ExhaustsResources
367 );
368 });
369 }
370
371 #[test]
372 fn operational_extrinsic_limited_by_operational_space_limit() {
373 new_test_ext().execute_with(|| {
374 let weights = block_weights();
375 let operational_limit = weights
376 .get(DispatchClass::Operational)
377 .max_total
378 .unwrap_or_else(|| weights.max_block);
379 let base_weight = weights.get(DispatchClass::Operational).base_extrinsic;
380
381 let call_weight = operational_limit - base_weight;
382 let okay = DispatchInfo {
383 call_weight,
384 class: DispatchClass::Operational,
385 ..Default::default()
386 };
387 let max = DispatchInfo {
388 call_weight: call_weight + Weight::from_parts(1, 0),
389 class: DispatchClass::Operational,
390 ..Default::default()
391 };
392 let len = 0_usize;
393
394 assert_eq!(CheckWeight::<Test>::do_validate(&okay, len), Ok(Default::default()));
395 assert_err!(
396 CheckWeight::<Test>::do_validate(&max, len),
397 InvalidTransaction::ExhaustsResources
398 );
399 });
400 }
401
402 #[test]
403 fn register_extra_weight_unchecked_doesnt_care_about_limits() {
404 new_test_ext().execute_with(|| {
405 System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Normal);
406 assert_eq!(System::block_weight().total(), Weight::MAX);
407 assert!(System::block_weight().total().ref_time() > block_weight_limit().ref_time());
408 });
409 }
410
411 #[test]
412 fn full_block_with_normal_and_operational() {
413 new_test_ext().execute_with(|| {
414 let max_normal =
420 DispatchInfo { call_weight: Weight::from_parts(753, 0), ..Default::default() };
421 let rest_operational = DispatchInfo {
422 call_weight: Weight::from_parts(246, 0),
423 class: DispatchClass::Operational,
424 ..Default::default()
425 };
426
427 let len = 0_usize;
428
429 let next_len = CheckWeight::<Test>::check_block_length(&max_normal, len).unwrap();
430 assert_ok!(CheckWeight::<Test>::do_prepare(&max_normal, len, next_len));
431 assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0));
432 let next_len = CheckWeight::<Test>::check_block_length(&rest_operational, len).unwrap();
433 assert_ok!(CheckWeight::<Test>::do_prepare(&rest_operational, len, next_len));
434 assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
435 assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0));
436 assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&rest_operational, len), Ok(()));
438 });
439 }
440
441 #[test]
442 fn dispatch_order_does_not_effect_weight_logic() {
443 new_test_ext().execute_with(|| {
444 let max_normal =
446 DispatchInfo { call_weight: Weight::from_parts(753, 0), ..Default::default() };
447 let rest_operational = DispatchInfo {
448 call_weight: Weight::from_parts(246, 0),
449 class: DispatchClass::Operational,
450 ..Default::default()
451 };
452
453 let len = 0_usize;
454
455 let next_len = CheckWeight::<Test>::check_block_length(&rest_operational, len).unwrap();
456 assert_ok!(CheckWeight::<Test>::do_prepare(&rest_operational, len, next_len));
457 assert_eq!(System::block_weight().total(), Weight::from_parts(266, 0));
459 let next_len = CheckWeight::<Test>::check_block_length(&max_normal, len).unwrap();
460 assert_ok!(CheckWeight::<Test>::do_prepare(&max_normal, len, next_len));
461 assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
462 assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0));
463 });
464 }
465
466 #[test]
467 fn operational_works_on_full_block() {
468 new_test_ext().execute_with(|| {
469 System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Mandatory);
471 let dispatch_normal = DispatchInfo {
472 call_weight: Weight::from_parts(251, 0),
473 class: DispatchClass::Normal,
474 ..Default::default()
475 };
476 let dispatch_operational = DispatchInfo {
477 call_weight: Weight::from_parts(246, 0),
478 class: DispatchClass::Operational,
479 ..Default::default()
480 };
481 let len = 0_usize;
482
483 let next_len = CheckWeight::<Test>::check_block_length(&dispatch_normal, len).unwrap();
484 assert_err!(
485 CheckWeight::<Test>::do_prepare(&dispatch_normal, len, next_len),
486 InvalidTransaction::ExhaustsResources
487 );
488 let next_len =
489 CheckWeight::<Test>::check_block_length(&dispatch_operational, len).unwrap();
490 assert_ok!(CheckWeight::<Test>::do_prepare(&dispatch_operational, len, next_len));
493 assert_err!(
495 CheckWeight::<Test>::do_prepare(&dispatch_operational, len, next_len),
496 InvalidTransaction::ExhaustsResources
497 );
498 assert_eq!(
500 CheckWeight::<Test>::check_extrinsic_weight(&dispatch_operational, len),
501 Ok(())
502 );
503 });
504 }
505
506 #[test]
507 fn signed_ext_check_weight_works_operational_tx() {
508 new_test_ext().execute_with(|| {
509 let normal =
510 DispatchInfo { call_weight: Weight::from_parts(100, 0), ..Default::default() };
511 let op = DispatchInfo {
512 call_weight: Weight::from_parts(100, 0),
513 extension_weight: Weight::zero(),
514 class: DispatchClass::Operational,
515 pays_fee: Pays::Yes,
516 };
517 let len = 0_usize;
518 let normal_limit = normal_weight_limit();
519
520 BlockWeight::<Test>::mutate(|current_weight| {
522 current_weight.set(normal_limit, DispatchClass::Normal)
523 });
524 assert_eq!(
526 CheckWeight::<Test>(PhantomData)
527 .validate_and_prepare(Some(1).into(), CALL, &normal, len, 0)
528 .unwrap_err(),
529 InvalidTransaction::ExhaustsResources.into()
530 );
531 assert_ok!(CheckWeight::<Test>(PhantomData).validate_and_prepare(
533 Some(1).into(),
534 CALL,
535 &op,
536 len,
537 0,
538 ));
539
540 let len = 100_usize;
542 BlockSize::<Test>::put(normal_length_limit());
543 assert_eq!(
544 CheckWeight::<Test>(PhantomData)
545 .validate_and_prepare(Some(1).into(), CALL, &normal, len, 0)
546 .unwrap_err(),
547 InvalidTransaction::ExhaustsResources.into()
548 );
549 assert_ok!(CheckWeight::<Test>(PhantomData).validate_and_prepare(
550 Some(1).into(),
551 CALL,
552 &op,
553 len,
554 0,
555 ));
556 })
557 }
558
559 #[test]
560 fn signed_ext_check_weight_block_size_works() {
561 new_test_ext().execute_with(|| {
562 let normal = DispatchInfo::default();
563 let normal_len_limit = normal_length_limit() as usize;
564 let reset_check_weight = |tx, s, f| {
565 BlockSize::<Test>::put(0);
566 let r = CheckWeight::<Test>(PhantomData).validate_and_prepare(
567 Some(1).into(),
568 CALL,
569 tx,
570 s,
571 0,
572 );
573 if f {
574 assert!(r.is_err())
575 } else {
576 assert!(r.is_ok())
577 }
578 };
579
580 reset_check_weight(&normal, normal_len_limit - 1, false);
581 reset_check_weight(&normal, normal_len_limit, false);
582 reset_check_weight(&normal, normal_len_limit + 1, true);
583
584 let op = DispatchInfo {
586 call_weight: Weight::zero(),
587 extension_weight: Weight::zero(),
588 class: DispatchClass::Operational,
589 pays_fee: Pays::Yes,
590 };
591 let operational_limit =
592 *<Test as Config>::BlockLength::get().max.get(DispatchClass::Operational) as usize;
593 reset_check_weight(&op, normal_len_limit, false);
594 reset_check_weight(&op, normal_len_limit + 100, false);
595 reset_check_weight(&op, operational_limit, false);
596 reset_check_weight(&op, operational_limit + 1, true);
597 })
598 }
599
600 #[test]
601 fn signed_ext_check_weight_works_normal_tx() {
602 new_test_ext().execute_with(|| {
603 let normal_limit = normal_weight_limit();
604 let small =
605 DispatchInfo { call_weight: Weight::from_parts(100, 0), ..Default::default() };
606 let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
607 let medium =
608 DispatchInfo { call_weight: normal_limit - base_extrinsic, ..Default::default() };
609 let big = DispatchInfo {
610 call_weight: normal_limit - base_extrinsic + Weight::from_parts(1, 0),
611 ..Default::default()
612 };
613 let len = 0_usize;
614
615 let reset_check_weight = |i, f, s| {
616 BlockWeight::<Test>::mutate(|current_weight| {
617 current_weight.set(s, DispatchClass::Normal)
618 });
619 let r = CheckWeight::<Test>(PhantomData).validate_and_prepare(
620 Some(1).into(),
621 CALL,
622 i,
623 len,
624 0,
625 );
626 if f {
627 assert!(r.is_err())
628 } else {
629 assert!(r.is_ok())
630 }
631 };
632
633 reset_check_weight(&small, false, Weight::zero());
634 reset_check_weight(&medium, false, Weight::zero());
635 reset_check_weight(&big, true, Weight::from_parts(1, 0));
636 })
637 }
638
639 #[test]
640 fn signed_ext_check_weight_refund_works() {
641 new_test_ext().execute_with(|| {
642 let info =
644 DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
645 let post_info = PostDispatchInfo {
646 actual_weight: Some(Weight::from_parts(128, 0)),
647 pays_fee: Default::default(),
648 };
649 let len = 0_usize;
650 let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
651
652 BlockWeight::<Test>::mutate(|current_weight| {
654 current_weight.set(Weight::zero(), DispatchClass::Mandatory);
655 current_weight
656 .set(Weight::from_parts(256, 0) - base_extrinsic, DispatchClass::Normal);
657 });
658
659 let pre = CheckWeight::<Test>(PhantomData)
660 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
661 .unwrap()
662 .0;
663 assert_eq!(
664 BlockWeight::<Test>::get().total(),
665 info.total_weight() + Weight::from_parts(256, 0)
666 );
667
668 assert_ok!(CheckWeight::<Test>::post_dispatch_details(
669 pre,
670 &info,
671 &post_info,
672 len,
673 &Ok(())
674 ));
675 assert_eq!(
676 BlockWeight::<Test>::get().total(),
677 post_info.actual_weight.unwrap() + Weight::from_parts(256, 0)
678 );
679 })
680 }
681
682 #[test]
683 fn signed_ext_check_weight_actual_weight_higher_than_max_is_capped() {
684 new_test_ext().execute_with(|| {
685 let info =
686 DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
687 let post_info = PostDispatchInfo {
688 actual_weight: Some(Weight::from_parts(700, 0)),
689 pays_fee: Default::default(),
690 };
691 let len = 0_usize;
692
693 BlockWeight::<Test>::mutate(|current_weight| {
694 current_weight.set(Weight::zero(), DispatchClass::Mandatory);
695 current_weight.set(Weight::from_parts(128, 0), DispatchClass::Normal);
696 });
697
698 let pre = CheckWeight::<Test>(PhantomData)
699 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
700 .unwrap()
701 .0;
702 assert_eq!(
703 BlockWeight::<Test>::get().total(),
704 info.total_weight()
705 + Weight::from_parts(128, 0)
706 + block_weights().get(DispatchClass::Normal).base_extrinsic,
707 );
708
709 assert_ok!(CheckWeight::<Test>::post_dispatch_details(
710 pre,
711 &info,
712 &post_info,
713 len,
714 &Ok(())
715 ));
716 assert_eq!(
717 BlockWeight::<Test>::get().total(),
718 info.total_weight()
719 + Weight::from_parts(128, 0)
720 + block_weights().get(DispatchClass::Normal).base_extrinsic,
721 );
722 })
723 }
724
725 #[test]
726 fn extrinsic_already_refunded_more_precisely() {
727 new_test_ext().execute_with(|| {
728 let info =
730 DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
731 let post_info = PostDispatchInfo {
732 actual_weight: Some(Weight::from_parts(128, 0)),
733 pays_fee: Default::default(),
734 };
735 let prior_block_weight = Weight::from_parts(64, 0);
736 let accurate_refund = Weight::from_parts(510, 0);
737 let len = 0_usize;
738 let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
739
740 BlockWeight::<Test>::mutate(|current_weight| {
742 current_weight.set(Weight::zero(), DispatchClass::Mandatory);
743 current_weight.set(prior_block_weight, DispatchClass::Normal);
744 });
745
746 let pre = CheckWeight::<Test>(PhantomData)
748 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
749 .unwrap()
750 .0;
751
752 assert_eq!(
753 BlockWeight::<Test>::get().total(),
754 info.total_weight() + prior_block_weight + base_extrinsic
755 );
756
757 BlockWeight::<Test>::mutate(|current_weight| {
759 current_weight.reduce(accurate_refund, DispatchClass::Normal);
760 });
761 crate::system::ExtrinsicWeightReclaimed::<Test>::put(accurate_refund);
762
763 assert_ok!(CheckWeight::<Test>::post_dispatch_details(
765 pre,
766 &info,
767 &post_info,
768 len,
769 &Ok(())
770 ));
771
772 assert_eq!(crate::system::ExtrinsicWeightReclaimed::<Test>::get(), accurate_refund);
774 assert_eq!(
775 BlockWeight::<Test>::get().total(),
776 info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic
777 );
778 })
779 }
780
781 #[test]
782 fn extrinsic_already_refunded_less_precisely() {
783 new_test_ext().execute_with(|| {
784 let info =
786 DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
787 let post_info = PostDispatchInfo {
788 actual_weight: Some(Weight::from_parts(128, 0)),
789 pays_fee: Default::default(),
790 };
791 let prior_block_weight = Weight::from_parts(64, 0);
792 let inaccurate_refund = Weight::from_parts(110, 0);
793 let len = 0_usize;
794 let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
795
796 BlockWeight::<Test>::mutate(|current_weight| {
798 current_weight.set(Weight::zero(), DispatchClass::Mandatory);
799 current_weight.set(prior_block_weight, DispatchClass::Normal);
800 });
801
802 let pre = CheckWeight::<Test>(PhantomData)
804 .validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
805 .unwrap()
806 .0;
807
808 let expected = info.total_weight() + prior_block_weight + base_extrinsic;
809 assert_eq!(expected, BlockWeight::<Test>::get().total());
810 assert_eq!(
811 RuntimeBlockWeights::get().max_block - expected,
812 System::remaining_block_weight().remaining()
813 );
814
815 BlockWeight::<Test>::mutate(|current_weight| {
817 current_weight.reduce(inaccurate_refund, DispatchClass::Normal);
818 });
819 crate::system::ExtrinsicWeightReclaimed::<Test>::put(inaccurate_refund);
820
821 assert_ok!(CheckWeight::<Test>::post_dispatch_details(
823 pre,
824 &info,
825 &post_info,
826 len,
827 &Ok(())
828 ));
829
830 assert_eq!(
832 crate::system::ExtrinsicWeightReclaimed::<Test>::get(),
833 post_info.calc_unspent(&info)
834 );
835 let expected = post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic;
836 assert_eq!(expected, BlockWeight::<Test>::get().total());
837 assert_eq!(
838 RuntimeBlockWeights::get().max_block - expected,
839 System::remaining_block_weight().remaining()
840 );
841 })
842 }
843
844 #[test]
845 fn zero_weight_extrinsic_still_has_base_weight() {
846 new_test_ext().execute_with(|| {
847 let weights = block_weights();
848 let free = DispatchInfo { call_weight: Weight::zero(), ..Default::default() };
849 let len = 0_usize;
850
851 assert_eq!(System::block_weight().total(), weights.base_block);
853 assert_ok!(CheckWeight::<Test>(PhantomData).validate_and_prepare(
854 Some(1).into(),
855 CALL,
856 &free,
857 len,
858 0,
859 ));
860 assert_eq!(
861 System::block_weight().total(),
862 weights.get(DispatchClass::Normal).base_extrinsic + weights.base_block
863 );
864 })
865 }
866
867 #[test]
868 fn normal_and_mandatory_tracked_separately() {
869 new_test_ext().execute_with(|| {
870 let max_normal =
874 DispatchInfo { call_weight: Weight::from_parts(753, 0), ..Default::default() };
875 let mandatory = DispatchInfo {
876 call_weight: Weight::from_parts(1019, 0),
877 class: DispatchClass::Mandatory,
878 ..Default::default()
879 };
880
881 let len = 0_usize;
882
883 let next_len = CheckWeight::<Test>::check_block_length(&max_normal, len).unwrap();
884 assert_ok!(CheckWeight::<Test>::do_prepare(&max_normal, len, next_len));
885 assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0));
886 let next_len = CheckWeight::<Test>::check_block_length(&mandatory, len).unwrap();
887 assert_ok!(CheckWeight::<Test>::do_prepare(&mandatory, len, next_len));
888 assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
889 assert_eq!(System::block_weight().total(), Weight::from_parts(1024 + 768, 0));
890 assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&mandatory, len), Ok(()));
891 });
892 }
893
894 #[test]
895 fn no_max_total_should_still_be_limited_by_max_block() {
896 let maximum_weight = BlockWeights::builder()
898 .base_block(Weight::zero())
899 .for_class(DispatchClass::non_mandatory(), |w| {
900 w.base_extrinsic = Weight::zero();
901 w.max_total = Some(Weight::from_parts(20, u64::MAX));
902 })
903 .for_class(DispatchClass::Mandatory, |w| {
904 w.base_extrinsic = Weight::zero();
905 w.reserved = Some(Weight::from_parts(5, u64::MAX));
906 w.max_total = None;
907 })
908 .build_or_panic();
909 let all_weight = crate::system::ConsumedWeight::new(|class| match class {
910 DispatchClass::Normal => Weight::from_parts(10, 0),
911 DispatchClass::Operational => Weight::from_parts(10, 0),
912 DispatchClass::Mandatory => Weight::zero(),
913 });
914 assert_eq!(maximum_weight.max_block, all_weight.total().set_proof_size(u64::MAX));
915
916 let mandatory1 = DispatchInfo {
918 call_weight: Weight::from_parts(5, 0),
919 class: DispatchClass::Mandatory,
920 ..Default::default()
921 };
922 let mandatory2 = DispatchInfo {
924 call_weight: Weight::from_parts(6, 0),
925 class: DispatchClass::Mandatory,
926 ..Default::default()
927 };
928
929 assert_ok!(calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
931 &maximum_weight,
932 all_weight.clone(),
933 &mandatory1,
934 0
935 ));
936 assert_err!(
937 calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
938 &maximum_weight,
939 all_weight,
940 &mandatory2,
941 0
942 ),
943 InvalidTransaction::ExhaustsResources
944 );
945 }
946
947 #[test]
948 fn check_extrinsic_proof_weight_includes_length() {
949 new_test_ext().execute_with(|| {
950 let weights = block_weights();
952 let max_extrinsic = weights.get(DispatchClass::Normal).max_extrinsic.unwrap();
953
954 let max_proof_size = max_extrinsic.proof_size() as usize;
955 let info = DispatchInfo {
957 call_weight: max_extrinsic.set_proof_size(0),
958 class: DispatchClass::Normal,
959 ..Default::default()
960 };
961
962 assert_ok!(CheckWeight::<Test>::check_extrinsic_weight(&info, 0));
964
965 assert_ok!(CheckWeight::<Test>::check_extrinsic_weight(&info, 100));
967
968 assert_ok!(CheckWeight::<Test>::check_extrinsic_weight(&info, max_proof_size));
970
971 assert_err!(
973 CheckWeight::<Test>::check_extrinsic_weight(&info, max_proof_size + 1),
974 InvalidTransaction::ExhaustsResources
975 );
976
977 let info_at_limit = DispatchInfo {
979 call_weight: max_extrinsic,
980 class: DispatchClass::Normal,
981 ..Default::default()
982 };
983
984 assert_ok!(CheckWeight::<Test>::check_extrinsic_weight(&info_at_limit, 0));
986
987 assert_err!(
989 CheckWeight::<Test>::check_extrinsic_weight(&info_at_limit, 1),
990 InvalidTransaction::ExhaustsResources
991 );
992
993 let info_zero = DispatchInfo {
995 call_weight: Weight::zero(),
996 class: DispatchClass::Normal,
997 ..Default::default()
998 };
999 let large_len = usize::MAX;
1001
1002 let result = CheckWeight::<Test>::check_extrinsic_weight(&info_zero, large_len);
1004 assert_err!(result, InvalidTransaction::ExhaustsResources);
1006
1007 let info_with_minimal_proof_size = DispatchInfo {
1009 call_weight: Weight::from_parts(0, 10),
1010 class: DispatchClass::Normal,
1011 ..Default::default()
1012 };
1013
1014 let result = CheckWeight::<Test>::check_extrinsic_weight(
1016 &info_with_minimal_proof_size,
1017 large_len,
1018 );
1019 assert_err!(result, InvalidTransaction::ExhaustsResources);
1021 });
1022 }
1023
1024 #[test]
1025 fn proof_size_includes_length() {
1026 let maximum_weight = BlockWeights::builder()
1027 .base_block(Weight::zero())
1028 .for_class(DispatchClass::non_mandatory(), |w| {
1029 w.base_extrinsic = Weight::zero();
1030 w.max_total = Some(Weight::from_parts(20, 1000));
1031 })
1032 .for_class(DispatchClass::Mandatory, |w| {
1033 w.base_extrinsic = Weight::zero();
1034 w.max_total = Some(Weight::from_parts(20, 1000));
1035 })
1036 .build_or_panic();
1037 let all_weight = crate::system::ConsumedWeight::new(|class| match class {
1038 DispatchClass::Normal => Weight::from_parts(5, 0),
1039 DispatchClass::Operational => Weight::from_parts(5, 0),
1040 DispatchClass::Mandatory => Weight::from_parts(0, 0),
1041 });
1042
1043 let normal = DispatchInfo {
1044 call_weight: Weight::from_parts(5, 0),
1045 class: DispatchClass::Normal,
1046 ..Default::default()
1047 };
1048
1049 let mandatory = DispatchInfo {
1050 call_weight: Weight::from_parts(5, 0),
1051 class: DispatchClass::Mandatory,
1052 ..Default::default()
1053 };
1054
1055 let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1057 &maximum_weight,
1058 all_weight.clone(),
1059 &normal,
1060 0,
1061 )
1062 .unwrap();
1063
1064 assert_eq!(consumed.total().saturating_sub(all_weight.total()), normal.total_weight());
1065
1066 let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1067 &maximum_weight,
1068 all_weight.clone(),
1069 &mandatory,
1070 0,
1071 )
1072 .unwrap();
1073 assert_eq!(consumed.total().saturating_sub(all_weight.total()), mandatory.total_weight());
1074
1075 let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1077 &maximum_weight,
1078 all_weight.clone(),
1079 &normal,
1080 100,
1081 )
1082 .unwrap();
1083 assert_eq!(
1085 consumed.total().saturating_sub(all_weight.total()),
1086 normal.total_weight().add_proof_size(100)
1087 );
1088
1089 let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1090 &maximum_weight,
1091 all_weight.clone(),
1092 &mandatory,
1093 100,
1094 )
1095 .unwrap();
1096 assert_eq!(
1098 consumed.total().saturating_sub(all_weight.total()),
1099 mandatory.total_weight().add_proof_size(100)
1100 );
1101
1102 let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1104 &maximum_weight,
1105 all_weight.clone(),
1106 &normal,
1107 2000,
1108 );
1109 assert_eq!(consumed, Err(InvalidTransaction::ExhaustsResources.into()));
1111
1112 let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1114 &maximum_weight,
1115 all_weight.clone(),
1116 &mandatory,
1117 2000,
1118 );
1119 assert_eq!(consumed, Err(InvalidTransaction::ExhaustsResources.into()));
1121 }
1122}