referrerpolicy=no-referrer-when-downgrade

pallet_contracts/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, 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	DefaultNoBound, RuntimeDebugNoBound,
37};
38use sp_runtime::{
39	traits::{Saturating, Zero},
40	DispatchError, FixedPointNumber, FixedU128,
41};
42
43/// Deposit that uses the native fungible's balance type.
44pub type DepositOf<T> = Deposit<BalanceOf<T>>;
45
46/// A production root storage meter that actually charges from its origin.
47pub type Meter<T> = RawMeter<T, ReservingExt, Root>;
48
49/// A production nested storage meter that actually charges from its origin.
50pub type NestedMeter<T> = RawMeter<T, ReservingExt, Nested>;
51
52/// A production storage meter that actually charges from its origin.
53///
54/// This can be used where we want to be generic over the state (Root vs. Nested).
55pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
56
57/// A trait that allows to decouple the metering from the charging of balance.
58///
59/// This mostly exists for testing so that the charging can be mocked.
60pub trait Ext<T: Config> {
61	/// This checks whether `origin` is able to afford the storage deposit limit.
62	///
63	/// It is necessary to do this check beforehand so that the charge won't fail later on.
64	///
65	/// `origin`: The origin of the call stack from which is responsible for putting down a deposit.
66	/// `limit`: The limit with which the meter was constructed.
67	/// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should
68	/// be left inside the `origin` account.
69	///
70	/// Returns the limit that should be used by the meter. If origin can't afford the `limit`
71	/// it returns `Err`.
72	fn check_limit(
73		origin: &T::AccountId,
74		limit: Option<BalanceOf<T>>,
75		min_leftover: BalanceOf<T>,
76	) -> Result<BalanceOf<T>, DispatchError>;
77	/// This is called to inform the implementer that some balance should be charged due to
78	/// some interaction of the `origin` with a `contract`.
79	///
80	/// The balance transfer can either flow from `origin` to `contract` or the other way
81	/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
82	/// It should be used in combination with `check_limit` to check that no more balance than this
83	/// limit is ever charged.
84	fn charge(
85		origin: &T::AccountId,
86		contract: &T::AccountId,
87		amount: &DepositOf<T>,
88		state: &ContractState<T>,
89	) -> Result<(), DispatchError>;
90}
91
92/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged.
93///
94/// It uses [`frame_support::traits::fungible::Mutate`] in order to do accomplish the reserves.
95pub enum ReservingExt {}
96
97/// Used to implement a type state pattern for the meter.
98///
99/// It is sealed and cannot be implemented outside of this module.
100pub trait State: private::Sealed {}
101
102/// State parameter that constitutes a meter that is in its root state.
103#[derive(Default, Debug)]
104pub struct Root;
105
106/// State parameter that constitutes a meter that is in its nested state.
107/// Its value indicates whether the nested meter has its own limit.
108#[derive(DefaultNoBound, RuntimeDebugNoBound)]
109pub enum Nested {
110	#[default]
111	DerivedLimit,
112	OwnLimit,
113}
114
115impl State for Root {}
116impl State for Nested {}
117
118/// A type that allows the metering of consumed or freed storage of a single contract call stack.
119#[derive(DefaultNoBound, RuntimeDebugNoBound)]
120pub struct RawMeter<T: Config, E, S: State + Default + Debug> {
121	/// The limit of how much balance this meter is allowed to consume.
122	limit: BalanceOf<T>,
123	/// The amount of balance that was used in this meter and all of its already absorbed children.
124	total_deposit: DepositOf<T>,
125	/// The amount of storage changes that were recorded in this meter alone.
126	own_contribution: Contribution<T>,
127	/// List of charges that should be applied at the end of a contract stack execution.
128	///
129	/// We only have one charge per contract hence the size of this vector is
130	/// limited by the maximum call depth.
131	charges: Vec<Charge<T>>,
132	/// We store the nested state to determine if it has a special limit for sub-call.
133	nested: S,
134	/// Type parameter only used in impls.
135	_phantom: PhantomData<E>,
136}
137
138/// This type is used to describe a storage change when charging from the meter.
139#[derive(Default, RuntimeDebugNoBound)]
140pub struct Diff {
141	/// How many bytes were added to storage.
142	pub bytes_added: u32,
143	/// How many bytes were removed from storage.
144	pub bytes_removed: u32,
145	/// How many storage items were added to storage.
146	pub items_added: u32,
147	/// How many storage items were removed from storage.
148	pub items_removed: u32,
149}
150
151impl Diff {
152	/// Calculate how much of a charge or refund results from applying the diff and store it
153	/// in the passed `info` if any.
154	///
155	/// # Note
156	///
157	/// In case `None` is passed for `info` only charges are calculated. This is because refunds
158	/// are calculated pro rata of the existing storage within a contract and hence need extract
159	/// this information from the passed `info`.
160	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		// Without any contract info we can only calculate diffs which add storage
169		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		// Refunds are calculated pro rata based on the accumulated storage within the contract
178		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		// We need to update the contract info structure with the new deposits
192		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			Deposit::Refund(amount) =>
200				info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount),
201		}
202		match &items_deposit {
203			Deposit::Charge(amount) =>
204				info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount),
205			Deposit::Refund(amount) =>
206				info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount),
207		}
208
209		bytes_deposit.saturating_add(&items_deposit)
210	}
211}
212
213impl Diff {
214	fn saturating_add(&self, rhs: &Self) -> Self {
215		Self {
216			bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
217			bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
218			items_added: self.items_added.saturating_add(rhs.items_added),
219			items_removed: self.items_removed.saturating_add(rhs.items_removed),
220		}
221	}
222}
223
224/// The state of a contract.
225///
226/// In case of termination the beneficiary is indicated.
227#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
228pub enum ContractState<T: Config> {
229	Alive,
230	Terminated { beneficiary: AccountIdOf<T> },
231}
232
233/// Records information to charge or refund a plain account.
234///
235/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
236/// this we can do all the refunds before doing any charge. This way a plain account can use
237/// more deposit than it has balance as along as it is covered by a refund. This
238/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
239/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract
240/// call. In that case the limit is enforced once the call is returned, rolling it back if
241/// exhausted.
242#[derive(RuntimeDebugNoBound, Clone)]
243struct Charge<T: Config> {
244	contract: T::AccountId,
245	amount: DepositOf<T>,
246	state: ContractState<T>,
247}
248
249/// Records the storage changes of a storage meter.
250#[derive(RuntimeDebugNoBound)]
251enum Contribution<T: Config> {
252	/// The contract the meter belongs to is alive and accumulates changes using a [`Diff`].
253	Alive(Diff),
254	/// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of
255	/// its execution. In this process the [`Diff`] was converted into a [`Deposit`].
256	Checked(DepositOf<T>),
257	/// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`]
258	/// in order to calculate the refund. Upon termination the `reducible_balance` in the
259	/// contract's account is transferred to the [`beneficiary`].
260	Terminated { deposit: DepositOf<T>, beneficiary: AccountIdOf<T> },
261}
262
263impl<T: Config> Contribution<T> {
264	/// See [`Diff::update_contract`].
265	fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
266		match self {
267			Self::Alive(diff) => diff.update_contract::<T>(info),
268			Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) =>
269				deposit.clone(),
270		}
271	}
272}
273
274impl<T: Config> Default for Contribution<T> {
275	fn default() -> Self {
276		Self::Alive(Default::default())
277	}
278}
279
280/// Functions that apply to all states.
281impl<T, E, S> RawMeter<T, E, S>
282where
283	T: Config,
284	E: Ext<T>,
285	S: State + Default + Debug,
286{
287	/// Create a new child that has its `limit`.
288	/// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent.
289	///
290	/// This is called whenever a new subcall is initiated in order to track the storage
291	/// usage for this sub call separately. This is necessary because we want to exchange balance
292	/// with the current contract we are interacting with.
293	pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
294		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
295		// If a special limit is specified higher than it is available,
296		// we want to enforce the lesser limit to the nested meter, to fail in the sub-call.
297		let limit = self.available().min(limit);
298		if limit.is_zero() {
299			RawMeter { limit: self.available(), ..Default::default() }
300		} else {
301			RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() }
302		}
303	}
304
305	/// Absorb a child that was spawned to handle a sub call.
306	///
307	/// This should be called whenever a sub call comes to its end and it is **not** reverted.
308	/// This does the actual balance transfer from/to `origin` and `contract` based on the
309	/// overall storage consumption of the call. It also updates the supplied contract info.
310	///
311	/// In case a contract reverted the child meter should just be dropped in order to revert
312	/// any changes it recorded.
313	///
314	/// # Parameters
315	///
316	/// - `absorbed`: The child storage meter that should be absorbed.
317	/// - `origin`: The origin that spawned the original root meter.
318	/// - `contract`: The contract's account that this sub call belongs to.
319	/// - `info`: The info of the contract in question. `None` if the contract was terminated.
320	pub fn absorb(
321		&mut self,
322		absorbed: RawMeter<T, E, Nested>,
323		contract: &T::AccountId,
324		info: Option<&mut ContractInfo<T>>,
325	) {
326		let own_deposit = absorbed.own_contribution.update_contract(info);
327		self.total_deposit = self
328			.total_deposit
329			.saturating_add(&absorbed.total_deposit)
330			.saturating_add(&own_deposit);
331		self.charges.extend_from_slice(&absorbed.charges);
332		if !own_deposit.is_zero() {
333			self.charges.push(Charge {
334				contract: contract.clone(),
335				amount: own_deposit,
336				state: absorbed.contract_state(),
337			});
338		}
339	}
340
341	/// The amount of balance that is still available from the original `limit`.
342	fn available(&self) -> BalanceOf<T> {
343		self.total_deposit.available(&self.limit)
344	}
345
346	/// Returns the state of the currently executed contract.
347	fn contract_state(&self) -> ContractState<T> {
348		match &self.own_contribution {
349			Contribution::Terminated { deposit: _, beneficiary } =>
350				ContractState::Terminated { beneficiary: beneficiary.clone() },
351			_ => ContractState::Alive,
352		}
353	}
354}
355
356/// Functions that only apply to the root state.
357impl<T, E> RawMeter<T, E, Root>
358where
359	T: Config,
360	E: Ext<T>,
361{
362	/// Create new storage meter for the specified `origin` and `limit`.
363	///
364	/// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible.
365	pub fn new(
366		origin: &Origin<T>,
367		limit: Option<BalanceOf<T>>,
368		min_leftover: BalanceOf<T>,
369	) -> Result<Self, DispatchError> {
370		// Check the limit only if the origin is not root.
371		return match origin {
372			Origin::Root => Ok(Self {
373				limit: limit.unwrap_or(T::DefaultDepositLimit::get()),
374				..Default::default()
375			}),
376			Origin::Signed(o) => {
377				let limit = E::check_limit(o, limit, min_leftover)?;
378				Ok(Self { limit, ..Default::default() })
379			},
380		}
381	}
382
383	/// The total amount of deposit that should change hands as result of the execution
384	/// that this meter was passed into. This will also perform all the charges accumulated
385	/// in the whole contract stack.
386	///
387	/// This drops the root meter in order to make sure it is only called when the whole
388	/// execution did finish.
389	pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
390		// Only refund or charge deposit if the origin is not root.
391		let origin = match origin {
392			Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
393			Origin::Signed(o) => o,
394		};
395		for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
396			E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
397		}
398		for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
399			E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
400		}
401		Ok(self.total_deposit)
402	}
403}
404
405/// Functions that only apply to the nested state.
406impl<T, E> RawMeter<T, E, Nested>
407where
408	T: Config,
409	E: Ext<T>,
410{
411	/// Charges `diff` from the meter.
412	pub fn charge(&mut self, diff: &Diff) {
413		match &mut self.own_contribution {
414			Contribution::Alive(own) => *own = own.saturating_add(diff),
415			_ => panic!("Charge is never called after termination; qed"),
416		};
417	}
418
419	/// Adds a deposit charge.
420	///
421	/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
422	/// change. This is the case when a `delegate_dependency` is added or removed, or when the
423	/// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the
424	/// deposit charge separately from the storage charge.
425	pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
426		self.total_deposit = self.total_deposit.saturating_add(&amount);
427		self.charges.push(Charge { contract, amount, state: ContractState::Alive });
428	}
429
430	/// Charges from `origin` a storage deposit for contract instantiation.
431	///
432	/// This immediately transfers the balance in order to create the account.
433	pub fn charge_instantiate(
434		&mut self,
435		origin: &T::AccountId,
436		contract: &T::AccountId,
437		contract_info: &mut ContractInfo<T>,
438		code_info: &CodeInfo<T>,
439	) -> Result<(), DispatchError> {
440		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
441
442		// We need to make sure that the contract's account exists.
443		let ed = Pallet::<T>::min_balance();
444		self.total_deposit = Deposit::Charge(ed);
445		T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
446
447		// A consumer is added at account creation and removed it on termination, otherwise the
448		// runtime could remove the account. As long as a contract exists its account must exist.
449		// With the consumer, a correct runtime cannot remove the account.
450		System::<T>::inc_consumers(contract)?;
451
452		let deposit = contract_info.update_base_deposit(&code_info);
453		let deposit = Deposit::Charge(deposit);
454
455		self.charge_deposit(contract.clone(), deposit);
456		Ok(())
457	}
458
459	/// Call to tell the meter that the currently executing contract was terminated.
460	///
461	/// This will manipulate the meter so that all storage deposit accumulated in
462	/// `contract_info` will be refunded to the `origin` of the meter. And the free
463	/// (`reducible_balance`) will be sent to the `beneficiary`.
464	pub fn terminate(&mut self, info: &ContractInfo<T>, beneficiary: T::AccountId) {
465		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
466		self.own_contribution = Contribution::Terminated {
467			deposit: Deposit::Refund(info.total_deposit()),
468			beneficiary,
469		};
470	}
471
472	/// [`Self::charge`] does not enforce the storage limit since we want to do this check as late
473	/// as possible to allow later refunds to offset earlier charges.
474	///
475	/// # Note
476	///
477	/// We normally need to call this **once** for every call stack and not for every cross contract
478	/// call. However, if a dedicated limit is specified for a sub-call, this needs to be called
479	/// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is
480	/// used.
481	pub fn enforce_limit(
482		&mut self,
483		info: Option<&mut ContractInfo<T>>,
484	) -> Result<(), DispatchError> {
485		let deposit = self.own_contribution.update_contract(info);
486		let total_deposit = self.total_deposit.saturating_add(&deposit);
487		// We don't want to override a `Terminated` with a `Checked`.
488		if matches!(self.contract_state(), ContractState::Alive) {
489			self.own_contribution = Contribution::Checked(deposit);
490		}
491		if let Deposit::Charge(amount) = total_deposit {
492			if amount > self.limit {
493				return Err(<Error<T>>::StorageDepositLimitExhausted.into())
494			}
495		}
496		Ok(())
497	}
498
499	/// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to
500	/// enforce its special limit if needed.
501	pub fn enforce_subcall_limit(
502		&mut self,
503		info: Option<&mut ContractInfo<T>>,
504	) -> Result<(), DispatchError> {
505		match self.nested {
506			Nested::OwnLimit => self.enforce_limit(info),
507			Nested::DerivedLimit => Ok(()),
508		}
509	}
510}
511
512impl<T: Config> Ext<T> for ReservingExt {
513	fn check_limit(
514		origin: &T::AccountId,
515		limit: Option<BalanceOf<T>>,
516		min_leftover: BalanceOf<T>,
517	) -> Result<BalanceOf<T>, DispatchError> {
518		// We are sending the `min_leftover` and the `min_balance` from the origin
519		// account as part of a contract call. Hence origin needs to have those left over
520		// as free balance after accounting for all deposits.
521		let max = T::Currency::reducible_balance(origin, Preservation::Preserve, Polite)
522			.saturating_sub(min_leftover)
523			.saturating_sub(Pallet::<T>::min_balance());
524		let default = max.min(T::DefaultDepositLimit::get());
525		let limit = limit.unwrap_or(default);
526		ensure!(
527			limit <= max &&
528				matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success),
529			<Error<T>>::StorageDepositNotEnoughFunds,
530		);
531		Ok(limit)
532	}
533
534	fn charge(
535		origin: &T::AccountId,
536		contract: &T::AccountId,
537		amount: &DepositOf<T>,
538		state: &ContractState<T>,
539	) -> Result<(), DispatchError> {
540		match amount {
541			Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()),
542			Deposit::Charge(amount) => {
543				// This could fail if the `origin` does not have enough liquidity. Ideally, though,
544				// this should have been checked before with `check_limit`.
545				T::Currency::transfer_and_hold(
546					&HoldReason::StorageDepositReserve.into(),
547					origin,
548					contract,
549					*amount,
550					Precision::Exact,
551					Preservation::Preserve,
552					Fortitude::Polite,
553				)?;
554
555				Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndHeld {
556					from: origin.clone(),
557					to: contract.clone(),
558					amount: *amount,
559				});
560			},
561			Deposit::Refund(amount) => {
562				let transferred = T::Currency::transfer_on_hold(
563					&HoldReason::StorageDepositReserve.into(),
564					contract,
565					origin,
566					*amount,
567					Precision::BestEffort,
568					Restriction::Free,
569					Fortitude::Polite,
570				)?;
571
572				Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndReleased {
573					from: contract.clone(),
574					to: origin.clone(),
575					amount: transferred,
576				});
577
578				if transferred < *amount {
579					// This should never happen, if it does it means that there is a bug in the
580					// runtime logic. In the rare case this happens we try to refund as much as we
581					// can, thus the `Precision::BestEffort`.
582					log::error!(
583						target: LOG_TARGET,
584						"Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.",
585						amount, contract, origin, transferred,
586					);
587				}
588			},
589		}
590		if let ContractState::<T>::Terminated { beneficiary } = state {
591			System::<T>::dec_consumers(&contract);
592			// Whatever is left in the contract is sent to the termination beneficiary.
593			T::Currency::transfer(
594				&contract,
595				&beneficiary,
596				T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite),
597				Preservation::Expendable,
598			)?;
599		}
600		Ok(())
601	}
602}
603
604mod private {
605	pub trait Sealed {}
606	impl Sealed for super::Root {}
607	impl Sealed for super::Nested {}
608}
609
610#[cfg(test)]
611mod tests {
612	use super::*;
613	use crate::{
614		exec::AccountIdOf,
615		tests::{Test, ALICE, BOB, CHARLIE},
616	};
617	use frame_support::parameter_types;
618	use pretty_assertions::assert_eq;
619
620	type TestMeter = RawMeter<Test, TestExt, Root>;
621
622	parameter_types! {
623		static TestExtTestValue: TestExt = Default::default();
624	}
625
626	#[derive(Debug, PartialEq, Eq, Clone)]
627	struct LimitCheck {
628		origin: AccountIdOf<Test>,
629		limit: BalanceOf<Test>,
630		min_leftover: BalanceOf<Test>,
631	}
632
633	#[derive(Debug, PartialEq, Eq, Clone)]
634	struct Charge {
635		origin: AccountIdOf<Test>,
636		contract: AccountIdOf<Test>,
637		amount: DepositOf<Test>,
638		state: ContractState<Test>,
639	}
640
641	#[derive(Default, Debug, PartialEq, Eq, Clone)]
642	pub struct TestExt {
643		limit_checks: Vec<LimitCheck>,
644		charges: Vec<Charge>,
645	}
646
647	impl TestExt {
648		fn clear(&mut self) {
649			self.limit_checks.clear();
650			self.charges.clear();
651		}
652	}
653
654	impl Ext<Test> for TestExt {
655		fn check_limit(
656			origin: &AccountIdOf<Test>,
657			limit: Option<BalanceOf<Test>>,
658			min_leftover: BalanceOf<Test>,
659		) -> Result<BalanceOf<Test>, DispatchError> {
660			let limit = limit.unwrap_or(42);
661			TestExtTestValue::mutate(|ext| {
662				ext.limit_checks
663					.push(LimitCheck { origin: origin.clone(), limit, min_leftover })
664			});
665			Ok(limit)
666		}
667
668		fn charge(
669			origin: &AccountIdOf<Test>,
670			contract: &AccountIdOf<Test>,
671			amount: &DepositOf<Test>,
672			state: &ContractState<Test>,
673		) -> Result<(), DispatchError> {
674			TestExtTestValue::mutate(|ext| {
675				ext.charges.push(Charge {
676					origin: origin.clone(),
677					contract: contract.clone(),
678					amount: amount.clone(),
679					state: state.clone(),
680				})
681			});
682			Ok(())
683		}
684	}
685
686	fn clear_ext() {
687		TestExtTestValue::mutate(|ext| ext.clear())
688	}
689
690	struct ChargingTestCase {
691		origin: Origin<Test>,
692		deposit: DepositOf<Test>,
693		expected: TestExt,
694	}
695
696	#[derive(Default)]
697	struct StorageInfo {
698		bytes: u32,
699		items: u32,
700		bytes_deposit: BalanceOf<Test>,
701		items_deposit: BalanceOf<Test>,
702	}
703
704	fn new_info(info: StorageInfo) -> ContractInfo<Test> {
705		ContractInfo::<Test> {
706			trie_id: Default::default(),
707			code_hash: Default::default(),
708			storage_bytes: info.bytes,
709			storage_items: info.items,
710			storage_byte_deposit: info.bytes_deposit,
711			storage_item_deposit: info.items_deposit,
712			storage_base_deposit: Default::default(),
713			delegate_dependencies: Default::default(),
714		}
715	}
716
717	#[test]
718	fn new_reserves_balance_works() {
719		clear_ext();
720
721		TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
722
723		assert_eq!(
724			TestExtTestValue::get(),
725			TestExt {
726				limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
727				..Default::default()
728			}
729		)
730	}
731
732	#[test]
733	fn empty_charge_works() {
734		clear_ext();
735
736		let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
737		assert_eq!(meter.available(), 1_000);
738
739		// an empty charge does not create a `Charge` entry
740		let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
741		nested0.charge(&Default::default());
742		meter.absorb(nested0, &BOB, None);
743
744		assert_eq!(
745			TestExtTestValue::get(),
746			TestExt {
747				limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
748				..Default::default()
749			}
750		)
751	}
752
753	#[test]
754	fn charging_works() {
755		let test_cases = vec![
756			ChargingTestCase {
757				origin: Origin::<Test>::from_account_id(ALICE),
758				deposit: Deposit::Refund(28),
759				expected: TestExt {
760					limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }],
761					charges: vec![
762						Charge {
763							origin: ALICE,
764							contract: CHARLIE,
765							amount: Deposit::Refund(10),
766							state: ContractState::Alive,
767						},
768						Charge {
769							origin: ALICE,
770							contract: CHARLIE,
771							amount: Deposit::Refund(20),
772							state: ContractState::Alive,
773						},
774						Charge {
775							origin: ALICE,
776							contract: BOB,
777							amount: Deposit::Charge(2),
778							state: ContractState::Alive,
779						},
780					],
781				},
782			},
783			ChargingTestCase {
784				origin: Origin::<Test>::Root,
785				deposit: Deposit::Charge(0),
786				expected: TestExt { limit_checks: vec![], charges: vec![] },
787			},
788		];
789
790		for test_case in test_cases {
791			clear_ext();
792
793			let mut meter = TestMeter::new(&test_case.origin, Some(100), 0).unwrap();
794			assert_eq!(meter.available(), 100);
795
796			let mut nested0_info = new_info(StorageInfo {
797				bytes: 100,
798				items: 5,
799				bytes_deposit: 100,
800				items_deposit: 10,
801			});
802			let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
803			nested0.charge(&Diff {
804				bytes_added: 108,
805				bytes_removed: 5,
806				items_added: 1,
807				items_removed: 2,
808			});
809			nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
810
811			let mut nested1_info = new_info(StorageInfo {
812				bytes: 100,
813				items: 10,
814				bytes_deposit: 100,
815				items_deposit: 20,
816			});
817			let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
818			nested1.charge(&Diff { items_removed: 5, ..Default::default() });
819			nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info));
820
821			let mut nested2_info = new_info(StorageInfo {
822				bytes: 100,
823				items: 7,
824				bytes_deposit: 100,
825				items_deposit: 20,
826			});
827			let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
828			nested2.charge(&Diff { items_removed: 7, ..Default::default() });
829			nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info));
830
831			nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
832			meter.absorb(nested0, &BOB, Some(&mut nested0_info));
833
834			assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
835
836			assert_eq!(nested0_info.extra_deposit(), 112);
837			assert_eq!(nested1_info.extra_deposit(), 110);
838			assert_eq!(nested2_info.extra_deposit(), 100);
839
840			assert_eq!(TestExtTestValue::get(), test_case.expected)
841		}
842	}
843
844	#[test]
845	fn termination_works() {
846		let test_cases = vec![
847			ChargingTestCase {
848				origin: Origin::<Test>::from_account_id(ALICE),
849				deposit: Deposit::Refund(108),
850				expected: TestExt {
851					limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
852					charges: vec![
853						Charge {
854							origin: ALICE,
855							contract: CHARLIE,
856							amount: Deposit::Refund(120),
857							state: ContractState::Terminated { beneficiary: CHARLIE },
858						},
859						Charge {
860							origin: ALICE,
861							contract: BOB,
862							amount: Deposit::Charge(12),
863							state: ContractState::Alive,
864						},
865					],
866				},
867			},
868			ChargingTestCase {
869				origin: Origin::<Test>::Root,
870				deposit: Deposit::Charge(0),
871				expected: TestExt { limit_checks: vec![], charges: vec![] },
872			},
873		];
874
875		for test_case in test_cases {
876			clear_ext();
877
878			let mut meter = TestMeter::new(&test_case.origin, Some(1_000), 0).unwrap();
879			assert_eq!(meter.available(), 1_000);
880
881			let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
882			nested0.charge(&Diff {
883				bytes_added: 5,
884				bytes_removed: 1,
885				items_added: 3,
886				items_removed: 1,
887			});
888			nested0.charge(&Diff { items_added: 2, ..Default::default() });
889
890			let mut nested1_info = new_info(StorageInfo {
891				bytes: 100,
892				items: 10,
893				bytes_deposit: 100,
894				items_deposit: 20,
895			});
896			let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
897			nested1.charge(&Diff { items_removed: 5, ..Default::default() });
898			nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
899			nested1.terminate(&nested1_info, CHARLIE);
900			nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
901			nested0.absorb(nested1, &CHARLIE, None);
902
903			meter.absorb(nested0, &BOB, None);
904			assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
905			assert_eq!(TestExtTestValue::get(), test_case.expected)
906		}
907	}
908}