pallet_evm_precompile_dispatch/
lib.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(unused_crate_dependencies)]
20
21extern crate alloc;
22
23#[cfg(test)]
24mod mock;
25
26#[cfg(test)]
27mod tests;
28
29use alloc::format;
30use core::marker::PhantomData;
31
32use scale_codec::{Decode, DecodeLimit};
33// Substrate
34use frame_support::{
35	dispatch::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo},
36	traits::{ConstU32, Get},
37};
38use sp_runtime::traits::Dispatchable;
39// Frontier
40use fp_evm::{
41	ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
42	PrecompileResult,
43};
44use pallet_evm::{AddressMapping, GasWeightMapping};
45
46// `DecodeLimit` specifies the max depth a call can use when decoding, as unbounded depth
47// can be used to overflow the stack.
48// Default value is 8, which is the same as in XCM call decoding.
49pub struct Dispatch<T, DispatchValidator = (), DecodeLimit = ConstU32<8>> {
50	_marker: PhantomData<(T, DispatchValidator, DecodeLimit)>,
51}
52
53impl<T, DispatchValidator, DecodeLimit> Precompile for Dispatch<T, DispatchValidator, DecodeLimit>
54where
55	T: pallet_evm::Config,
56	T::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo + Decode,
57	<T::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<pallet_evm::AccountIdOf<T>>>,
58	DispatchValidator: DispatchValidateT<pallet_evm::AccountIdOf<T>, T::RuntimeCall>,
59	DecodeLimit: Get<u32>,
60{
61	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
62		let input = handle.input();
63		let target_gas = handle.gas_limit();
64		let context = handle.context();
65
66		let call = T::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input)
67			.map_err(|_| PrecompileFailure::Error {
68				exit_status: ExitError::Other("decode failed".into()),
69			})?;
70		let info = call.get_dispatch_info();
71
72		if let Some(gas) = target_gas {
73			let valid_weight = info.total_weight().ref_time()
74				<= T::GasWeightMapping::gas_to_weight(gas, false).ref_time();
75			if !valid_weight {
76				return Err(PrecompileFailure::Error {
77					exit_status: ExitError::OutOfGas,
78				});
79			}
80		}
81
82		let origin = T::AddressMapping::into_account_id(context.caller);
83
84		if let Some(err) = DispatchValidator::validate_before_dispatch(&origin, &call) {
85			return Err(err);
86		}
87
88		handle.record_external_cost(
89			Some(info.total_weight().ref_time()),
90			Some(info.total_weight().proof_size()),
91			None,
92		)?;
93
94		match call.dispatch(Some(origin).into()) {
95			Ok(post_info) => {
96				if post_info.pays_fee(&info) == Pays::Yes {
97					let actual_weight = post_info.actual_weight.unwrap_or(info.total_weight());
98					let cost = T::GasWeightMapping::weight_to_gas(actual_weight);
99					handle.record_cost(cost)?;
100
101					handle.refund_external_cost(
102						Some(
103							info.total_weight()
104								.ref_time()
105								.saturating_sub(actual_weight.ref_time()),
106						),
107						Some(
108							info.total_weight()
109								.proof_size()
110								.saturating_sub(actual_weight.proof_size()),
111						),
112					);
113				}
114
115				Ok(PrecompileOutput {
116					exit_status: ExitSucceed::Stopped,
117					output: Default::default(),
118				})
119			}
120			Err(e) => Err(PrecompileFailure::Error {
121				exit_status: ExitError::Other(
122					format!("dispatch execution failed: {}", <&'static str>::from(e)).into(),
123				),
124			}),
125		}
126	}
127}
128
129/// Dispatch validation trait.
130pub trait DispatchValidateT<AccountId, RuntimeCall> {
131	fn validate_before_dispatch(
132		origin: &AccountId,
133		call: &RuntimeCall,
134	) -> Option<PrecompileFailure>;
135}
136
137/// The default implementation of `DispatchValidateT`.
138impl<AccountId, RuntimeCall> DispatchValidateT<AccountId, RuntimeCall> for ()
139where
140	RuntimeCall: GetDispatchInfo,
141{
142	fn validate_before_dispatch(
143		_origin: &AccountId,
144		call: &RuntimeCall,
145	) -> Option<PrecompileFailure> {
146		let info = call.get_dispatch_info();
147		if !(info.pays_fee == Pays::Yes && info.class == DispatchClass::Normal) {
148			return Some(PrecompileFailure::Error {
149				exit_status: ExitError::Other("invalid call".into()),
150			});
151		}
152		None
153	}
154}