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)]
128pub enum TransactionLimits<T: Config> {
129 EthereumGas {
131 eth_gas_limit: BalanceOf<T>,
133 weight_limit: Weight,
137 eth_tx_info: EthTxInfo<T>,
139 authorization_deposit: StorageDeposit<BalanceOf<T>>,
145 },
146 WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf<T> },
149}
150
151impl<T: Config> Default for TransactionLimits<T> {
152 fn default() -> Self {
153 Self::WeightAndDeposit {
154 weight_limit: Default::default(),
155 deposit_limit: Default::default(),
156 }
157 }
158}
159
160impl<T: Config, S: State> ResourceMeter<T, S> {
161 pub fn new_nested(&self, limit: &CallResources<T>) -> Result<FrameMeter<T>, DispatchError> {
163 log::trace!(
164 target: LOG_TARGET,
165 "Creating nested meter from parent: \
166 limit={limit:?}, \
167 weight_left={:?}, \
168 deposit_left={:?}, \
169 weight_consumed={:?}, \
170 deposit_consumed={:?}",
171 self.weight_left(),
172 self.deposit_left(),
173 self.weight_consumed(),
174 self.deposit_consumed(),
175 );
176
177 let mut new_meter = match &self.transaction_limits {
178 TransactionLimits::EthereumGas { eth_tx_info, .. } => {
179 math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info)
180 },
181 TransactionLimits::WeightAndDeposit { .. } => {
182 math::substrate_execution::new_nested_meter(self, limit)
183 },
184 }?;
185
186 new_meter.adjust_effective_weight_limit()?;
187
188 log::trace!(
189 target: LOG_TARGET,
190 "Creating nested meter done: \
191 weight_left={:?}, \
192 deposit_left={:?}, \
193 weight_consumed={:?}, \
194 deposit_consumed={:?}",
195 new_meter.weight_left(),
196 new_meter.deposit_left(),
197 new_meter.weight_consumed(),
198 new_meter.deposit_consumed(),
199 );
200
201 Ok(new_meter)
202 }
203
204 pub fn absorb_weight_meter_only(&mut self, other: FrameMeter<T>) {
206 log::trace!(
207 target: LOG_TARGET,
208 "Absorb weight meter only: \
209 parent_weight_left={:?}, \
210 parent_deposit_left={:?}, \
211 parent_weight_consumed={:?}, \
212 parent_deposit_consumed={:?}, \
213 child_weight_left={:?}, \
214 child_deposit_left={:?}, \
215 child_weight_consumed={:?}, \
216 child_deposit_consumed={:?}",
217 self.weight_left(),
218 self.deposit_left(),
219 self.weight_consumed(),
220 self.deposit_consumed(),
221 other.weight_left(),
222 other.deposit_left(),
223 other.weight_consumed(),
224 other.deposit_consumed(),
225 );
226
227 self.weight.absorb_nested(other.weight);
228 self.deposit.absorb_only_max_charged(other.deposit);
229
230 log::trace!(
231 target: LOG_TARGET,
232 "Absorb weight meter done: \
233 parent_weight_left={:?}, \
234 parent_deposit_left={:?}, \
235 parent_weight_consumed={:?}, \
236 parent_deposit_consumed={:?}",
237 self.weight_left(),
238 self.deposit_left(),
239 self.weight_consumed(),
240 self.deposit_consumed(),
241 );
242 }
243
244 pub fn absorb_all_meters(
246 &mut self,
247 other: FrameMeter<T>,
248 contract: &T::AccountId,
249 info: Option<&mut ContractInfo<T>>,
250 ) {
251 log::trace!(
252 target: LOG_TARGET,
253 "Absorb all meters: \
254 parent_weight_left={:?}, \
255 parent_deposit_left={:?}, \
256 parent_weight_consumed={:?}, \
257 parent_deposit_consumed={:?}, \
258 child_weight_left={:?}, \
259 child_deposit_left={:?}, \
260 child_weight_consumed={:?}, \
261 child_deposit_consumed={:?}",
262 self.weight_left(),
263 self.deposit_left(),
264 self.weight_consumed(),
265 self.deposit_consumed(),
266 other.weight_left(),
267 other.deposit_left(),
268 other.weight_consumed(),
269 other.deposit_consumed(),
270 );
271
272 self.weight.absorb_nested(other.weight);
273 self.deposit.absorb(other.deposit, contract, info);
274
275 let result = self.adjust_effective_weight_limit();
276 debug_assert!(result.is_ok(), "Absorbing nested meters should not exceed limits");
277
278 log::trace!(
279 target: LOG_TARGET,
280 "Absorb all meters done: \
281 parent_weight_left={:?}, \
282 parent_deposit_left={:?}, \
283 parent_weight_consumed={:?}, \
284 parent_deposit_consumed={:?}",
285 self.weight_left(),
286 self.deposit_left(),
287 self.weight_consumed(),
288 self.deposit_consumed(),
289 );
290 }
291
292 #[inline]
296 pub fn charge_weight_token<Tok: Token<T>>(
297 &mut self,
298 token: Tok,
299 ) -> Result<ChargedAmount, DispatchError> {
300 self.weight.charge(token)
301 }
302
303 #[inline]
305 pub fn charge_or_halt<Tok: Token<T>>(
306 &mut self,
307 token: Tok,
308 ) -> ControlFlow<Halt, ChargedAmount> {
309 self.weight.charge_or_halt(token)
310 }
311
312 pub fn adjust_weight<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
314 self.weight.adjust_weight(charged_amount, token);
315 }
316
317 pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
323 self.weight.sync_from_executor(engine_fuel)
324 }
325
326 pub fn sync_to_executor(&mut self) -> polkavm::Gas {
332 self.weight.sync_to_executor()
333 }
334
335 pub fn consume_all_weight(&mut self) {
337 self.weight.consume_all();
338 }
339
340 pub fn charge_deposit(&mut self, deposit: &DepositOf<T>) -> DispatchResult {
342 log::trace!(
343 target: LOG_TARGET,
344 "Charge deposit: \
345 deposit={:?}, \
346 deposit_left={:?}, \
347 deposit_consumed={:?}, \
348 max_charged={:?}",
349 deposit,
350 self.deposit_left(),
351 self.deposit_consumed(),
352 self.deposit.max_charged(),
353 );
354
355 if let StorageDeposit::Charge(amount) = deposit {
356 if self.deposit.is_root && self.deposit_left().map_or(true, |left| left < *amount) {
357 return Err(<Error<T>>::StorageDepositLimitExhausted.into());
358 }
359 }
360
361 self.deposit.record_charge(deposit);
362 self.adjust_effective_weight_limit()
363 }
364
365 pub fn eth_gas_left(&self) -> Option<BalanceOf<T>> {
372 let gas_left = match &self.transaction_limits {
373 TransactionLimits::EthereumGas { eth_tx_info, .. } => {
374 math::ethereum_execution::gas_left(self, eth_tx_info)
375 },
376 TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::gas_left(self),
377 }?;
378
379 gas_left.to_ethereum_gas()
380 }
381
382 pub fn weight_left(&self) -> Option<Weight> {
389 match &self.transaction_limits {
390 TransactionLimits::EthereumGas { eth_tx_info, .. } => {
391 math::ethereum_execution::weight_left(self, eth_tx_info)
392 },
393 TransactionLimits::WeightAndDeposit { .. } => {
394 math::substrate_execution::weight_left(self)
395 },
396 }
397 }
398
399 pub fn deposit_left(&self) -> Option<BalanceOf<T>> {
406 match &self.transaction_limits {
407 TransactionLimits::EthereumGas { eth_tx_info, .. } => {
408 math::ethereum_execution::deposit_left(self, eth_tx_info)
409 },
410 TransactionLimits::WeightAndDeposit { .. } => {
411 math::substrate_execution::deposit_left(self)
412 },
413 }
414 }
415
416 pub fn total_consumed_gas(&self) -> BalanceOf<T> {
423 let signed_gas = match &self.transaction_limits {
424 TransactionLimits::EthereumGas { eth_tx_info, .. } => {
425 math::ethereum_execution::total_consumed_gas(self, eth_tx_info)
426 },
427 TransactionLimits::WeightAndDeposit { .. } => {
428 math::substrate_execution::total_consumed_gas(self)
429 },
430 };
431
432 signed_gas.to_ethereum_gas().unwrap_or_default()
433 }
434
435 pub fn weight_consumed(&self) -> Weight {
437 self.weight.weight_consumed()
438 }
439
440 pub fn weight_required(&self) -> Weight {
445 self.weight.weight_required()
446 }
447
448 pub fn deposit_consumed(&self) -> DepositOf<T> {
452 self.deposit.consumed()
453 }
454
455 pub fn deposit_required(&self) -> DepositOf<T> {
460 self.deposit.max_charged()
461 }
462
463 pub fn eth_gas_consumed(&self) -> BalanceOf<T> {
465 self.eth_gas_consumed_signed().to_ethereum_gas().unwrap_or_default()
466 }
467
468 pub fn eth_gas_consumed_signed(&self) -> SignedGas<T> {
473 match &self.transaction_limits {
474 TransactionLimits::EthereumGas { eth_tx_info, .. } => {
475 math::ethereum_execution::eth_gas_consumed(self, eth_tx_info)
476 },
477 TransactionLimits::WeightAndDeposit { .. } => {
478 math::substrate_execution::eth_gas_consumed(self)
479 },
480 }
481 }
482
483 pub fn snapshot(&self) -> MeterSnapshot<T> {
486 MeterSnapshot { weight: self.weight_consumed(), gas: self.eth_gas_consumed_signed() }
487 }
488
489 pub fn delta_since(&self, snapshot: &MeterSnapshot<T>) -> (u64, Weight) {
494 let gas = self
495 .eth_gas_consumed_signed()
496 .saturating_sub(&snapshot.gas)
497 .to_ethereum_gas()
498 .unwrap_or_default()
499 .try_into()
500 .unwrap_or(u64::MAX);
501 let weight = self.weight_consumed().saturating_sub(snapshot.weight);
502 (gas, weight)
503 }
504
505 fn adjust_effective_weight_limit(&mut self) -> DispatchResult {
511 if matches!(self.transaction_limits, TransactionLimits::WeightAndDeposit { .. }) {
512 return Ok(());
513 }
514
515 if let Some(weight_left) = self.weight_left() {
516 let new_effective_limit = self.weight.weight_consumed().saturating_add(weight_left);
517 self.weight.set_effective_weight_limit(new_effective_limit);
518 Ok(())
519 } else {
520 Err(<Error<T>>::OutOfGas.into())
521 }
522 }
523}
524
525impl<T: Config> TransactionMeter<T> {
526 pub fn new(transaction_limits: TransactionLimits<T>) -> Result<Self, DispatchError> {
532 log::debug!(
533 target: LOG_TARGET,
534 "Start new meter: transaction_limits={transaction_limits:?}",
535 );
536
537 let mut transaction_meter = match transaction_limits {
538 TransactionLimits::EthereumGas {
539 eth_gas_limit,
540 weight_limit,
541 eth_tx_info,
542 authorization_deposit,
543 } => {
544 let mut meter =
545 math::ethereum_execution::new_root(eth_gas_limit, weight_limit, eth_tx_info)?;
546 if !authorization_deposit.is_zero() {
547 meter.deposit.record_charge(&authorization_deposit);
548 }
549 meter
550 },
551 TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => {
552 math::substrate_execution::new_root(weight_limit, deposit_limit)?
553 },
554 };
555
556 transaction_meter.adjust_effective_weight_limit()?;
557
558 log::trace!(
559 target: LOG_TARGET,
560 "New meter done: \
561 weight_left={:?}, \
562 deposit_left={:?}, \
563 weight_consumed={:?}, \
564 deposit_consumed={:?}",
565 transaction_meter.weight_left(),
566 transaction_meter.deposit_left(),
567 transaction_meter.weight_consumed(),
568 transaction_meter.deposit_consumed(),
569 );
570
571 Ok(transaction_meter)
572 }
573
574 pub fn new_from_limits(
576 weight_limit: Weight,
577 deposit_limit: BalanceOf<T>,
578 ) -> Result<Self, DispatchError> {
579 Self::new(TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit })
580 }
581
582 pub fn execute_postponed_deposits(
586 &mut self,
587 origin: &Origin<T>,
588 exec_config: &ExecConfig<T>,
589 ) -> Result<DepositOf<T>, DispatchError> {
590 log::debug!(
591 target: LOG_TARGET,
592 "Transaction meter finishes: \
593 weight_left={:?}, \
594 deposit_left={:?}, \
595 weight_consumed={:?}, \
596 deposit_consumed={:?}, \
597 eth_gas_consumed={:?}",
598 self.weight_left(),
599 self.deposit_left(),
600 self.weight_consumed(),
601 self.deposit_consumed(),
602 self.eth_gas_consumed(),
603 );
604
605 if self.deposit_left().is_none() {
606 return Err(<Error<T>>::StorageDepositNotEnoughFunds.into());
608 }
609
610 self.deposit.execute_postponed_deposits(origin, exec_config)
611 }
612
613 pub fn terminate(&mut self, contract_account: T::AccountId, refunded: BalanceOf<T>) {
619 self.deposit.terminate(contract_account, refunded);
620 }
621}
622
623impl<T: Config> FrameMeter<T> {
624 pub fn charge_contract_deposit_and_transfer(
629 &mut self,
630 contract: T::AccountId,
631 amount: DepositOf<T>,
632 ) -> DispatchResult {
633 log::trace!(
634 target: LOG_TARGET,
635 "Charge deposit and transfer: \
636 amount={:?}, \
637 deposit_left={:?}, \
638 deposit_consumed={:?}, \
639 max_charged={:?}",
640 amount,
641 self.deposit_left(),
642 self.deposit_consumed(),
643 self.deposit.max_charged(),
644 );
645
646 self.deposit.charge_deposit(contract, amount);
647 self.adjust_effective_weight_limit()
648 }
649
650 pub fn record_contract_storage_changes(&mut self, diff: &Diff) -> DispatchResult {
652 log::trace!(
653 target: LOG_TARGET,
654 "Charge contract storage: \
655 diff={:?}, \
656 deposit_left={:?}, \
657 deposit_consumed={:?}, \
658 max_charged={:?}",
659 diff,
660 self.deposit_left(),
661 self.deposit_consumed(),
662 self.deposit.max_charged(),
663 );
664
665 self.deposit.charge(diff);
666 self.adjust_effective_weight_limit()
667 }
668
669 pub fn finalize(&mut self, info: Option<&mut ContractInfo<T>>) -> DispatchResult {
673 self.deposit.finalize_own_contributions(info);
674
675 if self.deposit_left().is_none() {
676 return Err(<Error<T>>::StorageDepositLimitExhausted.into());
677 }
678
679 Ok(())
680 }
681
682 pub fn apply_pending_storage_changes(&self, info: &mut ContractInfo<T>) {
691 self.deposit.apply_pending_changes_to_contract(info);
692 }
693
694 pub fn bank_pending_storage_changes(
696 &mut self,
697 contract: T::AccountId,
698 info: &mut ContractInfo<T>,
699 ) {
700 self.deposit.bank_pending_changes(contract, info);
701 }
702}
703
704#[derive(DebugNoBound, Clone)]
709pub struct EthTxInfo<T: Config> {
710 pub encoded_len: u32,
712 pub extra_weight: Weight,
715 _phantom: PhantomData<T>,
716}
717
718impl<T: Config> EthTxInfo<T> {
719 pub fn new(encoded_len: u32, extra_weight: Weight) -> Self {
721 Self { encoded_len, extra_weight, _phantom: PhantomData }
722 }
723
724 pub fn gas_consumption(
726 &self,
727 consumed_weight: &Weight,
728 consumed_deposit: &DepositOf<T>,
729 ) -> SignedGas<T> {
730 let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
731 let deposit_and_fixed_fee =
732 consumed_deposit.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
733 let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
734
735 let weight_gas = SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee(
736 &consumed_weight.saturating_add(self.extra_weight),
737 ));
738
739 deposit_gas.saturating_add(&weight_gas)
740 }
741
742 pub fn max_deposit(&self, eth_gas_limit: BalanceOf<T>) -> BalanceOf<T> {
746 let max_gas = SignedGas::<T>::from_ethereum_gas(eth_gas_limit);
747 let overhead_gas =
748 self.gas_consumption(&Weight::zero(), &DepositOf::<T>::Charge(Zero::zero()));
749 let remaining = max_gas.saturating_sub(&overhead_gas);
750 remaining.to_adjusted_deposit_charge().unwrap_or_default()
751 }
752
753 pub fn weight_remaining(
758 &self,
759 max_total_gas: &SignedGas<T>,
760 total_weight_consumption: &Weight,
761 total_deposit_consumption: &DepositOf<T>,
762 ) -> Option<Weight> {
763 let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
764 let deposit_and_fixed_fee =
765 total_deposit_consumption.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
766 let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
767
768 let consumable_fee = max_total_gas.saturating_sub(&deposit_gas).to_weight_fee()?;
769
770 T::FeeInfo::fee_to_weight(consumable_fee)
771 .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight))
772 }
773}