referrerpolicy=no-referrer-when-downgrade

pallet_revive/metering/
mod.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
18mod gas;
19mod math;
20mod storage;
21mod weight;
22
23#[cfg(test)]
24mod tests;
25
26use crate::{
27	BalanceOf, Config, Error, ExecConfig, ExecOrigin as Origin, LOG_TARGET, StorageDeposit,
28	evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt,
29};
30
31pub use gas::SignedGas;
32pub use storage::Diff;
33pub use weight::{ChargedAmount, Token};
34
35use frame_support::{DebugNoBound, DefaultNoBound};
36use num_traits::Zero;
37
38use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow};
39use sp_runtime::{FixedPointNumber, Weight};
40use storage::{DepositOf, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter};
41use weight::WeightMeter;
42
43use sp_runtime::{DispatchError, DispatchResult, FixedU128, SaturatedConversion};
44
45/// A type-state pattern ensuring that meters can only be used in valid states (root vs nested).
46///
47/// It is sealed and cannot be implemented outside of this module.
48pub trait State: private::Sealed + Default + Debug {}
49
50/// Root state for transaction-level resource metering.
51///
52/// Represents the top-level accounting of a transaction's resource usage.
53#[derive(Default, Debug)]
54pub struct Root;
55
56/// Nested state for frame-level resource metering.
57///
58/// Represents resource accounting for a single call frame.
59#[derive(Default, Debug)]
60pub struct Nested;
61
62impl State for Root {}
63impl State for Nested {}
64
65mod private {
66	pub trait Sealed {}
67	impl Sealed for super::Root {}
68	impl Sealed for super::Nested {}
69}
70
71/// The type of resource meter used at the root level for transactions as a whole.
72pub type TransactionMeter<T> = ResourceMeter<T, Root>;
73/// The type of resource meter used for an execution frame.
74pub type FrameMeter<T> = ResourceMeter<T, Nested>;
75
76/// Snapshot of a [`ResourceMeter`]'s consumption at a point in time.
77///
78/// Produced by [`ResourceMeter::snapshot`] and consumed by [`ResourceMeter::delta_since`].
79pub struct MeterSnapshot<T: Config> {
80	weight: Weight,
81	gas: SignedGas<T>,
82}
83
84/// Resource meter tracking weight and storage deposit consumption.
85#[derive(DefaultNoBound)]
86pub struct ResourceMeter<T: Config, S: State> {
87	/// The weight meter. Tracks consumed weight and weight limits.
88	weight: WeightMeter<T>,
89
90	/// The deposit meter. Tracks consumed storage deposit and storage deposit limits.
91	deposit: GenericStorageMeter<T, S>,
92
93	/// This is the maximum total consumable gas.
94	///
95	/// It is the sum of a) the total consumed gas (i.e., including all previous frames) at the
96	/// time the frame started and b) the gas limit of the frame. We don't store the gas limit of
97	/// the frame separately, it can be derived from `max_total_gas` by subtracting the total gas
98	/// at the beginning of the frame.
99	///
100	/// `max_total_gas` is only required for Ethereum execution, it is always zero for Substrate
101	/// executions.
102	max_total_gas: SignedGas<T>,
103
104	/// The total consumed weight at the time the frame started.
105	total_consumed_weight_before: Weight,
106
107	/// The total consumed storage deposit at the time the frame started.
108	total_consumed_deposit_before: DepositOf<T>,
109
110	/// The limits defined for the transaction. This determines whether this transaction uses the
111	/// Ethereum or Substrate execution mode.
112	transaction_limits: TransactionLimits<T>,
113
114	_phantom: PhantomData<S>,
115}
116
117/// Transaction-wide resource limit configuration.
118///
119/// Represents the two supported resource accounting modes:
120/// - EthereumGas: Single gas limit
121/// - WeightAndDeposit: Explicit limits for both computational weight and storage deposit
122#[derive(DebugNoBound, Clone)]
123pub enum TransactionLimits<T: Config> {
124	/// Ethereum execution mode: the transaction only specifies a gas limit.
125	EthereumGas {
126		/// The Ethereum gas limit
127		eth_gas_limit: BalanceOf<T>,
128		/// The weight limit for this transaction. This ensures that execution will not exhaust
129		/// weight limit. This is required for eth_transact extrinsic execution to ensure that the
130		/// max extrinsic weights is not overstepped.
131		weight_limit: Weight,
132		/// Some extra information about the transaction that is required to calculate gas usage.
133		eth_tx_info: EthTxInfo<T>,
134	},
135	/// Substrate execution mode: the transaction specifies a weight limit and a storage deposit
136	/// limit
137	WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf<T> },
138}
139
140impl<T: Config> Default for TransactionLimits<T> {
141	fn default() -> Self {
142		Self::WeightAndDeposit {
143			weight_limit: Default::default(),
144			deposit_limit: Default::default(),
145		}
146	}
147}
148
149impl<T: Config, S: State> ResourceMeter<T, S> {
150	/// Create a new nested meter with derived resource limits.
151	pub fn new_nested(&self, limit: &CallResources<T>) -> Result<FrameMeter<T>, DispatchError> {
152		log::trace!(
153			target: LOG_TARGET,
154			"Creating nested meter from parent: \
155				limit={limit:?}, \
156				weight_left={:?}, \
157				deposit_left={:?}, \
158				weight_consumed={:?}, \
159				deposit_consumed={:?}",
160			self.weight_left(),
161			self.deposit_left(),
162			self.weight_consumed(),
163			self.deposit_consumed(),
164		);
165
166		let mut new_meter = match &self.transaction_limits {
167			TransactionLimits::EthereumGas { eth_tx_info, .. } => {
168				math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info)
169			},
170			TransactionLimits::WeightAndDeposit { .. } => {
171				math::substrate_execution::new_nested_meter(self, limit)
172			},
173		}?;
174
175		new_meter.adjust_effective_weight_limit()?;
176
177		log::trace!(
178			target: LOG_TARGET,
179			"Creating nested meter done: \
180				weight_left={:?}, \
181				deposit_left={:?}, \
182				weight_consumed={:?}, \
183				deposit_consumed={:?}",
184			new_meter.weight_left(),
185			new_meter.deposit_left(),
186			new_meter.weight_consumed(),
187			new_meter.deposit_consumed(),
188		);
189
190		Ok(new_meter)
191	}
192
193	/// Absorb only the weight consumption from a nested frame meter.
194	pub fn absorb_weight_meter_only(&mut self, other: FrameMeter<T>) {
195		log::trace!(
196			target: LOG_TARGET,
197			"Absorb weight meter only: \
198				parent_weight_left={:?}, \
199				parent_deposit_left={:?}, \
200				parent_weight_consumed={:?}, \
201				parent_deposit_consumed={:?}, \
202				child_weight_left={:?}, \
203				child_deposit_left={:?}, \
204				child_weight_consumed={:?}, \
205				child_deposit_consumed={:?}",
206			self.weight_left(),
207			self.deposit_left(),
208			self.weight_consumed(),
209			self.deposit_consumed(),
210			other.weight_left(),
211			other.deposit_left(),
212			other.weight_consumed(),
213			other.deposit_consumed(),
214		);
215
216		self.weight.absorb_nested(other.weight);
217		self.deposit.absorb_only_max_charged(other.deposit);
218
219		log::trace!(
220			target: LOG_TARGET,
221			"Absorb weight meter done: \
222				parent_weight_left={:?}, \
223				parent_deposit_left={:?}, \
224				parent_weight_consumed={:?}, \
225				parent_deposit_consumed={:?}",
226			self.weight_left(),
227			self.deposit_left(),
228			self.weight_consumed(),
229			self.deposit_consumed(),
230		);
231	}
232
233	/// Absorb all resource consumption from a nested frame meter.
234	pub fn absorb_all_meters(
235		&mut self,
236		other: FrameMeter<T>,
237		contract: &T::AccountId,
238		info: Option<&mut ContractInfo<T>>,
239	) {
240		log::trace!(
241			target: LOG_TARGET,
242			"Absorb all meters: \
243				parent_weight_left={:?}, \
244				parent_deposit_left={:?}, \
245				parent_weight_consumed={:?}, \
246				parent_deposit_consumed={:?}, \
247				child_weight_left={:?}, \
248				child_deposit_left={:?}, \
249				child_weight_consumed={:?}, \
250				child_deposit_consumed={:?}",
251			self.weight_left(),
252			self.deposit_left(),
253			self.weight_consumed(),
254			self.deposit_consumed(),
255			other.weight_left(),
256			other.deposit_left(),
257			other.weight_consumed(),
258			other.deposit_consumed(),
259		);
260
261		self.weight.absorb_nested(other.weight);
262		self.deposit.absorb(other.deposit, contract, info);
263
264		let result = self.adjust_effective_weight_limit();
265		debug_assert!(result.is_ok(), "Absorbing nested meters should not exceed limits");
266
267		log::trace!(
268			target: LOG_TARGET,
269			"Absorb all meters done: \
270				parent_weight_left={:?}, \
271				parent_deposit_left={:?}, \
272				parent_weight_consumed={:?}, \
273				parent_deposit_consumed={:?}",
274			self.weight_left(),
275			self.deposit_left(),
276			self.weight_consumed(),
277			self.deposit_consumed(),
278		);
279	}
280
281	/// Charge a weight token against this meter's remaining weight limit.
282	///
283	/// Returns `Err(Error::OutOfGas)` if the weight limit would be exceeded.
284	#[inline]
285	pub fn charge_weight_token<Tok: Token<T>>(
286		&mut self,
287		token: Tok,
288	) -> Result<ChargedAmount, DispatchError> {
289		self.weight.charge(token)
290	}
291
292	/// Try to charge a weight token or halt if not enough weight is left.
293	#[inline]
294	pub fn charge_or_halt<Tok: Token<T>>(
295		&mut self,
296		token: Tok,
297	) -> ControlFlow<Halt, ChargedAmount> {
298		self.weight.charge_or_halt(token)
299	}
300
301	/// Adjust an earlier weight charge with the actual weight consumed.
302	pub fn adjust_weight<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
303		self.weight.adjust_weight(charged_amount, token);
304	}
305
306	/// Synchronize meter state with PolkaVM executor's fuel consumption.
307	///
308	/// Maps the VM's internal fuel accounting to weight consumption:
309	/// - Converts engine fuel units to weight units
310	/// - Updates meter state to match actual VM resource usage
311	pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
312		self.weight.sync_from_executor(engine_fuel)
313	}
314
315	/// Convert meter state to PolkaVM executor fuel units.
316	///
317	/// Prepares for VM execution by:
318	/// - Computing remaining available weight
319	/// - Converting weight units to VM fuel units and return
320	pub fn sync_to_executor(&mut self) -> polkavm::Gas {
321		self.weight.sync_to_executor()
322	}
323
324	/// Consume all remaining weight in the meter.
325	pub fn consume_all_weight(&mut self) {
326		self.weight.consume_all();
327	}
328
329	/// Record a storage deposit charge against this meter.
330	pub fn charge_deposit(&mut self, deposit: &DepositOf<T>) -> DispatchResult {
331		log::trace!(
332			target: LOG_TARGET,
333			"Charge deposit: \
334				deposit={:?}, \
335				deposit_left={:?}, \
336				deposit_consumed={:?}, \
337				max_charged={:?}",
338			deposit,
339			self.deposit_left(),
340			self.deposit_consumed(),
341			self.deposit.max_charged(),
342		);
343
344		self.deposit.record_charge(deposit);
345		self.adjust_effective_weight_limit()?;
346
347		if self.deposit.is_root {
348			if self.deposit_left().is_none() {
349				self.deposit.reset();
350				self.adjust_effective_weight_limit()?;
351				return Err(<Error<T>>::StorageDepositLimitExhausted.into());
352			}
353		}
354
355		Ok(())
356	}
357
358	/// Get remaining ethereum gas equivalent.
359	///
360	/// Converts remaining resources to ethereum gas units:
361	/// - For ethereum mode: computes directly from gas accounting
362	/// - For substrate mode: converts weight+deposit to gas equivalent
363	/// Returns None if resources are exhausted or conversion fails.
364	pub fn eth_gas_left(&self) -> Option<BalanceOf<T>> {
365		let gas_left = match &self.transaction_limits {
366			TransactionLimits::EthereumGas { eth_tx_info, .. } => {
367				math::ethereum_execution::gas_left(self, eth_tx_info)
368			},
369			TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::gas_left(self),
370		}?;
371
372		gas_left.to_ethereum_gas()
373	}
374
375	/// Get remaining weight available.
376	///
377	/// Computes remaining computational capacity:
378	/// - For ethereum mode: converts from gas to weight units
379	/// - For substrate mode: subtracts consumed from weight limit
380	/// Returns None if resources are exhausted.
381	pub fn weight_left(&self) -> Option<Weight> {
382		match &self.transaction_limits {
383			TransactionLimits::EthereumGas { eth_tx_info, .. } => {
384				math::ethereum_execution::weight_left(self, eth_tx_info)
385			},
386			TransactionLimits::WeightAndDeposit { .. } => {
387				math::substrate_execution::weight_left(self)
388			},
389		}
390	}
391
392	/// Get remaining deposit available.
393	///
394	/// Computes remaining storage deposit allowance:
395	/// - For ethereum mode: converts from gas to deposit units
396	/// - For substrate mode: subtracts consumed from deposit limit
397	/// Returns None if resources are exhausted.
398	pub fn deposit_left(&self) -> Option<BalanceOf<T>> {
399		match &self.transaction_limits {
400			TransactionLimits::EthereumGas { eth_tx_info, .. } => {
401				math::ethereum_execution::deposit_left(self, eth_tx_info)
402			},
403			TransactionLimits::WeightAndDeposit { .. } => {
404				math::substrate_execution::deposit_left(self)
405			},
406		}
407	}
408
409	/// Calculate total gas consumed so far.
410	///
411	/// Computes the ethereum-gas equivalent of all resource usage:
412	/// - Converts weight and deposit consumption to gas units
413	/// - For ethereum mode: uses direct gas accounting
414	/// - For substrate mode: synthesizes from weight+deposit usage
415	pub fn total_consumed_gas(&self) -> BalanceOf<T> {
416		let signed_gas = match &self.transaction_limits {
417			TransactionLimits::EthereumGas { eth_tx_info, .. } => {
418				math::ethereum_execution::total_consumed_gas(self, eth_tx_info)
419			},
420			TransactionLimits::WeightAndDeposit { .. } => {
421				math::substrate_execution::total_consumed_gas(self)
422			},
423		};
424
425		signed_gas.to_ethereum_gas().unwrap_or_default()
426	}
427
428	/// Get total weight consumed
429	pub fn weight_consumed(&self) -> Weight {
430		self.weight.weight_consumed()
431	}
432
433	/// Get total weight required
434	/// This is the maximum amount of weight consumption that occurred during execution so far
435	/// This is relevant because consumed weight can decrease in case it is asjusted a posteriori
436	/// for some operations
437	pub fn weight_required(&self) -> Weight {
438		self.weight.weight_required()
439	}
440
441	/// Get total storage deposit consumed in the current frame.
442	///
443	/// Returns the net storage deposit change from this frame,
444	pub fn deposit_consumed(&self) -> DepositOf<T> {
445		self.deposit.consumed()
446	}
447
448	/// Get maximum storage deposit required at any point.
449	///
450	/// Returns the highest deposit amount needed during execution,
451	/// accounting for temporary storage spikes before later refunds.
452	pub fn deposit_required(&self) -> DepositOf<T> {
453		self.deposit.max_charged()
454	}
455
456	/// Get the Ethereum gas that has been consumed during the lifetime of this meter
457	pub fn eth_gas_consumed(&self) -> BalanceOf<T> {
458		self.eth_gas_consumed_signed().to_ethereum_gas().unwrap_or_default()
459	}
460
461	/// Same as [`Self::eth_gas_consumed`] but returns the unrounded [`SignedGas`].
462	///
463	/// Prefer this when computing a delta across two snapshots: subtracting in [`SignedGas`] form
464	/// avoids the double ceil-rounding that [`Self::eth_gas_consumed`] performs at each call.
465	pub fn eth_gas_consumed_signed(&self) -> SignedGas<T> {
466		match &self.transaction_limits {
467			TransactionLimits::EthereumGas { eth_tx_info, .. } => {
468				math::ethereum_execution::eth_gas_consumed(self, eth_tx_info)
469			},
470			TransactionLimits::WeightAndDeposit { .. } => {
471				math::substrate_execution::eth_gas_consumed(self)
472			},
473		}
474	}
475
476	/// Take a snapshot of the meter's current consumption for later use with
477	/// [`Self::delta_since`].
478	pub fn snapshot(&self) -> MeterSnapshot<T> {
479		MeterSnapshot { weight: self.weight_consumed(), gas: self.eth_gas_consumed_signed() }
480	}
481
482	/// Ethereum gas and weight consumed since `snapshot` was taken.
483	///
484	/// Gas subtraction happens in [`SignedGas`] form so that the ceil-rounding inside
485	/// `to_ethereum_gas` is applied once to the delta, not to each snapshot.
486	pub fn delta_since(&self, snapshot: &MeterSnapshot<T>) -> (u64, Weight) {
487		let gas = self
488			.eth_gas_consumed_signed()
489			.saturating_sub(&snapshot.gas)
490			.to_ethereum_gas()
491			.unwrap_or_default()
492			.try_into()
493			.unwrap_or(u64::MAX);
494		let weight = self.weight_consumed().saturating_sub(snapshot.weight);
495		(gas, weight)
496	}
497
498	/// Determine and set the new effective weight limit of the weight meter.
499	///
500	/// This function needs to be called whenever there is a change in the deposit meter. It is a
501	/// function of `ResourceMeter` instead of `WeightMeter` because its outcome also depends on the
502	/// consumed storage deposits.
503	fn adjust_effective_weight_limit(&mut self) -> DispatchResult {
504		if matches!(self.transaction_limits, TransactionLimits::WeightAndDeposit { .. }) {
505			return Ok(());
506		}
507
508		if let Some(weight_left) = self.weight_left() {
509			let new_effective_limit = self.weight.weight_consumed().saturating_add(weight_left);
510			self.weight.set_effective_weight_limit(new_effective_limit);
511			Ok(())
512		} else {
513			Err(<Error<T>>::OutOfGas.into())
514		}
515	}
516}
517
518impl<T: Config> TransactionMeter<T> {
519	/// Create a new transaction-level meter with the specified resource limits.
520	///
521	/// Initializes either:
522	/// - An ethereum-style gas-based meter or
523	/// - A substrate-style meter with explicit weight and deposit limits
524	pub fn new(transaction_limits: TransactionLimits<T>) -> Result<Self, DispatchError> {
525		log::debug!(
526			target: LOG_TARGET,
527			"Start new meter: transaction_limits={transaction_limits:?}",
528		);
529
530		let mut transaction_meter = match transaction_limits {
531			TransactionLimits::EthereumGas { eth_gas_limit, weight_limit, eth_tx_info } => {
532				math::ethereum_execution::new_root(eth_gas_limit, weight_limit, eth_tx_info)
533			},
534			TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => {
535				math::substrate_execution::new_root(weight_limit, deposit_limit)
536			},
537		}?;
538
539		transaction_meter.adjust_effective_weight_limit()?;
540
541		log::trace!(
542			target: LOG_TARGET,
543			"New meter done: \
544				weight_left={:?}, \
545				deposit_left={:?}, \
546				weight_consumed={:?}, \
547				deposit_consumed={:?}",
548			transaction_meter.weight_left(),
549			transaction_meter.deposit_left(),
550			transaction_meter.weight_consumed(),
551			transaction_meter.deposit_consumed(),
552		);
553
554		Ok(transaction_meter)
555	}
556
557	/// Convenience constructor for substrate-style weight+deposit limits.
558	pub fn new_from_limits(
559		weight_limit: Weight,
560		deposit_limit: BalanceOf<T>,
561	) -> Result<Self, DispatchError> {
562		Self::new(TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit })
563	}
564
565	/// Execute all postponed storage deposit operations.
566	///
567	/// Returns `Err(Error::StorageDepositNotEnoughFunds)` if deposit limit would be exceeded.
568	pub fn execute_postponed_deposits(
569		&mut self,
570		origin: &Origin<T>,
571		exec_config: &ExecConfig<T>,
572	) -> Result<DepositOf<T>, DispatchError> {
573		log::debug!(
574			target: LOG_TARGET,
575			"Transaction meter finishes: \
576				weight_left={:?}, \
577				deposit_left={:?}, \
578				weight_consumed={:?}, \
579				deposit_consumed={:?}, \
580				eth_gas_consumed={:?}",
581			self.weight_left(),
582			self.deposit_left(),
583			self.weight_consumed(),
584			self.deposit_consumed(),
585			self.eth_gas_consumed(),
586		);
587
588		if self.deposit_left().is_none() {
589			// Deposit limit exceeded
590			return Err(<Error<T>>::StorageDepositNotEnoughFunds.into());
591		}
592
593		self.deposit.execute_postponed_deposits(origin, exec_config)
594	}
595
596	/// Mark a contract as terminated
597	///
598	/// This will signal to the meter to discard all charged and refunds incured by this
599	/// contract. Furthermore it will record that there was a refund of `refunded` and adapt the
600	/// total deposit accordingly
601	pub fn terminate(&mut self, contract_account: T::AccountId, refunded: BalanceOf<T>) {
602		self.deposit.terminate(contract_account, refunded);
603	}
604}
605
606impl<T: Config> FrameMeter<T> {
607	/// Record a contract's storage deposit and schedule the transfer.
608	///
609	/// Updates the frame's deposit accounting and schedules the actual token transfer
610	/// for later execution – at the end of the transaction execution.
611	pub fn charge_contract_deposit_and_transfer(
612		&mut self,
613		contract: T::AccountId,
614		amount: DepositOf<T>,
615	) -> DispatchResult {
616		log::trace!(
617			target: LOG_TARGET,
618			"Charge deposit and transfer: \
619				amount={:?}, \
620				deposit_left={:?}, \
621				deposit_consumed={:?}, \
622				max_charged={:?}",
623			amount,
624			self.deposit_left(),
625			self.deposit_consumed(),
626			self.deposit.max_charged(),
627		);
628
629		self.deposit.charge_deposit(contract, amount);
630		self.adjust_effective_weight_limit()
631	}
632
633	/// Record storage changes of a contract.
634	pub fn record_contract_storage_changes(&mut self, diff: &Diff) -> DispatchResult {
635		log::trace!(
636			target: LOG_TARGET,
637			"Charge contract storage: \
638				diff={:?}, \
639				deposit_left={:?}, \
640				deposit_consumed={:?}, \
641				max_charged={:?}",
642			diff,
643			self.deposit_left(),
644			self.deposit_consumed(),
645			self.deposit.max_charged(),
646		);
647
648		self.deposit.charge(diff);
649		self.adjust_effective_weight_limit()
650	}
651
652	/// [`Self::charge_contract_deposit_and_transfer`] and [`Self::record_contract_storage_changes`]
653	/// does not enforce the storage limit since we want to do this check as late as possible to
654	/// allow later refunds to offset earlier charges.
655	pub fn finalize(&mut self, info: Option<&mut ContractInfo<T>>) -> DispatchResult {
656		self.deposit.finalize_own_contributions(info);
657
658		if self.deposit_left().is_none() {
659			return Err(<Error<T>>::StorageDepositLimitExhausted.into());
660		}
661
662		Ok(())
663	}
664
665	/// Apply pending storage changes to a ContractInfo without finalizing the meter.
666	///
667	/// This is used before creating a nested frame to ensure the child frame can see
668	/// the parent's pending storage changes when calculating refunds. This fixes the issue
669	/// where storage deposit refunds fail in subframes because the parent's pending
670	/// charges haven't been committed to ContractInfo yet.
671	///
672	/// See: <https://github.com/paritytech/contract-issues/issues/213>
673	pub fn apply_pending_storage_changes(&self, info: &mut ContractInfo<T>) {
674		self.deposit.apply_pending_changes_to_contract(info);
675	}
676}
677
678/// Ethereum transaction context for gas conversions.
679///
680/// Contains the parameters needed to convert between ethereum gas and substrate resources
681/// (weight/deposit)
682#[derive(DebugNoBound, Clone)]
683pub struct EthTxInfo<T: Config> {
684	/// The encoding length of the extrinsic
685	pub encoded_len: u32,
686	/// The extra weight of the transaction. The total weight of the extrinsic is `extra_weight` +
687	/// the weight consumed during smart contract execution.
688	pub extra_weight: Weight,
689	_phantom: PhantomData<T>,
690}
691
692impl<T: Config> EthTxInfo<T> {
693	/// Create a new ethereum transaction context with the given parameters.
694	pub fn new(encoded_len: u32, extra_weight: Weight) -> Self {
695		Self { encoded_len, extra_weight, _phantom: PhantomData }
696	}
697
698	/// Calculate total gas consumed by weight and storage operations.
699	pub fn gas_consumption(
700		&self,
701		consumed_weight: &Weight,
702		consumed_deposit: &DepositOf<T>,
703	) -> SignedGas<T> {
704		let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
705		let deposit_and_fixed_fee =
706			consumed_deposit.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
707		let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
708
709		let weight_gas = SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee(
710			&consumed_weight.saturating_add(self.extra_weight),
711		));
712
713		deposit_gas.saturating_add(&weight_gas)
714	}
715
716	/// Calculate maximal possible remaining weight that can be consumed given a particular gas
717	/// limit.
718	///
719	/// Returns None if remaining gas would not allow any more weight consumption.
720	pub fn weight_remaining(
721		&self,
722		max_total_gas: &SignedGas<T>,
723		total_weight_consumption: &Weight,
724		total_deposit_consumption: &DepositOf<T>,
725	) -> Option<Weight> {
726		let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
727		let deposit_and_fixed_fee =
728			total_deposit_consumption.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
729		let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
730
731		let consumable_fee = max_total_gas.saturating_sub(&deposit_gas).to_weight_fee()?;
732
733		T::FeeInfo::fee_to_weight(consumable_fee)
734			.checked_sub(&total_weight_consumption.saturating_add(self.extra_weight))
735	}
736}