1// This file is part of Frontier.
23// Copyright (c) Moonsong Labs.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
67// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
1819//! Utils related to Substrate features:
20//! - Substrate call dispatch.
21//! - Substrate DB read and write costs
2223use core::marker::PhantomData;
2425// Substrate
26use frame_support::{
27 dispatch::{GetDispatchInfo, PostDispatchInfo},
28 traits::Get,
29 weights::Weight,
30};
31use sp_runtime::{traits::Dispatchable, DispatchError};
32// Frontier
33use fp_evm::{ExitError, PrecompileFailure, PrecompileHandle};
34use pallet_evm::GasWeightMapping;
3536use crate::{evm::handle::using_precompile_handle, solidity::revert::revert};
3738#[derive(Debug)]
39pub enum TryDispatchError {
40 Evm(ExitError),
41 Substrate(DispatchError),
42}
4344impl From<TryDispatchError> for PrecompileFailure {
45fn from(f: TryDispatchError) -> PrecompileFailure {
46match f {
47 TryDispatchError::Evm(e) => PrecompileFailure::Error { exit_status: e },
48 TryDispatchError::Substrate(e) => {
49 revert(alloc::format!("Dispatched call failed with error: {e:?}"))
50 }
51 }
52 }
53}
5455/// Helper functions requiring a Substrate runtime.
56/// This runtime must of course implement `pallet_evm::Config`.
57#[derive(Clone, Copy, Debug)]
58pub struct RuntimeHelper<Runtime>(PhantomData<Runtime>);
5960impl<Runtime> RuntimeHelper<Runtime>
61where
62Runtime: pallet_evm::Config,
63 Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
64{
65#[inline(always)]
66pub fn record_external_cost(
67 handle: &mut impl PrecompileHandle,
68 weight: Weight,
69 storage_growth: u64,
70 ) -> Result<(), ExitError> {
71// Make sure there is enough gas.
72let remaining_gas = handle.remaining_gas();
73let required_gas = Runtime::GasWeightMapping::weight_to_gas(weight);
74if required_gas > remaining_gas {
75return Err(ExitError::OutOfGas);
76 }
7778// Make sure there is enough remaining weight
79 // TODO: record ref time when precompile will be benchmarked
80handle.record_external_cost(None, Some(weight.proof_size()), Some(storage_growth))
81 }
8283#[inline(always)]
84pub fn refund_weight_v2_cost(
85 handle: &mut impl PrecompileHandle,
86 weight: Weight,
87 maybe_actual_weight: Option<Weight>,
88 ) -> Result<u64, ExitError> {
89// Refund weights and compute used weight them record used gas
90 // TODO: refund ref time when precompile will be benchmarked
91let used_weight = if let Some(actual_weight) = maybe_actual_weight {
92let refund_weight = weight.checked_sub(&actual_weight).unwrap_or_default();
93 handle.refund_external_cost(None, Some(refund_weight.proof_size()));
94 actual_weight
95 } else {
96 weight
97 };
98let used_gas = Runtime::GasWeightMapping::weight_to_gas(used_weight);
99 handle.record_cost(used_gas)?;
100Ok(used_gas)
101 }
102103/// Try to dispatch a Substrate call.
104 /// Return an error if there are not enough gas, or if the call fails.
105 /// If successful returns the used gas using the Runtime GasWeightMapping.
106pub fn try_dispatch<Call>(
107 handle: &mut impl PrecompileHandle,
108 origin: <Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin,
109 call: Call,
110 storage_growth: u64,
111 ) -> Result<PostDispatchInfo, TryDispatchError>
112where
113Runtime::RuntimeCall: From<Call>,
114 {
115let call = Runtime::RuntimeCall::from(call);
116let dispatch_info = call.get_dispatch_info();
117118Self::record_external_cost(handle, dispatch_info.total_weight(), storage_growth)
119 .map_err(TryDispatchError::Evm)?;
120121// Dispatch call.
122 // It may be possible to not record gas cost if the call returns Pays::No.
123 // However while Substrate handle checking weight while not making the sender pay for it,
124 // the EVM doesn't. It seems this safer to always record the costs to avoid unmetered
125 // computations.
126let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin))
127 .map_err(|e| TryDispatchError::Substrate(e.error))?;
128129Self::refund_weight_v2_cost(
130 handle,
131 dispatch_info.total_weight(),
132 post_dispatch_info.actual_weight,
133 )
134 .map_err(TryDispatchError::Evm)?;
135136Ok(post_dispatch_info)
137 }
138}
139140impl<Runtime> RuntimeHelper<Runtime>
141where
142Runtime: pallet_evm::Config,
143{
144/// Cost of a Substrate DB write in gas.
145pub fn db_write_gas_cost() -> u64 {
146 <Runtime as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
147 <Runtime as frame_system::Config>::DbWeight::get().writes(1),
148 )
149 }
150151/// Cost of a Substrate DB read in gas.
152pub fn db_read_gas_cost() -> u64 {
153 <Runtime as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
154 <Runtime as frame_system::Config>::DbWeight::get().reads(1),
155 )
156 }
157}