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	DebugNoBound, DefaultNoBound,
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, DebugNoBound)]
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, DebugNoBound)]
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, DebugNoBound)]
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			},
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/// The state of a contract.
229///
230/// In case of termination the beneficiary is indicated.
231#[derive(DebugNoBound, Clone, PartialEq, Eq)]
232pub enum ContractState<T: Config> {
233	Alive,
234	Terminated { beneficiary: AccountIdOf<T> },
235}
236
237/// Records information to charge or refund a plain account.
238///
239/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
240/// this we can do all the refunds before doing any charge. This way a plain account can use
241/// more deposit than it has balance as along as it is covered by a refund. This
242/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
243/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract
244/// call. In that case the limit is enforced once the call is returned, rolling it back if
245/// exhausted.
246#[derive(DebugNoBound, Clone)]
247struct Charge<T: Config> {
248	contract: T::AccountId,
249	amount: DepositOf<T>,
250	state: ContractState<T>,
251}
252
253/// Records the storage changes of a storage meter.
254#[derive(DebugNoBound)]
255enum Contribution<T: Config> {
256	/// The contract the meter belongs to is alive and accumulates changes using a [`Diff`].
257	Alive(Diff),
258	/// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of
259	/// its execution. In this process the [`Diff`] was converted into a [`Deposit`].
260	Checked(DepositOf<T>),
261	/// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`]
262	/// in order to calculate the refund. Upon termination the `reducible_balance` in the
263	/// contract's account is transferred to the [`beneficiary`].
264	Terminated { deposit: DepositOf<T>, beneficiary: AccountIdOf<T> },
265}
266
267impl<T: Config> Contribution<T> {
268	/// See [`Diff::update_contract`].
269	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
285/// Functions that apply to all states.
286impl<T, E, S> RawMeter<T, E, S>
287where
288	T: Config,
289	E: Ext<T>,
290	S: State + Default + Debug,
291{
292	/// Create a new child that has its `limit`.
293	/// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent.
294	///
295	/// This is called whenever a new subcall is initiated in order to track the storage
296	/// usage for this sub call separately. This is necessary because we want to exchange balance
297	/// with the current contract we are interacting with.
298	pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
299		debug_assert!(matches!(self.contract_state(), ContractState::Alive));
300		// If a special limit is specified higher than it is available,
301		// we want to enforce the lesser limit to the nested meter, to fail in the sub-call.
302		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	/// Absorb a child that was spawned to handle a sub call.
311	///
312	/// This should be called whenever a sub call comes to its end and it is **not** reverted.
313	/// This does the actual balance transfer from/to `origin` and `contract` based on the
314	/// overall storage consumption of the call. It also updates the supplied contract info.
315	///
316	/// In case a contract reverted the child meter should just be dropped in order to revert
317	/// any changes it recorded.
318	///
319	/// # Parameters
320	///
321	/// - `absorbed`: The child storage meter that should be absorbed.
322	/// - `origin`: The origin that spawned the original root meter.
323	/// - `contract`: The contract's account that this sub call belongs to.
324	/// - `info`: The info of the contract in question. `None` if the contract was terminated.
325	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	/// The amount of balance that is still available from the original `limit`.
347	fn available(&self) -> BalanceOf<T> {
348		self.total_deposit.available(&self.limit)
349	}
350
351	/// Returns the state of the currently executed contract.
352	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
362/// Functions that only apply to the root state.
363impl<T, E> RawMeter<T, E, Root>
364where
365	T: Config,
366	E: Ext<T>,
367{
368	/// Create new storage meter for the specified `origin` and `limit`.
369	///
370	/// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible.
371	pub fn new(
372		origin: &Origin<T>,
373		limit: Option<BalanceOf<T>>,
374		min_leftover: BalanceOf<T>,
375	) -> Result<Self, DispatchError> {
376		// Check the limit only if the origin is not root.
377		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	/// The total amount of deposit that should change hands as result of the execution
390	/// that this meter was passed into. This will also perform all the charges accumulated
391	/// in the whole contract stack.
392	///
393	/// This drops the root meter in order to make sure it is only called when the whole
394	/// execution did finish.
395	pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
396		// Only refund or charge deposit if the origin is not root.
397		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
411/// Functions that only apply to the nested state.
412impl<T, E> RawMeter<T, E, Nested>
413where
414	T: Config,
415	E: Ext<T>,
416{
417	/// Charges `diff` from the meter.
418	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	/// Adds a deposit charge.
426	///
427	/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
428	/// change. This is the case when a `delegate_dependency` is added or removed, or when the
429	/// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the
430	/// deposit charge separately from the storage charge.
431	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	/// Charges from `origin` a storage deposit for contract instantiation.
437	///
438	/// This immediately transfers the balance in order to create the account.
439	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		// We need to make sure that the contract's account exists.
449		let ed = Pallet::<T>::min_balance();
450		self.total_deposit = Deposit::Charge(ed);
451		T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
452
453		// A consumer is added at account creation and removed it on termination, otherwise the
454		// runtime could remove the account. As long as a contract exists its account must exist.
455		// With the consumer, a correct runtime cannot remove the account.
456		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	/// Call to tell the meter that the currently executing contract was terminated.
466	///
467	/// This will manipulate the meter so that all storage deposit accumulated in
468	/// `contract_info` will be refunded to the `origin` of the meter. And the free
469	/// (`reducible_balance`) will be sent to the `beneficiary`.
470	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	/// [`Self::charge`] does not enforce the storage limit since we want to do this check as late
479	/// as possible to allow later refunds to offset earlier charges.
480	///
481	/// # Note
482	///
483	/// We normally need to call this **once** for every call stack and not for every cross contract
484	/// call. However, if a dedicated limit is specified for a sub-call, this needs to be called
485	/// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is
486	/// used.
487	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		// We don't want to override a `Terminated` with a `Checked`.
494		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	/// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to
506	/// enforce its special limit if needed.
507	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		// We are sending the `min_leftover` and the `min_balance` from the origin
525		// account as part of a contract call. Hence origin needs to have those left over
526		// as free balance after accounting for all deposits.
527		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				// This could fail if the `origin` does not have enough liquidity. Ideally, though,
550				// this should have been checked before with `check_limit`.
551				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					// This should never happen, if it does it means that there is a bug in the
586					// runtime logic. In the rare case this happens we try to refund as much as we
587					// can, thus the `Precision::BestEffort`.
588					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			// Whatever is left in the contract is sent to the termination beneficiary.
599			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		// an empty charge does not create a `Charge` entry
746		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}