referrerpolicy=no-referrer-when-downgrade

pallet_revive/metering/
storage.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
20#[cfg(test)]
21mod tests;
22
23use super::{Nested, Root, State};
24use crate::{
25	BalanceOf, Config, ExecConfig, ExecOrigin as Origin, HoldReason, Pallet,
26	StorageDeposit as Deposit, storage::ContractInfo,
27};
28use alloc::vec::Vec;
29use core::{marker::PhantomData, mem};
30use frame_support::{DebugNoBound, DefaultNoBound, traits::Get};
31use sp_runtime::{
32	DispatchError, FixedPointNumber, FixedU128,
33	traits::{Saturating, Zero},
34};
35
36#[cfg(test)]
37use num_traits::Bounded;
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 storage meter that actually charges from its origin.
46///
47/// This can be used where we want to be generic over the state (Root vs. Nested).
48pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
49
50/// A trait that allows to decouple the metering from the charging of balance.
51///
52/// This mostly exists for testing so that the charging can be mocked.
53pub trait Ext<T: Config> {
54	/// This is called to inform the implementer that some balance should be charged due to
55	/// some interaction of the `origin` with a `contract`.
56	///
57	/// The balance transfer can either flow from `origin` to `contract` or the other way
58	/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
59	/// It will fail in case the `origin` has not enough balance to cover all storage deposits.
60	fn charge(
61		origin: &T::AccountId,
62		contract: &T::AccountId,
63		amount: &DepositOf<T>,
64		exec_config: &ExecConfig<T>,
65	) -> Result<(), DispatchError>;
66}
67
68/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged.
69///
70/// It uses [`frame_support::traits::fungible::Mutate`] in order to do accomplish the reserves.
71pub enum ReservingExt {}
72
73/// A type that allows the metering of consumed or freed storage of a single contract call stack.
74#[derive(DefaultNoBound, DebugNoBound)]
75pub struct RawMeter<T: Config, E, S: State> {
76	/// The limit of how much balance this meter is allowed to consume.
77	pub(crate) limit: Option<BalanceOf<T>>,
78	/// The amount of balance that was used in this meter and all of its already absorbed children.
79	total_deposit: DepositOf<T>,
80	/// The amount of storage changes that were recorded in this meter alone.
81	/// This has no meaning for Root meters and will always be Contribution::Checked(0)
82	own_contribution: Contribution<T>,
83	/// List of charges that should be applied at the end of a contract stack execution.
84	///
85	/// We only have one charge per contract hence the size of this vector is
86	/// limited by the maximum call depth.
87	charges: Vec<Charge<T>>,
88	/// The maximal consumed deposit that occurred at any point during the execution of this
89	/// storage deposit meter
90	max_charged: BalanceOf<T>,
91	/// True if this is the root meter.
92	///
93	/// Sometimes we cannot know at compile time.
94	pub(crate) is_root: bool,
95	/// Type parameter only used in impls.
96	_phantom: PhantomData<(E, S)>,
97}
98
99/// This type is used to describe a storage change when charging from the meter.
100#[derive(Default, DebugNoBound)]
101pub struct Diff {
102	/// How many bytes were added to storage.
103	pub bytes_added: u32,
104	/// How many bytes were removed from storage.
105	pub bytes_removed: u32,
106	/// How many storage items were added to storage.
107	pub items_added: u32,
108	/// How many storage items were removed from storage.
109	pub items_removed: u32,
110}
111
112impl Diff {
113	/// Calculate how much of a charge or refund results from applying the diff and store it
114	/// in the passed `info` if any.
115	///
116	/// # Note
117	///
118	/// In case `None` is passed for `info` only charges are calculated. This is because refunds
119	/// are calculated pro rata of the existing storage within a contract and hence need extract
120	/// this information from the passed `info`.
121	pub fn update_contract<T: Config>(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
122		let per_byte = T::DepositPerByte::get();
123		let per_item = T::DepositPerChildTrieItem::get();
124		let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed);
125		let items_added = self.items_added.saturating_sub(self.items_removed);
126		let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into()));
127		let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into()));
128
129		// Without any contract info we can only calculate diffs which add storage
130		let info = if let Some(info) = info {
131			info
132		} else {
133			return bytes_deposit.saturating_add(&items_deposit);
134		};
135
136		// Refunds are calculated pro rata based on the accumulated storage within the contract
137		let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added);
138		let items_removed = self.items_removed.saturating_sub(self.items_added);
139		let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes)
140			.unwrap_or_default()
141			.min(FixedU128::from_u32(1));
142		bytes_deposit = bytes_deposit
143			.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit)));
144		let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items)
145			.unwrap_or_default()
146			.min(FixedU128::from_u32(1));
147		items_deposit = items_deposit
148			.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit)));
149
150		// We need to update the contract info structure with the new deposits
151		info.storage_bytes =
152			info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed);
153		info.storage_items =
154			info.storage_items.saturating_add(items_added).saturating_sub(items_removed);
155		match &bytes_deposit {
156			Deposit::Charge(amount) => {
157				info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount)
158			},
159			Deposit::Refund(amount) => {
160				info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount)
161			},
162		}
163		match &items_deposit {
164			Deposit::Charge(amount) => {
165				info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount)
166			},
167			Deposit::Refund(amount) => {
168				info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount)
169			},
170		}
171
172		bytes_deposit.saturating_add(&items_deposit)
173	}
174}
175
176impl Diff {
177	fn saturating_add(&self, rhs: &Self) -> Self {
178		Self {
179			bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
180			bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
181			items_added: self.items_added.saturating_add(rhs.items_added),
182			items_removed: self.items_removed.saturating_add(rhs.items_removed),
183		}
184	}
185}
186
187/// The state of a contract.
188#[derive(DebugNoBound, Clone, PartialEq, Eq)]
189pub enum ContractState<T: Config> {
190	Alive { amount: DepositOf<T> },
191	Terminated,
192}
193
194/// Records information to charge or refund a plain account.
195///
196/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
197/// this we can do all the refunds before doing any charge. This way a plain account can use
198/// more deposit than it has balance as along as it is covered by a refund. This
199/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
200/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract
201/// call. In that case the limit is enforced once the call is returned, rolling it back if
202/// exhausted.
203#[derive(DebugNoBound, Clone)]
204struct Charge<T: Config> {
205	contract: T::AccountId,
206	state: ContractState<T>,
207}
208
209/// Records the storage changes of a storage meter.
210#[derive(DebugNoBound)]
211enum Contribution<T: Config> {
212	/// The contract the meter belongs to is alive and accumulates changes using a [`Diff`].
213	Alive(Diff),
214	/// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of
215	/// its execution. In this process the [`Diff`] was converted into a [`Deposit`].
216	Checked(DepositOf<T>),
217}
218
219impl<T: Config> Contribution<T> {
220	/// See [`Diff::update_contract`].
221	fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
222		match self {
223			Self::Alive(diff) => diff.update_contract::<T>(info),
224			Self::Checked(deposit) => deposit.clone(),
225		}
226	}
227}
228
229impl<T: Config> Default for Contribution<T> {
230	fn default() -> Self {
231		Self::Alive(Default::default())
232	}
233}
234
235/// Functions that apply to all states.
236impl<T, E, S> RawMeter<T, E, S>
237where
238	T: Config,
239	E: Ext<T>,
240	S: State,
241{
242	/// Create a new child that has its `limit`.
243	///
244	/// This is called whenever a new subcall is initiated in order to track the storage
245	/// usage for this sub call separately. This is necessary because we want to exchange balance
246	/// with the current contract we are interacting with.
247	pub fn nested(&self, mut limit: Option<BalanceOf<T>>) -> RawMeter<T, E, Nested> {
248		if let (Some(new_limit), Some(old_limit)) = (limit, self.limit) {
249			limit = Some(new_limit.min(old_limit));
250		}
251
252		RawMeter { limit, ..Default::default() }
253	}
254
255	/// Absorb a child that was spawned to handle a sub call.
256	///
257	/// This should be called whenever a sub call comes to its end and it is **not** reverted.
258	/// This does the actual balance transfer from/to `origin` and `contract` based on the
259	/// overall storage consumption of the call. It also updates the supplied contract info.
260	///
261	/// In case a contract reverted the child meter should just be dropped in order to revert
262	/// any changes it recorded.
263	///
264	/// # Parameters
265	///
266	/// - `absorbed`: The child storage meter that should be absorbed.
267	/// - `origin`: The origin that spawned the original root meter.
268	/// - `contract`: The contract's account that this sub call belongs to.
269	/// - `info`: The info of the contract in question. `None` if the contract was terminated.
270	pub fn absorb(
271		&mut self,
272		absorbed: RawMeter<T, E, Nested>,
273		contract: &T::AccountId,
274		info: Option<&mut ContractInfo<T>>,
275	) {
276		// We are now at the position to calculate the actual final net charge of `absorbed` as we
277		// now have the contract information `info`. Before that we only took net charges related to
278		// the contract storage into account but ignored net refunds.
279		// However, with this complete information there is no need to recalculate `max_charged` for
280		// `absorbed` here before we absorb it because the actual final net charge will not be more
281		// than the net charge we observed before (as we only ignored net refunds but not net
282		// charges).
283		self.max_charged = self
284			.max_charged
285			.max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero());
286
287		let own_deposit = absorbed.own_contribution.update_contract(info);
288		self.total_deposit = self
289			.total_deposit
290			.saturating_add(&absorbed.total_deposit)
291			.saturating_add(&own_deposit);
292		self.charges.extend_from_slice(&absorbed.charges);
293
294		self.recalulculate_max_charged();
295
296		if !own_deposit.is_zero() {
297			self.charges.push(Charge {
298				contract: contract.clone(),
299				state: ContractState::Alive { amount: own_deposit },
300			});
301		}
302	}
303
304	/// Absorb only the maximum charge of the child meter.
305	///
306	/// This should be called whenever a sub call ends and reverts.
307	///
308	/// # Parameters
309	///
310	/// - `absorbed`: The child storage meter
311	pub fn absorb_only_max_charged(&mut self, absorbed: RawMeter<T, E, Nested>) {
312		self.max_charged = self
313			.max_charged
314			.max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero());
315	}
316
317	/// Record a charge that has taken place externally.
318	///
319	/// This will not perform a charge. It just records it to reflect it in the
320	/// total amount of storage required for a transaction.
321	pub fn record_charge(&mut self, amount: &DepositOf<T>) {
322		self.total_deposit = self.total_deposit.saturating_add(amount);
323		self.recalulculate_max_charged();
324	}
325
326	/// The amount of balance that this meter has consumed.
327	///
328	/// This disregards any refunds pending in the current frame. This
329	/// is because we can calculate refunds only at the end of each frame.
330	pub fn consumed(&self) -> DepositOf<T> {
331		self.total_deposit.saturating_add(&self.own_contribution.update_contract(None))
332	}
333
334	/// Return the maximum consumed deposit at any point in the previous execution
335	pub fn max_charged(&self) -> DepositOf<T> {
336		Deposit::Charge(self.max_charged)
337	}
338
339	/// Recaluclate the max deposit value
340	fn recalulculate_max_charged(&mut self) {
341		self.max_charged = self.max_charged.max(self.consumed().charge_or_zero());
342	}
343
344	/// The amount of balance still available from the current meter.
345	///
346	/// This includes charges from the current frame but no refunds.
347	#[cfg(test)]
348	pub fn available(&self) -> BalanceOf<T> {
349		self.consumed()
350			.available(&self.limit.unwrap_or(BalanceOf::<T>::max_value()))
351			.unwrap_or_default()
352	}
353}
354
355/// Functions that only apply to the root state.
356impl<T, E> RawMeter<T, E, Root>
357where
358	T: Config,
359	E: Ext<T>,
360{
361	/// Create new storage limiting storage deposits to the passed `limit`.
362	///
363	/// If the limit is larger than what the origin can afford we will just fail
364	/// when collecting the deposits in `execute_postponed_deposits`.
365	pub fn new(limit: Option<BalanceOf<T>>) -> Self {
366		Self {
367			limit,
368			is_root: true,
369			own_contribution: Contribution::Checked(Default::default()),
370			..Default::default()
371		}
372	}
373
374	/// The total amount of deposit that should change hands as result of the execution
375	/// that this meter was passed into. This will also perform all the charges accumulated
376	/// in the whole contract stack.
377	pub fn execute_postponed_deposits(
378		&mut self,
379		origin: &Origin<T>,
380		exec_config: &ExecConfig<T>,
381	) -> Result<DepositOf<T>, DispatchError> {
382		// Only refund or charge deposit if the origin is not root.
383		let origin = match origin {
384			Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
385			Origin::Signed(o) => o,
386		};
387
388		// Coalesce charges of the same contract
389		self.charges.sort_by(|a, b| a.contract.cmp(&b.contract));
390		self.charges = {
391			let mut coalesced: Vec<Charge<T>> = Vec::with_capacity(self.charges.len());
392			for mut ch in mem::take(&mut self.charges) {
393				if let Some(last) = coalesced.last_mut() {
394					if last.contract == ch.contract {
395						match (&mut last.state, &mut ch.state) {
396							(
397								ContractState::Alive { amount: last_amount },
398								ContractState::Alive { amount: ch_amount },
399							) => {
400								*last_amount = last_amount.saturating_add(&ch_amount);
401							},
402							(ContractState::Alive { amount }, ContractState::Terminated) |
403							(ContractState::Terminated, ContractState::Alive { amount }) => {
404								// undo all deposits made by a terminated contract
405								self.total_deposit = self.total_deposit.saturating_sub(&amount);
406								last.state = ContractState::Terminated;
407							},
408							(ContractState::Terminated, ContractState::Terminated) => {
409								debug_assert!(
410									false,
411									"We never emit two terminates for the same contract."
412								)
413							},
414						}
415						continue;
416					}
417				}
418				coalesced.push(ch);
419			}
420			coalesced
421		};
422
423		// refunds first so origin is able to pay for the charges using the refunds
424		for charge in self.charges.iter() {
425			if let ContractState::Alive { amount: amount @ Deposit::Refund(_) } = &charge.state {
426				E::charge(origin, &charge.contract, amount, exec_config)?;
427			}
428		}
429		for charge in self.charges.iter() {
430			if let ContractState::Alive { amount: amount @ Deposit::Charge(_) } = &charge.state {
431				E::charge(origin, &charge.contract, amount, exec_config)?;
432			}
433		}
434
435		Ok(self.total_deposit.clone())
436	}
437
438	/// Flag a `contract` as terminated.
439	///
440	/// This will signal to the meter to discard all charged and refunds incured by this
441	/// contract.
442	pub fn terminate(&mut self, contract: T::AccountId, refunded: BalanceOf<T>) {
443		self.total_deposit = self.total_deposit.saturating_add(&Deposit::Refund(refunded));
444		self.charges.push(Charge { contract, state: ContractState::Terminated });
445
446		// no need to recalculate max_charged here as the total consumed amount will just decrease
447		// with this extra refund
448	}
449}
450
451/// Functions that only apply to the nested state.
452impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> {
453	/// Charges `diff` from the meter.
454	pub fn charge(&mut self, diff: &Diff) {
455		match &mut self.own_contribution {
456			Contribution::Alive(own) => {
457				*own = own.saturating_add(diff);
458				self.recalulculate_max_charged();
459			},
460			_ => panic!("Charge is never called after termination; qed"),
461		};
462	}
463
464	/// Adds a charge without recording it in the contract info.
465	///
466	/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
467	/// change within the contract's child trie. This is the case when when the `code_hash` is
468	/// updated. [`Self::charge`] cannot be used here because we keep track of the deposit charge
469	/// separately from the storage charge.
470	///
471	/// If this functions is used the amount of the charge has to be stored by the caller somewhere
472	/// alese in order to be able to refund it.
473	pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
474		// will not fail in a nested meter
475		self.record_charge(&amount);
476		self.charges.push(Charge { contract, state: ContractState::Alive { amount } });
477	}
478
479	/// Determine the actual final charge from the own contributions
480	pub fn finalize_own_contributions(&mut self, info: Option<&mut ContractInfo<T>>) {
481		let deposit = self.own_contribution.update_contract(info);
482		self.own_contribution = Contribution::Checked(deposit);
483
484		// no need to recalculate max_charged here as the consumed amount cannot increase
485		// when taking removed bytes/items into account
486	}
487
488	/// Apply pending storage changes to a ContractInfo without finalizing the meter.
489	///
490	/// This is used before creating a nested frame to ensure the child frame can see
491	/// the parent's pending storage changes when calculating refunds.
492	///
493	/// Unlike [`Self::finalize_own_contributions`], this does not consume the pending diff,
494	/// allowing the meter to continue tracking changes after the nested call returns.
495	pub fn apply_pending_changes_to_contract(&self, info: &mut ContractInfo<T>) {
496		if let Contribution::Alive(diff) = &self.own_contribution {
497			// Apply the diff to update the ContractInfo's storage deposit fields.
498			// We don't care about the return value (the deposit amount) here,
499			// we just want to update the ContractInfo so child frames can see it.
500			let _ = diff.update_contract::<T>(Some(info));
501		}
502	}
503
504	/// Apply the pending diff to `info` and push its deposit as a final charge, then reset
505	/// `own_contribution` so finalize does not apply it a second time.
506	pub fn bank_pending_changes(&mut self, contract: T::AccountId, info: &mut ContractInfo<T>) {
507		if let Contribution::Alive(_) = &self.own_contribution {
508			let deposit = self.own_contribution.update_contract(Some(info));
509			self.own_contribution = Contribution::Alive(Default::default());
510			if !deposit.is_zero() {
511				self.charge_deposit(contract, deposit);
512			}
513		} else {
514			debug_assert!(
515				false,
516				"on-stack ancestor frames have not finalized yet, so own_contribution \
517				 should be Alive when banked; qed",
518			);
519		}
520	}
521}
522
523impl<T: Config> Ext<T> for ReservingExt {
524	fn charge(
525		origin: &T::AccountId,
526		contract: &T::AccountId,
527		amount: &DepositOf<T>,
528		exec_config: &ExecConfig<T>,
529	) -> Result<(), DispatchError> {
530		match amount {
531			Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => (),
532			Deposit::Charge(amount) => {
533				<Pallet<T>>::charge_deposit(
534					HoldReason::StorageDepositReserve,
535					origin,
536					contract,
537					*amount,
538					exec_config,
539				)?;
540			},
541			Deposit::Refund(amount) => {
542				<Pallet<T>>::refund_deposit(
543					HoldReason::StorageDepositReserve,
544					contract,
545					exec_config.funds(origin),
546					*amount,
547				)?;
548			},
549		}
550		Ok(())
551	}
552}