precompile_utils/
substrate.rs

1// This file is part of Frontier.
2
3// Copyright (c) Moonsong Labs.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
6
7// 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.
18
19//! Utils related to Substrate features:
20//! - Substrate call dispatch.
21//! - Substrate DB read and write costs
22
23use core::marker::PhantomData;
24
25// 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;
35
36use crate::{evm::handle::using_precompile_handle, solidity::revert::revert};
37
38#[derive(Debug)]
39pub enum TryDispatchError {
40	Evm(ExitError),
41	Substrate(DispatchError),
42}
43
44impl From<TryDispatchError> for PrecompileFailure {
45	fn from(f: TryDispatchError) -> PrecompileFailure {
46		match 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}
54
55/// 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>);
59
60impl<Runtime> RuntimeHelper<Runtime>
61where
62	Runtime: pallet_evm::Config,
63	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
64{
65	#[inline(always)]
66	pub 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.
72		let remaining_gas = handle.remaining_gas();
73		let required_gas = Runtime::GasWeightMapping::weight_to_gas(weight);
74		if required_gas > remaining_gas {
75			return Err(ExitError::OutOfGas);
76		}
77
78		// Make sure there is enough remaining weight
79		// TODO: record ref time when precompile will be benchmarked
80		handle.record_external_cost(None, Some(weight.proof_size()), Some(storage_growth))
81	}
82
83	#[inline(always)]
84	pub 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
91		let used_weight = if let Some(actual_weight) = maybe_actual_weight {
92			let 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		};
98		let used_gas = Runtime::GasWeightMapping::weight_to_gas(used_weight);
99		handle.record_cost(used_gas)?;
100		Ok(used_gas)
101	}
102
103	/// 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.
106	pub 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>
112	where
113		Runtime::RuntimeCall: From<Call>,
114	{
115		let call = Runtime::RuntimeCall::from(call);
116		let dispatch_info = call.get_dispatch_info();
117
118		Self::record_external_cost(handle, dispatch_info.total_weight(), storage_growth)
119			.map_err(TryDispatchError::Evm)?;
120
121		// 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.
126		let post_dispatch_info = using_precompile_handle(handle, || call.dispatch(origin))
127			.map_err(|e| TryDispatchError::Substrate(e.error))?;
128
129		Self::refund_weight_v2_cost(
130			handle,
131			dispatch_info.total_weight(),
132			post_dispatch_info.actual_weight,
133		)
134		.map_err(TryDispatchError::Evm)?;
135
136		Ok(post_dispatch_info)
137	}
138}
139
140impl<Runtime> RuntimeHelper<Runtime>
141where
142	Runtime: pallet_evm::Config,
143{
144	/// Cost of a Substrate DB write in gas.
145	pub 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	}
150
151	/// Cost of a Substrate DB read in gas.
152	pub 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}