1mod gas;
19mod math;
20mod storage;
21mod weight;
22
23#[cfg(test)]
24mod tests;
25
26use crate::{
27 evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config,
28 Error, ExecConfig, ExecOrigin as Origin, StorageDeposit, LOG_TARGET,
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
76#[derive(DefaultNoBound)]
78pub struct ResourceMeter<T: Config, S: State> {
79 weight: WeightMeter<T>,
81
82 deposit: GenericStorageMeter<T, S>,
84
85 max_total_gas: SignedGas<T>,
95
96 total_consumed_weight_before: Weight,
98
99 total_consumed_deposit_before: DepositOf<T>,
101
102 transaction_limits: TransactionLimits<T>,
105
106 _phantom: PhantomData<S>,
107}
108
109#[derive(DebugNoBound, Clone)]
115pub enum TransactionLimits<T: Config> {
116 EthereumGas {
118 eth_gas_limit: BalanceOf<T>,
120 maybe_weight_limit: Option<Weight>,
124 eth_tx_info: EthTxInfo<T>,
126 },
127 WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf<T> },
130}
131
132impl<T: Config> Default for TransactionLimits<T> {
133 fn default() -> Self {
134 Self::WeightAndDeposit {
135 weight_limit: Default::default(),
136 deposit_limit: Default::default(),
137 }
138 }
139}
140
141impl<T: Config, S: State> ResourceMeter<T, S> {
142 pub fn new_nested(&self, limit: &CallResources<T>) -> Result<FrameMeter<T>, DispatchError> {
144 log::trace!(
145 target: LOG_TARGET,
146 "Creating nested meter from parent: \
147 limit={limit:?}, \
148 weight_left={:?}, \
149 deposit_left={:?}, \
150 weight_consumed={:?}, \
151 deposit_consumed={:?}",
152 self.weight_left(),
153 self.deposit_left(),
154 self.weight_consumed(),
155 self.deposit_consumed(),
156 );
157
158 let mut new_meter = match &self.transaction_limits {
159 TransactionLimits::EthereumGas { eth_tx_info, .. } =>
160 math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info),
161 TransactionLimits::WeightAndDeposit { .. } =>
162 math::substrate_execution::new_nested_meter(self, limit),
163 }?;
164
165 new_meter.adjust_effective_weight_limit()?;
166
167 log::trace!(
168 target: LOG_TARGET,
169 "Creating nested meter done: \
170 weight_left={:?}, \
171 deposit_left={:?}, \
172 weight_consumed={:?}, \
173 deposit_consumed={:?}",
174 new_meter.weight_left(),
175 new_meter.deposit_left(),
176 new_meter.weight_consumed(),
177 new_meter.deposit_consumed(),
178 );
179
180 Ok(new_meter)
181 }
182
183 pub fn absorb_weight_meter_only(&mut self, other: FrameMeter<T>) {
185 log::trace!(
186 target: LOG_TARGET,
187 "Absorb weight meter only: \
188 parent_weight_left={:?}, \
189 parent_deposit_left={:?}, \
190 parent_weight_consumed={:?}, \
191 parent_deposit_consumed={:?}, \
192 child_weight_left={:?}, \
193 child_deposit_left={:?}, \
194 child_weight_consumed={:?}, \
195 child_deposit_consumed={:?}",
196 self.weight_left(),
197 self.deposit_left(),
198 self.weight_consumed(),
199 self.deposit_consumed(),
200 other.weight_left(),
201 other.deposit_left(),
202 other.weight_consumed(),
203 other.deposit_consumed(),
204 );
205
206 self.weight.absorb_nested(other.weight);
207 self.deposit.absorb_only_max_charged(other.deposit);
208
209 log::trace!(
210 target: LOG_TARGET,
211 "Absorb weight meter done: \
212 parent_weight_left={:?}, \
213 parent_deposit_left={:?}, \
214 parent_weight_consumed={:?}, \
215 parent_deposit_consumed={:?}",
216 self.weight_left(),
217 self.deposit_left(),
218 self.weight_consumed(),
219 self.deposit_consumed(),
220 );
221 }
222
223 pub fn absorb_all_meters(
225 &mut self,
226 other: FrameMeter<T>,
227 contract: &T::AccountId,
228 info: Option<&mut ContractInfo<T>>,
229 ) {
230 log::trace!(
231 target: LOG_TARGET,
232 "Absorb all meters: \
233 parent_weight_left={:?}, \
234 parent_deposit_left={:?}, \
235 parent_weight_consumed={:?}, \
236 parent_deposit_consumed={:?}, \
237 child_weight_left={:?}, \
238 child_deposit_left={:?}, \
239 child_weight_consumed={:?}, \
240 child_deposit_consumed={:?}",
241 self.weight_left(),
242 self.deposit_left(),
243 self.weight_consumed(),
244 self.deposit_consumed(),
245 other.weight_left(),
246 other.deposit_left(),
247 other.weight_consumed(),
248 other.deposit_consumed(),
249 );
250
251 self.weight.absorb_nested(other.weight);
252 self.deposit.absorb(other.deposit, contract, info);
253
254 let result = self.adjust_effective_weight_limit();
255 debug_assert!(result.is_ok(), "Absorbing nested meters should not exceed limits");
256
257 log::trace!(
258 target: LOG_TARGET,
259 "Absorb all meters done: \
260 parent_weight_left={:?}, \
261 parent_deposit_left={:?}, \
262 parent_weight_consumed={:?}, \
263 parent_deposit_consumed={:?}",
264 self.weight_left(),
265 self.deposit_left(),
266 self.weight_consumed(),
267 self.deposit_consumed(),
268 );
269 }
270
271 #[inline]
275 pub fn charge_weight_token<Tok: Token<T>>(
276 &mut self,
277 token: Tok,
278 ) -> Result<ChargedAmount, DispatchError> {
279 self.weight.charge(token)
280 }
281
282 #[inline]
284 pub fn charge_or_halt<Tok: Token<T>>(
285 &mut self,
286 token: Tok,
287 ) -> ControlFlow<Halt, ChargedAmount> {
288 self.weight.charge_or_halt(token)
289 }
290
291 pub fn adjust_weight<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
293 self.weight.adjust_weight(charged_amount, token);
294 }
295
296 pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
302 self.weight.sync_from_executor(engine_fuel)
303 }
304
305 pub fn sync_to_executor(&mut self) -> polkavm::Gas {
311 self.weight.sync_to_executor()
312 }
313
314 pub fn consume_all_weight(&mut self) {
316 self.weight.consume_all();
317 }
318
319 pub fn charge_deposit(&mut self, deposit: &DepositOf<T>) -> DispatchResult {
321 log::trace!(
322 target: LOG_TARGET,
323 "Charge deposit: \
324 deposit={:?}, \
325 deposit_left={:?}, \
326 deposit_consumed={:?}, \
327 max_charged={:?}",
328 deposit,
329 self.deposit_left(),
330 self.deposit_consumed(),
331 self.deposit.max_charged(),
332 );
333
334 self.deposit.record_charge(deposit);
335 self.adjust_effective_weight_limit()?;
336
337 if self.deposit.is_root {
338 if self.deposit_left().is_none() {
339 self.deposit.reset();
340 self.adjust_effective_weight_limit()?;
341 return Err(<Error<T>>::StorageDepositLimitExhausted.into());
342 }
343 }
344
345 Ok(())
346 }
347
348 pub fn eth_gas_left(&self) -> Option<BalanceOf<T>> {
355 let gas_left = match &self.transaction_limits {
356 TransactionLimits::EthereumGas { eth_tx_info, .. } =>
357 math::ethereum_execution::gas_left(self, eth_tx_info),
358 TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::gas_left(self),
359 }?;
360
361 gas_left.to_ethereum_gas()
362 }
363
364 pub fn weight_left(&self) -> Option<Weight> {
371 match &self.transaction_limits {
372 TransactionLimits::EthereumGas { eth_tx_info, .. } =>
373 math::ethereum_execution::weight_left(self, eth_tx_info),
374 TransactionLimits::WeightAndDeposit { .. } =>
375 math::substrate_execution::weight_left(self),
376 }
377 }
378
379 pub fn deposit_left(&self) -> Option<BalanceOf<T>> {
386 match &self.transaction_limits {
387 TransactionLimits::EthereumGas { eth_tx_info, .. } =>
388 math::ethereum_execution::deposit_left(self, eth_tx_info),
389 TransactionLimits::WeightAndDeposit { .. } =>
390 math::substrate_execution::deposit_left(self),
391 }
392 }
393
394 pub fn total_consumed_gas(&self) -> BalanceOf<T> {
401 let signed_gas = match &self.transaction_limits {
402 TransactionLimits::EthereumGas { eth_tx_info, .. } =>
403 math::ethereum_execution::total_consumed_gas(self, eth_tx_info),
404 TransactionLimits::WeightAndDeposit { .. } =>
405 math::substrate_execution::total_consumed_gas(self),
406 };
407
408 signed_gas.to_ethereum_gas().unwrap_or_default()
409 }
410
411 pub fn weight_consumed(&self) -> Weight {
413 self.weight.weight_consumed()
414 }
415
416 pub fn weight_required(&self) -> Weight {
421 self.weight.weight_required()
422 }
423
424 pub fn deposit_consumed(&self) -> DepositOf<T> {
428 self.deposit.consumed()
429 }
430
431 pub fn deposit_required(&self) -> DepositOf<T> {
436 self.deposit.max_charged()
437 }
438
439 pub fn eth_gas_consumed(&self) -> BalanceOf<T> {
441 let signed_gas = match &self.transaction_limits {
442 TransactionLimits::EthereumGas { eth_tx_info, .. } =>
443 math::ethereum_execution::eth_gas_consumed(self, eth_tx_info),
444 TransactionLimits::WeightAndDeposit { .. } =>
445 math::substrate_execution::eth_gas_consumed(self),
446 };
447
448 signed_gas.to_ethereum_gas().unwrap_or_default()
449 }
450
451 fn adjust_effective_weight_limit(&mut self) -> DispatchResult {
457 if matches!(self.transaction_limits, TransactionLimits::WeightAndDeposit { .. }) {
458 return Ok(())
459 }
460
461 if let Some(weight_left) = self.weight_left() {
462 let new_effective_limit = self.weight.weight_consumed().saturating_add(weight_left);
463 self.weight.set_effective_weight_limit(new_effective_limit);
464 Ok(())
465 } else {
466 Err(<Error<T>>::OutOfGas.into())
467 }
468 }
469}
470
471impl<T: Config> TransactionMeter<T> {
472 pub fn new(transaction_limits: TransactionLimits<T>) -> Result<Self, DispatchError> {
478 log::debug!(
479 target: LOG_TARGET,
480 "Start new meter: transaction_limits={transaction_limits:?}",
481 );
482
483 let mut transaction_meter = match transaction_limits {
484 TransactionLimits::EthereumGas { eth_gas_limit, maybe_weight_limit, eth_tx_info } =>
485 math::ethereum_execution::new_root(eth_gas_limit, maybe_weight_limit, eth_tx_info),
486 TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } =>
487 math::substrate_execution::new_root(weight_limit, deposit_limit),
488 }?;
489
490 transaction_meter.adjust_effective_weight_limit()?;
491
492 log::trace!(
493 target: LOG_TARGET,
494 "New meter done: \
495 weight_left={:?}, \
496 deposit_left={:?}, \
497 weight_consumed={:?}, \
498 deposit_consumed={:?}",
499 transaction_meter.weight_left(),
500 transaction_meter.deposit_left(),
501 transaction_meter.weight_consumed(),
502 transaction_meter.deposit_consumed(),
503 );
504
505 Ok(transaction_meter)
506 }
507
508 pub fn new_from_limits(
510 weight_limit: Weight,
511 deposit_limit: BalanceOf<T>,
512 ) -> Result<Self, DispatchError> {
513 Self::new(TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit })
514 }
515
516 pub fn execute_postponed_deposits(
520 &mut self,
521 origin: &Origin<T>,
522 exec_config: &ExecConfig<T>,
523 ) -> Result<DepositOf<T>, DispatchError> {
524 log::debug!(
525 target: LOG_TARGET,
526 "Transaction meter finishes: \
527 weight_left={:?}, \
528 deposit_left={:?}, \
529 weight_consumed={:?}, \
530 deposit_consumed={:?}, \
531 eth_gas_consumed={:?}",
532 self.weight_left(),
533 self.deposit_left(),
534 self.weight_consumed(),
535 self.deposit_consumed(),
536 self.eth_gas_consumed(),
537 );
538
539 if self.deposit_left().is_none() {
540 return Err(<Error<T>>::StorageDepositNotEnoughFunds.into());
542 }
543
544 self.deposit.execute_postponed_deposits(origin, exec_config)
545 }
546
547 pub fn terminate(&mut self, contract_account: T::AccountId, refunded: BalanceOf<T>) {
553 self.deposit.terminate(contract_account, refunded);
554 }
555}
556
557impl<T: Config> FrameMeter<T> {
558 pub fn charge_contract_deposit_and_transfer(
563 &mut self,
564 contract: T::AccountId,
565 amount: DepositOf<T>,
566 ) -> DispatchResult {
567 log::trace!(
568 target: LOG_TARGET,
569 "Charge deposit and transfer: \
570 amount={:?}, \
571 deposit_left={:?}, \
572 deposit_consumed={:?}, \
573 max_charged={:?}",
574 amount,
575 self.deposit_left(),
576 self.deposit_consumed(),
577 self.deposit.max_charged(),
578 );
579
580 self.deposit.charge_deposit(contract, amount);
581 self.adjust_effective_weight_limit()
582 }
583
584 pub fn record_contract_storage_changes(&mut self, diff: &Diff) -> DispatchResult {
586 log::trace!(
587 target: LOG_TARGET,
588 "Charge contract storage: \
589 diff={:?}, \
590 deposit_left={:?}, \
591 deposit_consumed={:?}, \
592 max_charged={:?}",
593 diff,
594 self.deposit_left(),
595 self.deposit_consumed(),
596 self.deposit.max_charged(),
597 );
598
599 self.deposit.charge(diff);
600 self.adjust_effective_weight_limit()
601 }
602
603 pub fn finalize(&mut self, info: Option<&mut ContractInfo<T>>) -> DispatchResult {
607 self.deposit.finalize_own_contributions(info);
608
609 if self.deposit_left().is_none() {
610 return Err(<Error<T>>::StorageDepositLimitExhausted.into());
611 }
612
613 Ok(())
614 }
615}
616
617#[derive(DebugNoBound, Clone)]
622pub struct EthTxInfo<T: Config> {
623 pub encoded_len: u32,
625 pub extra_weight: Weight,
628 _phantom: PhantomData<T>,
629}
630
631impl<T: Config> EthTxInfo<T> {
632 pub fn new(encoded_len: u32, extra_weight: Weight) -> Self {
634 Self { encoded_len, extra_weight, _phantom: PhantomData }
635 }
636
637 pub fn gas_consumption(
639 &self,
640 consumed_weight: &Weight,
641 consumed_deposit: &DepositOf<T>,
642 ) -> SignedGas<T> {
643 let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
644 let deposit_and_fixed_fee =
645 consumed_deposit.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
646 let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
647
648 let weight_gas = SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee(
649 &consumed_weight.saturating_add(self.extra_weight),
650 ));
651
652 deposit_gas.saturating_add(&weight_gas)
653 }
654
655 pub fn weight_remaining(
660 &self,
661 max_total_gas: &SignedGas<T>,
662 total_weight_consumption: &Weight,
663 total_deposit_consumption: &DepositOf<T>,
664 ) -> Option<Weight> {
665 let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
666 let deposit_and_fixed_fee =
667 total_deposit_consumption.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
668 let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
669
670 let consumable_fee = max_total_gas.saturating_sub(&deposit_gas).to_weight_fee()?;
671
672 T::FeeInfo::fee_to_weight(consumable_fee)
673 .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight))
674 }
675}