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