referrerpolicy=no-referrer-when-downgrade

pallet_revive/storage/
meter.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! This module contains functions to meter the storage deposit.
19
20use crate::{
21	storage::ContractInfo, AccountIdOf, BalanceOf, Config, Error, HoldReason, Inspect, Origin,
22	StorageDeposit as Deposit, System, LOG_TARGET,
23};
24use alloc::vec::Vec;
25use core::{fmt::Debug, marker::PhantomData};
26use frame_support::{
27	traits::{
28		fungible::{Mutate, MutateHold},
29		tokens::{Fortitude, Fortitude::Polite, Precision, Preservation, Restriction},
30		Get,
31	},
32	DefaultNoBound, RuntimeDebugNoBound,
33};
34use sp_runtime::{
35	traits::{Saturating, Zero},
36	DispatchError, DispatchResult, FixedPointNumber, FixedU128,
37};
38
39/// Deposit that uses the native fungible's balance type.
40pub type DepositOf<T> = Deposit<BalanceOf<T>>;
41
42/// A production root storage meter that actually charges from its origin.
43pub type Meter<T> = RawMeter<T, ReservingExt, Root>;
44
45/// A production nested storage meter that actually charges from its origin.
46pub type NestedMeter<T> = RawMeter<T, ReservingExt, Nested>;
47
48/// A production storage meter that actually charges from its origin.
49///
50/// This can be used where we want to be generic over the state (Root vs. Nested).
51pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
52
53/// A trait that allows to decouple the metering from the charging of balance.
54///
55/// This mostly exists for testing so that the charging can be mocked.
56pub trait Ext<T: Config> {
57	/// This is called to inform the implementer that some balance should be charged due to
58	/// some interaction of the `origin` with a `contract`.
59	///
60	/// The balance transfer can either flow from `origin` to `contract` or the other way
61	/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
62	/// It will fail in case the `origin` has not enough balance to cover all storage deposits.
63	fn charge(
64		origin: &T::AccountId,
65		contract: &T::AccountId,
66		amount: &DepositOf<T>,
67		state: &ContractState<T>,
68	) -> Result<(), DispatchError>;
69}
70
71/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged.
72///
73/// It uses [`frame_support::traits::fungible::Mutate`] in order to do accomplish the reserves.
74pub enum ReservingExt {}
75
76/// Used to implement a type state pattern for the meter.
77///
78/// It is sealed and cannot be implemented outside of this module.
79pub trait State: private::Sealed {}
80
81/// State parameter that constitutes a meter that is in its root state.
82#[derive(Default, Debug)]
83pub struct Root;
84
85/// State parameter that constitutes a meter that is in its nested state.
86/// Its value indicates whether the nested meter has its own limit.
87#[derive(Default, Debug)]
88pub struct Nested;
89
90impl State for Root {}
91impl State for Nested {}
92
93/// A type that allows the metering of consumed or freed storage of a single contract call stack.
94#[derive(DefaultNoBound, RuntimeDebugNoBound)]
95pub struct RawMeter<T: Config, E, S: State + Default + Debug> {
96	/// The limit of how much balance this meter is allowed to consume.
97	limit: BalanceOf<T>,
98	/// The amount of balance that was used in this meter and all of its already absorbed children.
99	total_deposit: DepositOf<T>,
100	/// The amount of storage changes that were recorded in this meter alone.
101	own_contribution: Contribution<T>,
102	/// List of charges that should be applied at the end of a contract stack execution.
103	///
104	/// We only have one charge per contract hence the size of this vector is
105	/// limited by the maximum call depth.
106	charges: Vec<Charge<T>>,
107	/// True if this is the root meter.
108	///
109	/// Sometimes we cannot know at compile time.
110	is_root: bool,
111	/// Type parameter only used in impls.
112	_phantom: PhantomData<(E, S)>,
113}
114
115/// This type is used to describe a storage change when charging from the meter.
116#[derive(Default, RuntimeDebugNoBound)]
117pub struct Diff {
118	/// How many bytes were added to storage.
119	pub bytes_added: u32,
120	/// How many bytes were removed from storage.
121	pub bytes_removed: u32,
122	/// How many storage items were added to storage.
123	pub items_added: u32,
124	/// How many storage items were removed from storage.
125	pub items_removed: u32,
126}
127
128impl Diff {
129	/// Calculate how much of a charge or refund results from applying the diff and store it
130	/// in the passed `info` if any.
131	///
132	/// # Note
133	///
134	/// In case `None` is passed for `info` only charges are calculated. This is because refunds
135	/// are calculated pro rata of the existing storage within a contract and hence need extract
136	/// this information from the passed `info`.
137	pub fn update_contract<T: Config>(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
138		let per_byte = T::DepositPerByte::get();
139		let per_item = T::DepositPerItem::get();
140		let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed);
141		let items_added = self.items_added.saturating_sub(self.items_removed);
142		let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into()));
143		let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into()));
144
145		// Without any contract info we can only calculate diffs which add storage
146		let info = if let Some(info) = info {
147			info
148		} else {
149			debug_assert_eq!(self.bytes_removed, 0);
150			debug_assert_eq!(self.items_removed, 0);
151			return bytes_deposit.saturating_add(&items_deposit)
152		};
153
154		// Refunds are calculated pro rata based on the accumulated storage within the contract
155		let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added);
156		let items_removed = self.items_removed.saturating_sub(self.items_added);
157		let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes)
158			.unwrap_or_default()
159			.min(FixedU128::from_u32(1));
160		bytes_deposit = bytes_deposit
161			.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit)));
162		let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items)
163			.unwrap_or_default()
164			.min(FixedU128::from_u32(1));
165		items_deposit = items_deposit
166			.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit)));
167
168		// We need to update the contract info structure with the new deposits
169		info.storage_bytes =
170			info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed);
171		info.storage_items =
172			info.storage_items.saturating_add(items_added).saturating_sub(items_removed);
173		match &bytes_deposit {
174			Deposit::Charge(amount) =>
175				info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount),
176			Deposit::Refund(amount) =>
177				info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount),
178		}
179		match &items_deposit {
180			Deposit::Charge(amount) =>
181				info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount),
182			Deposit::Refund(amount) =>
183				info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount),
184		}
185
186		bytes_deposit.saturating_add(&items_deposit)
187	}
188}
189
190impl Diff {
191	fn saturating_add(&self, rhs: &Self) -> Self {
192		Self {
193			bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
194			bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
195			items_added: self.items_added.saturating_add(rhs.items_added),
196			items_removed: self.items_removed.saturating_add(rhs.items_removed),
197		}
198	}
199}
200
201/// The state of a contract.
202///
203/// In case of termination the beneficiary is indicated.
204#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
205pub enum ContractState<T: Config> {
206	Alive,
207	Terminated { beneficiary: AccountIdOf<T> },
208}
209
210/// Records information to charge or refund a plain account.
211///
212/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
213/// this we can do all the refunds before doing any charge. This way a plain account can use
214/// more deposit than it has balance as along as it is covered by a refund. This
215/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
216/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract
217/// call. In that case the limit is enforced once the call is returned, rolling it back if
218/// exhausted.
219#[derive(RuntimeDebugNoBound, Clone)]
220struct Charge<T: Config> {
221	contract: T::AccountId,
222	amount: DepositOf<T>,
223	state: ContractState<T>,
224}
225
226/// Records the storage changes of a storage meter.
227#[derive(RuntimeDebugNoBound)]
228enum Contribution<T: Config> {
229	/// The contract the meter belongs to is alive and accumulates changes using a [`Diff`].
230	Alive(Diff),
231	/// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of
232	/// its execution. In this process the [`Diff`] was converted into a [`Deposit`].
233	Checked(DepositOf<T>),
234	/// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`]
235	/// in order to calculate the refund. Upon termination the `reducible_balance` in the
236	/// contract's account is transferred to the [`beneficiary`].
237	Terminated { deposit: DepositOf<T>, beneficiary: AccountIdOf<T> },
238}
239
240impl<T: Config> Contribution<T> {
241	/// See [`Diff::update_contract`].
242	fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
243		match self {
244			Self::Alive(diff) => diff.update_contract::<T>(info),
245			Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) =>
246				deposit.clone(),
247		}
248	}
249}
250
251impl<T: Config> Default for Contribution<T> {
252	fn default() -> Self {
253		Self::Alive(Default::default())
254	}
255}
256
257/// Functions that apply to all states.
258impl<T, E, S> RawMeter<T, E, S>
259where
260	T: Config,
261	E: Ext<T>,
262	S: State + Default + Debug,
263{
264	/// Create a new child that has its `limit`.
265	///
266	/// This is called whenever a new subcall is initiated in order to track the storage
267	/// usage for this sub call separately. This is necessary because we want to exchange balance
268	/// with the current contract we are interacting with.
269	pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
270		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
271
272		RawMeter { limit: self.available().min(limit), ..Default::default() }
273	}
274
275	/// Absorb a child that was spawned to handle a sub call.
276	///
277	/// This should be called whenever a sub call comes to its end and it is **not** reverted.
278	/// This does the actual balance transfer from/to `origin` and `contract` based on the
279	/// overall storage consumption of the call. It also updates the supplied contract info.
280	///
281	/// In case a contract reverted the child meter should just be dropped in order to revert
282	/// any changes it recorded.
283	///
284	/// # Parameters
285	///
286	/// - `absorbed`: The child storage meter that should be absorbed.
287	/// - `origin`: The origin that spawned the original root meter.
288	/// - `contract`: The contract's account that this sub call belongs to.
289	/// - `info`: The info of the contract in question. `None` if the contract was terminated.
290	pub fn absorb(
291		&mut self,
292		absorbed: RawMeter<T, E, Nested>,
293		contract: &T::AccountId,
294		info: Option<&mut ContractInfo<T>>,
295	) {
296		let own_deposit = absorbed.own_contribution.update_contract(info);
297		self.total_deposit = self
298			.total_deposit
299			.saturating_add(&absorbed.total_deposit)
300			.saturating_add(&own_deposit);
301		self.charges.extend_from_slice(&absorbed.charges);
302		if !own_deposit.is_zero() {
303			self.charges.push(Charge {
304				contract: contract.clone(),
305				amount: own_deposit,
306				state: absorbed.contract_state(),
307			});
308		}
309	}
310
311	/// Record a charge that has taken place externally.
312	///
313	/// This will not perform a charge. It just records it to reflect it in the
314	/// total amount of storage required for a transaction.
315	pub fn record_charge(&mut self, amount: &DepositOf<T>) -> DispatchResult {
316		let total_deposit = self.total_deposit.saturating_add(&amount);
317
318		// Limits are enforced at the end of each frame. But plain balance transfers
319		// do not sapwn a frame. This is specifically to enforce the limit for those.
320		if self.is_root && total_deposit.charge_or_zero() > self.limit {
321			log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit);
322			return Err(<Error<T>>::StorageDepositLimitExhausted.into())
323		}
324
325		self.total_deposit = total_deposit;
326		Ok(())
327	}
328
329	/// The amount of balance that is still available from the original `limit`.
330	fn available(&self) -> BalanceOf<T> {
331		self.total_deposit.available(&self.limit)
332	}
333
334	/// Returns the state of the currently executed contract.
335	fn contract_state(&self) -> ContractState<T> {
336		match &self.own_contribution {
337			Contribution::Terminated { deposit: _, beneficiary } =>
338				ContractState::Terminated { beneficiary: beneficiary.clone() },
339			_ => ContractState::Alive,
340		}
341	}
342}
343
344/// Functions that only apply to the root state.
345impl<T, E> RawMeter<T, E, Root>
346where
347	T: Config,
348	E: Ext<T>,
349{
350	/// Create new storage limiting storage deposits to the passed `limit`.
351	///
352	/// If the limit larger then what the origin can afford we will just fail
353	/// when collecting the deposits in `try_into_deposit`.
354	pub fn new(limit: BalanceOf<T>) -> Self {
355		Self { limit, is_root: true, ..Default::default() }
356	}
357
358	/// The total amount of deposit that should change hands as result of the execution
359	/// that this meter was passed into. This will also perform all the charges accumulated
360	/// in the whole contract stack.
361	///
362	/// This drops the root meter in order to make sure it is only called when the whole
363	/// execution did finish.
364	pub fn try_into_deposit(
365		self,
366		origin: &Origin<T>,
367		skip_transfer: bool,
368	) -> Result<DepositOf<T>, DispatchError> {
369		if !skip_transfer {
370			// Only refund or charge deposit if the origin is not root.
371			let origin = match origin {
372				Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
373				Origin::Signed(o) => o,
374			};
375			let try_charge = || {
376				for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_)))
377				{
378					E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
379				}
380				for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_)))
381				{
382					E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
383				}
384				Ok(())
385			};
386			try_charge().map_err(|_: DispatchError| <Error<T>>::StorageDepositNotEnoughFunds)?;
387		}
388
389		Ok(self.total_deposit)
390	}
391}
392
393/// Functions that only apply to the nested state.
394impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> {
395	/// Charges `diff` from the meter.
396	pub fn charge(&mut self, diff: &Diff) {
397		match &mut self.own_contribution {
398			Contribution::Alive(own) => *own = own.saturating_add(diff),
399			_ => panic!("Charge is never called after termination; qed"),
400		};
401	}
402
403	/// Adds a charge without recording it in the contract info.
404	///
405	/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
406	/// change within the contract's child trie. This is the case when when the `code_hash` is
407	/// updated. [`Self::charge`] cannot be used here because we keep track of the deposit charge
408	/// separately from the storage charge.
409	///
410	/// If this functions is used the amount of the charge has to be stored by the caller somewhere
411	/// alese in order to be able to refund it.
412	pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
413		// will not fail in a nested meter
414		self.record_charge(&amount).ok();
415		self.charges.push(Charge { contract, amount, state: ContractState::Alive });
416	}
417
418	/// Call to tell the meter that the currently executing contract was terminated.
419	///
420	/// This will manipulate the meter so that all storage deposit accumulated in
421	/// `contract_info` will be refunded to the `origin` of the meter. And the free
422	/// (`reducible_balance`) will be sent to the `beneficiary`.
423	pub fn terminate(&mut self, info: &ContractInfo<T>, beneficiary: T::AccountId) {
424		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
425		self.own_contribution = Contribution::Terminated {
426			deposit: Deposit::Refund(info.total_deposit()),
427			beneficiary,
428		};
429	}
430
431	/// [`Self::charge`] does not enforce the storage limit since we want to do this check as late
432	/// as possible to allow later refunds to offset earlier charges.
433	pub fn enforce_limit(
434		&mut self,
435		info: Option<&mut ContractInfo<T>>,
436	) -> Result<(), DispatchError> {
437		let deposit = self.own_contribution.update_contract(info);
438		let total_deposit = self.total_deposit.saturating_add(&deposit);
439		// We don't want to override a `Terminated` with a `Checked`.
440		if matches!(self.contract_state(), ContractState::Alive) {
441			self.own_contribution = Contribution::Checked(deposit);
442		}
443		if let Deposit::Charge(amount) = total_deposit {
444			if amount > self.limit {
445				log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit);
446				return Err(<Error<T>>::StorageDepositLimitExhausted.into())
447			}
448		}
449		Ok(())
450	}
451}
452
453impl<T: Config> Ext<T> for ReservingExt {
454	fn charge(
455		origin: &T::AccountId,
456		contract: &T::AccountId,
457		amount: &DepositOf<T>,
458		state: &ContractState<T>,
459	) -> Result<(), DispatchError> {
460		match amount {
461			Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()),
462			Deposit::Charge(amount) => {
463				T::Currency::transfer_and_hold(
464					&HoldReason::StorageDepositReserve.into(),
465					origin,
466					contract,
467					*amount,
468					Precision::Exact,
469					Preservation::Preserve,
470					Fortitude::Polite,
471				)?;
472			},
473			Deposit::Refund(amount) => {
474				let transferred = T::Currency::transfer_on_hold(
475					&HoldReason::StorageDepositReserve.into(),
476					contract,
477					origin,
478					*amount,
479					Precision::BestEffort,
480					Restriction::Free,
481					Fortitude::Polite,
482				)?;
483
484				if transferred < *amount {
485					// This should never happen, if it does it means that there is a bug in the
486					// runtime logic. In the rare case this happens we try to refund as much as we
487					// can, thus the `Precision::BestEffort`.
488					log::error!(
489						target: LOG_TARGET,
490						"Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.",
491						amount, contract, origin, transferred,
492					);
493				}
494			},
495		}
496		if let ContractState::<T>::Terminated { beneficiary } = state {
497			System::<T>::dec_consumers(&contract);
498			// Whatever is left in the contract is sent to the termination beneficiary.
499			T::Currency::transfer(
500				&contract,
501				&beneficiary,
502				T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite),
503				Preservation::Expendable,
504			)?;
505		}
506		Ok(())
507	}
508}
509
510mod private {
511	pub trait Sealed {}
512	impl Sealed for super::Root {}
513	impl Sealed for super::Nested {}
514}
515
516#[cfg(test)]
517mod tests {
518	use super::*;
519	use crate::{exec::AccountIdOf, test_utils::*, tests::Test};
520	use frame_support::parameter_types;
521	use pretty_assertions::assert_eq;
522
523	type TestMeter = RawMeter<Test, TestExt, Root>;
524
525	parameter_types! {
526		static TestExtTestValue: TestExt = Default::default();
527	}
528
529	#[derive(Debug, PartialEq, Eq, Clone)]
530	struct Charge {
531		origin: AccountIdOf<Test>,
532		contract: AccountIdOf<Test>,
533		amount: DepositOf<Test>,
534		state: ContractState<Test>,
535	}
536
537	#[derive(Default, Debug, PartialEq, Eq, Clone)]
538	pub struct TestExt {
539		charges: Vec<Charge>,
540	}
541
542	impl TestExt {
543		fn clear(&mut self) {
544			self.charges.clear();
545		}
546	}
547
548	impl Ext<Test> for TestExt {
549		fn charge(
550			origin: &AccountIdOf<Test>,
551			contract: &AccountIdOf<Test>,
552			amount: &DepositOf<Test>,
553			state: &ContractState<Test>,
554		) -> Result<(), DispatchError> {
555			TestExtTestValue::mutate(|ext| {
556				ext.charges.push(Charge {
557					origin: origin.clone(),
558					contract: contract.clone(),
559					amount: amount.clone(),
560					state: state.clone(),
561				})
562			});
563			Ok(())
564		}
565	}
566
567	fn clear_ext() {
568		TestExtTestValue::mutate(|ext| ext.clear())
569	}
570
571	struct ChargingTestCase {
572		origin: Origin<Test>,
573		deposit: DepositOf<Test>,
574		expected: TestExt,
575	}
576
577	#[derive(Default)]
578	struct StorageInfo {
579		bytes: u32,
580		items: u32,
581		bytes_deposit: BalanceOf<Test>,
582		items_deposit: BalanceOf<Test>,
583		immutable_data_len: u32,
584	}
585
586	fn new_info(info: StorageInfo) -> ContractInfo<Test> {
587		ContractInfo::<Test> {
588			trie_id: Default::default(),
589			code_hash: Default::default(),
590			storage_bytes: info.bytes,
591			storage_items: info.items,
592			storage_byte_deposit: info.bytes_deposit,
593			storage_item_deposit: info.items_deposit,
594			storage_base_deposit: Default::default(),
595			immutable_data_len: info.immutable_data_len,
596		}
597	}
598
599	#[test]
600	fn new_reserves_balance_works() {
601		clear_ext();
602
603		TestMeter::new(1_000);
604
605		assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() })
606	}
607
608	/// Previously, passing a limit of 0 meant unlimited storage for a nested call.
609	///
610	/// Now, a limit of 0 means the subcall will not be able to use any storage.
611	#[test]
612	fn nested_zero_limit_requested() {
613		clear_ext();
614
615		let meter = TestMeter::new(1_000);
616		assert_eq!(meter.available(), 1_000);
617		let nested0 = meter.nested(BalanceOf::<Test>::zero());
618		assert_eq!(nested0.available(), 0);
619	}
620
621	#[test]
622	fn nested_some_limit_requested() {
623		clear_ext();
624
625		let meter = TestMeter::new(1_000);
626		assert_eq!(meter.available(), 1_000);
627		let nested0 = meter.nested(500);
628		assert_eq!(nested0.available(), 500);
629	}
630
631	#[test]
632	fn nested_all_limit_requested() {
633		clear_ext();
634
635		let meter = TestMeter::new(1_000);
636		assert_eq!(meter.available(), 1_000);
637		let nested0 = meter.nested(1_000);
638		assert_eq!(nested0.available(), 1_000);
639	}
640
641	#[test]
642	fn nested_over_limit_requested() {
643		clear_ext();
644
645		let meter = TestMeter::new(1_000);
646		assert_eq!(meter.available(), 1_000);
647		let nested0 = meter.nested(2_000);
648		assert_eq!(nested0.available(), 1_000);
649	}
650
651	#[test]
652	fn empty_charge_works() {
653		clear_ext();
654
655		let mut meter = TestMeter::new(1_000);
656		assert_eq!(meter.available(), 1_000);
657
658		// an empty charge does not create a `Charge` entry
659		let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
660		nested0.charge(&Default::default());
661		meter.absorb(nested0, &BOB, None);
662
663		assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() })
664	}
665
666	#[test]
667	fn charging_works() {
668		let test_cases = vec![
669			ChargingTestCase {
670				origin: Origin::<Test>::from_account_id(ALICE),
671				deposit: Deposit::Refund(28),
672				expected: TestExt {
673					charges: vec![
674						Charge {
675							origin: ALICE,
676							contract: CHARLIE,
677							amount: Deposit::Refund(10),
678							state: ContractState::Alive,
679						},
680						Charge {
681							origin: ALICE,
682							contract: CHARLIE,
683							amount: Deposit::Refund(20),
684							state: ContractState::Alive,
685						},
686						Charge {
687							origin: ALICE,
688							contract: BOB,
689							amount: Deposit::Charge(2),
690							state: ContractState::Alive,
691						},
692					],
693				},
694			},
695			ChargingTestCase {
696				origin: Origin::<Test>::Root,
697				deposit: Deposit::Charge(0),
698				expected: TestExt { charges: vec![] },
699			},
700		];
701
702		for test_case in test_cases {
703			clear_ext();
704
705			let mut meter = TestMeter::new(100);
706			assert_eq!(meter.available(), 100);
707
708			let mut nested0_info = new_info(StorageInfo {
709				bytes: 100,
710				items: 5,
711				bytes_deposit: 100,
712				items_deposit: 10,
713				immutable_data_len: 0,
714			});
715			let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
716			nested0.charge(&Diff {
717				bytes_added: 108,
718				bytes_removed: 5,
719				items_added: 1,
720				items_removed: 2,
721			});
722			nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
723
724			let mut nested1_info = new_info(StorageInfo {
725				bytes: 100,
726				items: 10,
727				bytes_deposit: 100,
728				items_deposit: 20,
729				immutable_data_len: 0,
730			});
731			let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
732			nested1.charge(&Diff { items_removed: 5, ..Default::default() });
733			nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info));
734
735			let mut nested2_info = new_info(StorageInfo {
736				bytes: 100,
737				items: 7,
738				bytes_deposit: 100,
739				items_deposit: 20,
740				immutable_data_len: 0,
741			});
742			let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
743			nested2.charge(&Diff { items_removed: 7, ..Default::default() });
744			nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info));
745
746			nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
747			meter.absorb(nested0, &BOB, Some(&mut nested0_info));
748
749			assert_eq!(
750				meter.try_into_deposit(&test_case.origin, false).unwrap(),
751				test_case.deposit
752			);
753
754			assert_eq!(nested0_info.extra_deposit(), 112);
755			assert_eq!(nested1_info.extra_deposit(), 110);
756			assert_eq!(nested2_info.extra_deposit(), 100);
757
758			assert_eq!(TestExtTestValue::get(), test_case.expected)
759		}
760	}
761
762	#[test]
763	fn termination_works() {
764		let test_cases = vec![
765			ChargingTestCase {
766				origin: Origin::<Test>::from_account_id(ALICE),
767				deposit: Deposit::Refund(108),
768				expected: TestExt {
769					charges: vec![
770						Charge {
771							origin: ALICE,
772							contract: CHARLIE,
773							amount: Deposit::Refund(120),
774							state: ContractState::Terminated { beneficiary: CHARLIE },
775						},
776						Charge {
777							origin: ALICE,
778							contract: BOB,
779							amount: Deposit::Charge(12),
780							state: ContractState::Alive,
781						},
782					],
783				},
784			},
785			ChargingTestCase {
786				origin: Origin::<Test>::Root,
787				deposit: Deposit::Charge(0),
788				expected: TestExt { charges: vec![] },
789			},
790		];
791
792		for test_case in test_cases {
793			clear_ext();
794
795			let mut meter = TestMeter::new(1_000);
796			assert_eq!(meter.available(), 1_000);
797
798			let mut nested0 = meter.nested(BalanceOf::<Test>::max_value());
799			nested0.charge(&Diff {
800				bytes_added: 5,
801				bytes_removed: 1,
802				items_added: 3,
803				items_removed: 1,
804			});
805			nested0.charge(&Diff { items_added: 2, ..Default::default() });
806
807			let mut nested1_info = new_info(StorageInfo {
808				bytes: 100,
809				items: 10,
810				bytes_deposit: 100,
811				items_deposit: 20,
812				immutable_data_len: 0,
813			});
814			let mut nested1 = nested0.nested(BalanceOf::<Test>::max_value());
815			nested1.charge(&Diff { items_removed: 5, ..Default::default() });
816			nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
817			nested1.terminate(&nested1_info, CHARLIE);
818			nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
819			nested0.absorb(nested1, &CHARLIE, None);
820
821			meter.absorb(nested0, &BOB, None);
822			assert_eq!(
823				meter.try_into_deposit(&test_case.origin, false).unwrap(),
824				test_case.deposit
825			);
826			assert_eq!(TestExtTestValue::get(), test_case.expected)
827		}
828	}
829}