1use crate::{
21 storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason,
22 Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
23};
24
25use alloc::vec::Vec;
26use core::{fmt::Debug, marker::PhantomData};
27use frame_support::{
28 ensure,
29 traits::{
30 fungible::{Mutate, MutateHold},
31 tokens::{
32 Fortitude, Fortitude::Polite, Precision, Preservation, Restriction, WithdrawConsequence,
33 },
34 Get,
35 },
36 DebugNoBound, DefaultNoBound,
37};
38use sp_runtime::{
39 traits::{Saturating, Zero},
40 DispatchError, FixedPointNumber, FixedU128,
41};
42
43pub type DepositOf<T> = Deposit<BalanceOf<T>>;
45
46pub type Meter<T> = RawMeter<T, ReservingExt, Root>;
48
49pub type NestedMeter<T> = RawMeter<T, ReservingExt, Nested>;
51
52pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
56
57pub trait Ext<T: Config> {
61 fn check_limit(
73 origin: &T::AccountId,
74 limit: Option<BalanceOf<T>>,
75 min_leftover: BalanceOf<T>,
76 ) -> Result<BalanceOf<T>, DispatchError>;
77 fn charge(
85 origin: &T::AccountId,
86 contract: &T::AccountId,
87 amount: &DepositOf<T>,
88 state: &ContractState<T>,
89 ) -> Result<(), DispatchError>;
90}
91
92pub enum ReservingExt {}
96
97pub trait State: private::Sealed {}
101
102#[derive(Default, Debug)]
104pub struct Root;
105
106#[derive(DefaultNoBound, DebugNoBound)]
109pub enum Nested {
110 #[default]
111 DerivedLimit,
112 OwnLimit,
113}
114
115impl State for Root {}
116impl State for Nested {}
117
118#[derive(DefaultNoBound, DebugNoBound)]
120pub struct RawMeter<T: Config, E, S: State + Default + Debug> {
121 limit: BalanceOf<T>,
123 total_deposit: DepositOf<T>,
125 own_contribution: Contribution<T>,
127 charges: Vec<Charge<T>>,
132 nested: S,
134 _phantom: PhantomData<E>,
136}
137
138#[derive(Default, DebugNoBound)]
140pub struct Diff {
141 pub bytes_added: u32,
143 pub bytes_removed: u32,
145 pub items_added: u32,
147 pub items_removed: u32,
149}
150
151impl Diff {
152 pub fn update_contract<T: Config>(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
161 let per_byte = T::DepositPerByte::get();
162 let per_item = T::DepositPerItem::get();
163 let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed);
164 let items_added = self.items_added.saturating_sub(self.items_removed);
165 let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into()));
166 let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into()));
167
168 let info = if let Some(info) = info {
170 info
171 } else {
172 debug_assert_eq!(self.bytes_removed, 0);
173 debug_assert_eq!(self.items_removed, 0);
174 return bytes_deposit.saturating_add(&items_deposit);
175 };
176
177 let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added);
179 let items_removed = self.items_removed.saturating_sub(self.items_added);
180 let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes)
181 .unwrap_or_default()
182 .min(FixedU128::from_u32(1));
183 bytes_deposit = bytes_deposit
184 .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit)));
185 let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items)
186 .unwrap_or_default()
187 .min(FixedU128::from_u32(1));
188 items_deposit = items_deposit
189 .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit)));
190
191 info.storage_bytes =
193 info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed);
194 info.storage_items =
195 info.storage_items.saturating_add(items_added).saturating_sub(items_removed);
196 match &bytes_deposit {
197 Deposit::Charge(amount) => {
198 info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount)
199 },
200 Deposit::Refund(amount) => {
201 info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount)
202 },
203 }
204 match &items_deposit {
205 Deposit::Charge(amount) => {
206 info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount)
207 },
208 Deposit::Refund(amount) => {
209 info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount)
210 },
211 }
212
213 bytes_deposit.saturating_add(&items_deposit)
214 }
215}
216
217impl Diff {
218 fn saturating_add(&self, rhs: &Self) -> Self {
219 Self {
220 bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
221 bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
222 items_added: self.items_added.saturating_add(rhs.items_added),
223 items_removed: self.items_removed.saturating_add(rhs.items_removed),
224 }
225 }
226}
227
228#[derive(DebugNoBound, Clone, PartialEq, Eq)]
232pub enum ContractState<T: Config> {
233 Alive,
234 Terminated { beneficiary: AccountIdOf<T> },
235}
236
237#[derive(DebugNoBound, Clone)]
247struct Charge<T: Config> {
248 contract: T::AccountId,
249 amount: DepositOf<T>,
250 state: ContractState<T>,
251}
252
253#[derive(DebugNoBound)]
255enum Contribution<T: Config> {
256 Alive(Diff),
258 Checked(DepositOf<T>),
261 Terminated { deposit: DepositOf<T>, beneficiary: AccountIdOf<T> },
265}
266
267impl<T: Config> Contribution<T> {
268 fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
270 match self {
271 Self::Alive(diff) => diff.update_contract::<T>(info),
272 Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) => {
273 deposit.clone()
274 },
275 }
276 }
277}
278
279impl<T: Config> Default for Contribution<T> {
280 fn default() -> Self {
281 Self::Alive(Default::default())
282 }
283}
284
285impl<T, E, S> RawMeter<T, E, S>
287where
288 T: Config,
289 E: Ext<T>,
290 S: State + Default + Debug,
291{
292 pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
299 debug_assert!(matches!(self.contract_state(), ContractState::Alive));
300 let limit = self.available().min(limit);
303 if limit.is_zero() {
304 RawMeter { limit: self.available(), ..Default::default() }
305 } else {
306 RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() }
307 }
308 }
309
310 pub fn absorb(
326 &mut self,
327 absorbed: RawMeter<T, E, Nested>,
328 contract: &T::AccountId,
329 info: Option<&mut ContractInfo<T>>,
330 ) {
331 let own_deposit = absorbed.own_contribution.update_contract(info);
332 self.total_deposit = self
333 .total_deposit
334 .saturating_add(&absorbed.total_deposit)
335 .saturating_add(&own_deposit);
336 self.charges.extend_from_slice(&absorbed.charges);
337 if !own_deposit.is_zero() {
338 self.charges.push(Charge {
339 contract: contract.clone(),
340 amount: own_deposit,
341 state: absorbed.contract_state(),
342 });
343 }
344 }
345
346 fn available(&self) -> BalanceOf<T> {
348 self.total_deposit.available(&self.limit)
349 }
350
351 fn contract_state(&self) -> ContractState<T> {
353 match &self.own_contribution {
354 Contribution::Terminated { deposit: _, beneficiary } => {
355 ContractState::Terminated { beneficiary: beneficiary.clone() }
356 },
357 _ => ContractState::Alive,
358 }
359 }
360}
361
362impl<T, E> RawMeter<T, E, Root>
364where
365 T: Config,
366 E: Ext<T>,
367{
368 pub fn new(
372 origin: &Origin<T>,
373 limit: Option<BalanceOf<T>>,
374 min_leftover: BalanceOf<T>,
375 ) -> Result<Self, DispatchError> {
376 return match origin {
378 Origin::Root => Ok(Self {
379 limit: limit.unwrap_or(T::DefaultDepositLimit::get()),
380 ..Default::default()
381 }),
382 Origin::Signed(o) => {
383 let limit = E::check_limit(o, limit, min_leftover)?;
384 Ok(Self { limit, ..Default::default() })
385 },
386 };
387 }
388
389 pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
396 let origin = match origin {
398 Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
399 Origin::Signed(o) => o,
400 };
401 for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
402 E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
403 }
404 for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
405 E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
406 }
407 Ok(self.total_deposit)
408 }
409}
410
411impl<T, E> RawMeter<T, E, Nested>
413where
414 T: Config,
415 E: Ext<T>,
416{
417 pub fn charge(&mut self, diff: &Diff) {
419 match &mut self.own_contribution {
420 Contribution::Alive(own) => *own = own.saturating_add(diff),
421 _ => panic!("Charge is never called after termination; qed"),
422 };
423 }
424
425 pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
432 self.total_deposit = self.total_deposit.saturating_add(&amount);
433 self.charges.push(Charge { contract, amount, state: ContractState::Alive });
434 }
435
436 pub fn charge_instantiate(
440 &mut self,
441 origin: &T::AccountId,
442 contract: &T::AccountId,
443 contract_info: &mut ContractInfo<T>,
444 code_info: &CodeInfo<T>,
445 ) -> Result<(), DispatchError> {
446 debug_assert!(matches!(self.contract_state(), ContractState::Alive));
447
448 let ed = Pallet::<T>::min_balance();
450 self.total_deposit = Deposit::Charge(ed);
451 T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
452
453 System::<T>::inc_consumers(contract)?;
457
458 let deposit = contract_info.update_base_deposit(&code_info);
459 let deposit = Deposit::Charge(deposit);
460
461 self.charge_deposit(contract.clone(), deposit);
462 Ok(())
463 }
464
465 pub fn terminate(&mut self, info: &ContractInfo<T>, beneficiary: T::AccountId) {
471 debug_assert!(matches!(self.contract_state(), ContractState::Alive));
472 self.own_contribution = Contribution::Terminated {
473 deposit: Deposit::Refund(info.total_deposit()),
474 beneficiary,
475 };
476 }
477
478 pub fn enforce_limit(
488 &mut self,
489 info: Option<&mut ContractInfo<T>>,
490 ) -> Result<(), DispatchError> {
491 let deposit = self.own_contribution.update_contract(info);
492 let total_deposit = self.total_deposit.saturating_add(&deposit);
493 if matches!(self.contract_state(), ContractState::Alive) {
495 self.own_contribution = Contribution::Checked(deposit);
496 }
497 if let Deposit::Charge(amount) = total_deposit {
498 if amount > self.limit {
499 return Err(<Error<T>>::StorageDepositLimitExhausted.into());
500 }
501 }
502 Ok(())
503 }
504
505 pub fn enforce_subcall_limit(
508 &mut self,
509 info: Option<&mut ContractInfo<T>>,
510 ) -> Result<(), DispatchError> {
511 match self.nested {
512 Nested::OwnLimit => self.enforce_limit(info),
513 Nested::DerivedLimit => Ok(()),
514 }
515 }
516}
517
518impl<T: Config> Ext<T> for ReservingExt {
519 fn check_limit(
520 origin: &T::AccountId,
521 limit: Option<BalanceOf<T>>,
522 min_leftover: BalanceOf<T>,
523 ) -> Result<BalanceOf<T>, DispatchError> {
524 let max = T::Currency::reducible_balance(origin, Preservation::Preserve, Polite)
528 .saturating_sub(min_leftover)
529 .saturating_sub(Pallet::<T>::min_balance());
530 let default = max.min(T::DefaultDepositLimit::get());
531 let limit = limit.unwrap_or(default);
532 ensure!(
533 limit <= max &&
534 matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success),
535 <Error<T>>::StorageDepositNotEnoughFunds,
536 );
537 Ok(limit)
538 }
539
540 fn charge(
541 origin: &T::AccountId,
542 contract: &T::AccountId,
543 amount: &DepositOf<T>,
544 state: &ContractState<T>,
545 ) -> Result<(), DispatchError> {
546 match amount {
547 Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()),
548 Deposit::Charge(amount) => {
549 T::Currency::transfer_and_hold(
552 &HoldReason::StorageDepositReserve.into(),
553 origin,
554 contract,
555 *amount,
556 Precision::Exact,
557 Preservation::Preserve,
558 Fortitude::Polite,
559 )?;
560
561 Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndHeld {
562 from: origin.clone(),
563 to: contract.clone(),
564 amount: *amount,
565 });
566 },
567 Deposit::Refund(amount) => {
568 let transferred = T::Currency::transfer_on_hold(
569 &HoldReason::StorageDepositReserve.into(),
570 contract,
571 origin,
572 *amount,
573 Precision::BestEffort,
574 Restriction::Free,
575 Fortitude::Polite,
576 )?;
577
578 Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndReleased {
579 from: contract.clone(),
580 to: origin.clone(),
581 amount: transferred,
582 });
583
584 if transferred < *amount {
585 log::error!(
589 target: LOG_TARGET,
590 "Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.",
591 amount, contract, origin, transferred,
592 );
593 }
594 },
595 }
596 if let ContractState::<T>::Terminated { beneficiary } = state {
597 System::<T>::dec_consumers(&contract);
598 T::Currency::transfer(
600 &contract,
601 &beneficiary,
602 T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite),
603 Preservation::Expendable,
604 )?;
605 }
606 Ok(())
607 }
608}
609
610mod private {
611 pub trait Sealed {}
612 impl Sealed for super::Root {}
613 impl Sealed for super::Nested {}
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619 use crate::{
620 exec::AccountIdOf,
621 tests::{Test, ALICE, BOB, CHARLIE},
622 };
623 use frame_support::parameter_types;
624 use pretty_assertions::assert_eq;
625
626 type TestMeter = RawMeter<Test, TestExt, Root>;
627
628 parameter_types! {
629 static TestExtTestValue: TestExt = Default::default();
630 }
631
632 #[derive(Debug, PartialEq, Eq, Clone)]
633 struct LimitCheck {
634 origin: AccountIdOf<Test>,
635 limit: BalanceOf<Test>,
636 min_leftover: BalanceOf<Test>,
637 }
638
639 #[derive(Debug, PartialEq, Eq, Clone)]
640 struct Charge {
641 origin: AccountIdOf<Test>,
642 contract: AccountIdOf<Test>,
643 amount: DepositOf<Test>,
644 state: ContractState<Test>,
645 }
646
647 #[derive(Default, Debug, PartialEq, Eq, Clone)]
648 pub struct TestExt {
649 limit_checks: Vec<LimitCheck>,
650 charges: Vec<Charge>,
651 }
652
653 impl TestExt {
654 fn clear(&mut self) {
655 self.limit_checks.clear();
656 self.charges.clear();
657 }
658 }
659
660 impl Ext<Test> for TestExt {
661 fn check_limit(
662 origin: &AccountIdOf<Test>,
663 limit: Option<BalanceOf<Test>>,
664 min_leftover: BalanceOf<Test>,
665 ) -> Result<BalanceOf<Test>, DispatchError> {
666 let limit = limit.unwrap_or(42);
667 TestExtTestValue::mutate(|ext| {
668 ext.limit_checks
669 .push(LimitCheck { origin: origin.clone(), limit, min_leftover })
670 });
671 Ok(limit)
672 }
673
674 fn charge(
675 origin: &AccountIdOf<Test>,
676 contract: &AccountIdOf<Test>,
677 amount: &DepositOf<Test>,
678 state: &ContractState<Test>,
679 ) -> Result<(), DispatchError> {
680 TestExtTestValue::mutate(|ext| {
681 ext.charges.push(Charge {
682 origin: origin.clone(),
683 contract: contract.clone(),
684 amount: amount.clone(),
685 state: state.clone(),
686 })
687 });
688 Ok(())
689 }
690 }
691
692 fn clear_ext() {
693 TestExtTestValue::mutate(|ext| ext.clear())
694 }
695
696 struct ChargingTestCase {
697 origin: Origin<Test>,
698 deposit: DepositOf<Test>,
699 expected: TestExt,
700 }
701
702 #[derive(Default)]
703 struct StorageInfo {
704 bytes: u32,
705 items: u32,
706 bytes_deposit: BalanceOf<Test>,
707 items_deposit: BalanceOf<Test>,
708 }
709
710 fn new_info(info: StorageInfo) -> ContractInfo<Test> {
711 ContractInfo::<Test> {
712 trie_id: Default::default(),
713 code_hash: Default::default(),
714 storage_bytes: info.bytes,
715 storage_items: info.items,
716 storage_byte_deposit: info.bytes_deposit,
717 storage_item_deposit: info.items_deposit,
718 storage_base_deposit: Default::default(),
719 delegate_dependencies: Default::default(),
720 }
721 }
722
723 #[test]
724 fn new_reserves_balance_works() {
725 clear_ext();
726
727 TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
728
729 assert_eq!(
730 TestExtTestValue::get(),
731 TestExt {
732 limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
733 ..Default::default()
734 }
735 )
736 }
737
738 #[test]
739 fn empty_charge_works() {
740 clear_ext();
741
742 let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
743 assert_eq!(meter.available(), 1_000);
744
745 let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
747 nested0.charge(&Default::default());
748 meter.absorb(nested0, &BOB, None);
749
750 assert_eq!(
751 TestExtTestValue::get(),
752 TestExt {
753 limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
754 ..Default::default()
755 }
756 )
757 }
758
759 #[test]
760 fn charging_works() {
761 let test_cases = vec![
762 ChargingTestCase {
763 origin: Origin::<Test>::from_account_id(ALICE),
764 deposit: Deposit::Refund(28),
765 expected: TestExt {
766 limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }],
767 charges: vec![
768 Charge {
769 origin: ALICE,
770 contract: CHARLIE,
771 amount: Deposit::Refund(10),
772 state: ContractState::Alive,
773 },
774 Charge {
775 origin: ALICE,
776 contract: CHARLIE,
777 amount: Deposit::Refund(20),
778 state: ContractState::Alive,
779 },
780 Charge {
781 origin: ALICE,
782 contract: BOB,
783 amount: Deposit::Charge(2),
784 state: ContractState::Alive,
785 },
786 ],
787 },
788 },
789 ChargingTestCase {
790 origin: Origin::<Test>::Root,
791 deposit: Deposit::Charge(0),
792 expected: TestExt { limit_checks: vec![], charges: vec![] },
793 },
794 ];
795
796 for test_case in test_cases {
797 clear_ext();
798
799 let mut meter = TestMeter::new(&test_case.origin, Some(100), 0).unwrap();
800 assert_eq!(meter.available(), 100);
801
802 let mut nested0_info = new_info(StorageInfo {
803 bytes: 100,
804 items: 5,
805 bytes_deposit: 100,
806 items_deposit: 10,
807 });
808 let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
809 nested0.charge(&Diff {
810 bytes_added: 108,
811 bytes_removed: 5,
812 items_added: 1,
813 items_removed: 2,
814 });
815 nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
816
817 let mut nested1_info = new_info(StorageInfo {
818 bytes: 100,
819 items: 10,
820 bytes_deposit: 100,
821 items_deposit: 20,
822 });
823 let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
824 nested1.charge(&Diff { items_removed: 5, ..Default::default() });
825 nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info));
826
827 let mut nested2_info = new_info(StorageInfo {
828 bytes: 100,
829 items: 7,
830 bytes_deposit: 100,
831 items_deposit: 20,
832 });
833 let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
834 nested2.charge(&Diff { items_removed: 7, ..Default::default() });
835 nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info));
836
837 nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
838 meter.absorb(nested0, &BOB, Some(&mut nested0_info));
839
840 assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
841
842 assert_eq!(nested0_info.extra_deposit(), 112);
843 assert_eq!(nested1_info.extra_deposit(), 110);
844 assert_eq!(nested2_info.extra_deposit(), 100);
845
846 assert_eq!(TestExtTestValue::get(), test_case.expected)
847 }
848 }
849
850 #[test]
851 fn termination_works() {
852 let test_cases = vec![
853 ChargingTestCase {
854 origin: Origin::<Test>::from_account_id(ALICE),
855 deposit: Deposit::Refund(108),
856 expected: TestExt {
857 limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
858 charges: vec![
859 Charge {
860 origin: ALICE,
861 contract: CHARLIE,
862 amount: Deposit::Refund(120),
863 state: ContractState::Terminated { beneficiary: CHARLIE },
864 },
865 Charge {
866 origin: ALICE,
867 contract: BOB,
868 amount: Deposit::Charge(12),
869 state: ContractState::Alive,
870 },
871 ],
872 },
873 },
874 ChargingTestCase {
875 origin: Origin::<Test>::Root,
876 deposit: Deposit::Charge(0),
877 expected: TestExt { limit_checks: vec![], charges: vec![] },
878 },
879 ];
880
881 for test_case in test_cases {
882 clear_ext();
883
884 let mut meter = TestMeter::new(&test_case.origin, Some(1_000), 0).unwrap();
885 assert_eq!(meter.available(), 1_000);
886
887 let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
888 nested0.charge(&Diff {
889 bytes_added: 5,
890 bytes_removed: 1,
891 items_added: 3,
892 items_removed: 1,
893 });
894 nested0.charge(&Diff { items_added: 2, ..Default::default() });
895
896 let mut nested1_info = new_info(StorageInfo {
897 bytes: 100,
898 items: 10,
899 bytes_deposit: 100,
900 items_deposit: 20,
901 });
902 let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
903 nested1.charge(&Diff { items_removed: 5, ..Default::default() });
904 nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
905 nested1.terminate(&nested1_info, CHARLIE);
906 nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
907 nested0.absorb(nested1, &CHARLIE, None);
908
909 meter.absorb(nested0, &BOB, None);
910 assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
911 assert_eq!(TestExtTestValue::get(), test_case.expected)
912 }
913 }
914}