pallet_evm_precompile_dispatch/
lib.rs1#![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};
33use frame_support::{
35 dispatch::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo},
36 traits::{ConstU32, Get},
37};
38use sp_runtime::traits::Dispatchable;
39use fp_evm::{
41 ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
42 PrecompileResult,
43};
44use pallet_evm::{AddressMapping, GasWeightMapping};
45
46pub 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
129pub trait DispatchValidateT<AccountId, RuntimeCall> {
131 fn validate_before_dispatch(
132 origin: &AccountId,
133 call: &RuntimeCall,
134 ) -> Option<PrecompileFailure>;
135}
136
137impl<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}