1mod 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
45pub trait State: private::Sealed + Default + Debug {}
49
50#[derive(Default, Debug)]
54pub struct Root;
55
56#[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
71pub type TransactionMeter<T> = ResourceMeter<T, Root>;
73pub type FrameMeter<T> = ResourceMeter<T, Nested>;
75
76pub struct MeterSnapshot<T: Config> {
80 weight: Weight,
81 gas: SignedGas<T>,
82}
83
84#[derive(DefaultNoBound)]
86pub struct ResourceMeter<T: Config, S: State> {
87 weight: WeightMeter<T>,
89
90 deposit: GenericStorageMeter<T, S>,
92
93 max_total_gas: SignedGas<T>,
103
104 total_consumed_weight_before: Weight,
106
107 total_consumed_deposit_before: DepositOf<T>,
109
110 transaction_limits: TransactionLimits<T>,
113
114 _phantom: PhantomData<S>,
115}
116
117#[derive(DebugNoBound, Clone)]
123pub enum TransactionLimits<T: Config> {
124 EthereumGas {
126 eth_gas_limit: BalanceOf<T>,
128 weight_limit: Weight,
132 eth_tx_info: EthTxInfo<T>,
134 },
135 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 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 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 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 #[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 #[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 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 pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
312 self.weight.sync_from_executor(engine_fuel)
313 }
314
315 pub fn sync_to_executor(&mut self) -> polkavm::Gas {
321 self.weight.sync_to_executor()
322 }
323
324 pub fn consume_all_weight(&mut self) {
326 self.weight.consume_all();
327 }
328
329 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 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 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 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 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 pub fn weight_consumed(&self) -> Weight {
430 self.weight.weight_consumed()
431 }
432
433 pub fn weight_required(&self) -> Weight {
438 self.weight.weight_required()
439 }
440
441 pub fn deposit_consumed(&self) -> DepositOf<T> {
445 self.deposit.consumed()
446 }
447
448 pub fn deposit_required(&self) -> DepositOf<T> {
453 self.deposit.max_charged()
454 }
455
456 pub fn eth_gas_consumed(&self) -> BalanceOf<T> {
458 self.eth_gas_consumed_signed().to_ethereum_gas().unwrap_or_default()
459 }
460
461 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 pub fn snapshot(&self) -> MeterSnapshot<T> {
479 MeterSnapshot { weight: self.weight_consumed(), gas: self.eth_gas_consumed_signed() }
480 }
481
482 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 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 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 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 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 return Err(<Error<T>>::StorageDepositNotEnoughFunds.into());
591 }
592
593 self.deposit.execute_postponed_deposits(origin, exec_config)
594 }
595
596 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 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 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 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 pub fn apply_pending_storage_changes(&self, info: &mut ContractInfo<T>) {
674 self.deposit.apply_pending_changes_to_contract(info);
675 }
676}
677
678#[derive(DebugNoBound, Clone)]
683pub struct EthTxInfo<T: Config> {
684 pub encoded_len: u32,
686 pub extra_weight: Weight,
689 _phantom: PhantomData<T>,
690}
691
692impl<T: Config> EthTxInfo<T> {
693 pub fn new(encoded_len: u32, extra_weight: Weight) -> Self {
695 Self { encoded_len, extra_weight, _phantom: PhantomData }
696 }
697
698 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 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}