From 3ca26f308e1894b32d9be484ee86fd0bc320d215 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 8 Jan 2024 01:12:00 +0000 Subject: [PATCH] evm: Fix gas estimation issue in call delegation --- Cargo.lock | 28 ++-- standalone/runtime/src/evm_precompiles.rs | 195 ++++++++++++++++++++++ standalone/runtime/src/lib.rs | 7 +- standalone/runtime/src/precompiles.rs | 70 -------- 4 files changed, 214 insertions(+), 86 deletions(-) create mode 100644 standalone/runtime/src/evm_precompiles.rs delete mode 100644 standalone/runtime/src/precompiles.rs diff --git a/Cargo.lock b/Cargo.lock index 5badb081b..e724d133c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1413,7 +1413,7 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.9", + "prettyplease 0.2.16", "proc-macro2", "quote", "regex", @@ -4066,7 +4066,7 @@ dependencies = [ "ethers-etherscan", "eyre", "hex", - "prettyplease 0.2.9", + "prettyplease 0.2.16", "proc-macro2", "quote", "regex", @@ -11421,9 +11421,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", "syn 2.0.47", @@ -15252,7 +15252,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.4.1" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -15292,7 +15292,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "proc-macro2", "quote", @@ -15313,7 +15313,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "environmental", "parity-scale-codec", @@ -15514,7 +15514,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -15545,7 +15545,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "Inflector", "expander", @@ -15637,7 +15637,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?branch=release-polk [[package]] name = "sp-std" version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" [[package]] name = "sp-storage" @@ -15655,7 +15655,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "impl-serde", "parity-scale-codec", @@ -15693,7 +15693,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "parity-scale-codec", "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk)", @@ -15795,7 +15795,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f82c297728373b9aa09893d15c2c8f9c01ba8cf3" +source = "git+https://github.com/paritytech/polkadot-sdk#2e4b8996c4924fc39f85198019039cf0987f89ec" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -17676,7 +17676,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.4.6", + "rand 0.8.5", "static_assertions", ] diff --git a/standalone/runtime/src/evm_precompiles.rs b/standalone/runtime/src/evm_precompiles.rs new file mode 100644 index 000000000..789330028 --- /dev/null +++ b/standalone/runtime/src/evm_precompiles.rs @@ -0,0 +1,195 @@ +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use pallet_evm::{ + IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet, +}; +use sp_core::H160; +use sp_runtime::traits::Dispatchable; +use sp_std::marker::PhantomData; + +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; + +pub struct FrontierPrecompiles(PhantomData); +impl Default for FrontierPrecompiles { + fn default() -> Self { + Self(PhantomData) + } +} + +impl FrontierPrecompiles +where + R: pallet_evm::Config, +{ + pub fn used_addresses() -> [H160; 8] { + [ + hash(1), + hash(2), + hash(3), + hash(4), + hash(5), + hash(1024), + hash(1025), + hash(2048), + ] + } +} +impl PrecompileSet for FrontierPrecompiles +where + R: pallet_evm::Config, + R::RuntimeCall: Dispatchable + GetDispatchInfo, + ::RuntimeOrigin: From>, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + // Ethereum precompiles : + a if a == hash(1) => Some(ECRecover::execute(handle)), + a if a == hash(2) => Some(Sha256::execute(handle)), + a if a == hash(3) => Some(Ripemd160::execute(handle)), + a if a == hash(4) => Some(Identity::execute(handle)), + a if a == hash(5) => Some(Modexp::execute(handle)), + // Non-Frontier specific nor Ethereum precompiles : + a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), + a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), + a if a == hash(2048) => Some(substrate_call::Dispatch::::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: Self::used_addresses().contains(&address), + extra_cost: 0, + } + } +} + +fn hash(a: u64) -> H160 { + H160::from_low_u64_be(a) +} + +mod substrate_call { + use alloc::format; + use core::marker::PhantomData; + + use codec::{Decode, DecodeLimit}; + // Substrate + use frame_support::{ + dispatch::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo}, + traits::{ConstU32, Get}, + }; + use sp_runtime::traits::Dispatchable; + // Frontier + use fp_evm::{ + ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, + PrecompileResult, + }; + use pallet_evm::{AddressMapping, GasWeightMapping}; + + // `DecodeLimit` specifies the max depth a call can use when decoding, as unbounded depth + // can be used to overflow the stack. + // Default value is 8, which is the same as in XCM call decoding. + pub struct Dispatch> { + _marker: PhantomData<(T, DispatchValidator, DecodeLimit)>, + } + + impl Precompile for Dispatch + where + T: pallet_evm::Config, + T::RuntimeCall: Dispatchable + GetDispatchInfo + Decode, + ::RuntimeOrigin: From>, + DispatchValidator: DispatchValidateT, + DecodeLimit: Get, + { + fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { + let input = handle.input(); + let target_gas = handle.gas_limit(); + let context = handle.context(); + + let call = T::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*input) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("decode failed".into()), + })?; + let info = call.get_dispatch_info(); + + if let Some(gas) = target_gas { + let valid_weight = T::GasWeightMapping::weight_to_gas(info.weight) <= gas; + if !valid_weight { + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); + } + } + + let origin = T::AddressMapping::into_account_id(context.caller); + + if let Some(err) = DispatchValidator::validate_before_dispatch(&origin, &call) { + return Err(err); + } + + handle.record_external_cost( + Some(info.weight.ref_time()), + Some(info.weight.proof_size()), + None, + )?; + match call.dispatch(Some(origin).into()) { + Ok(post_info) => { + if post_info.pays_fee(&info) == Pays::Yes { + let actual_weight = post_info.actual_weight.unwrap_or(info.weight); + let cost = T::GasWeightMapping::weight_to_gas(actual_weight); + handle.refund_external_cost( + Some( + info.weight + .ref_time() + .saturating_sub(actual_weight.ref_time()), + ), + Some( + info.weight + .proof_size() + .saturating_sub(actual_weight.proof_size()), + ), + ); + handle.record_cost(cost)?; + } + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Stopped, + output: Default::default(), + }) + } + Err(e) => Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + format!("dispatch execution failed: {}", <&'static str>::from(e)).into(), + ), + }), + } + } + } + + /// Dispatch validation trait. + pub trait DispatchValidateT { + fn validate_before_dispatch( + origin: &AccountId, + call: &RuntimeCall, + ) -> Option; + } + + /// The default implementation of `DispatchValidateT`. + impl DispatchValidateT for () + where + RuntimeCall: GetDispatchInfo, + { + fn validate_before_dispatch( + _origin: &AccountId, + call: &RuntimeCall, + ) -> Option { + let info = call.get_dispatch_info(); + if !(info.pays_fee == Pays::Yes && info.class == DispatchClass::Normal) { + return Some(PrecompileFailure::Error { + exit_status: ExitError::Other("invalid call".into()), + }); + } + None + } + } +} diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 275564a14..3e8f4ba75 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -23,6 +23,9 @@ #![recursion_limit = "1024"] #![allow(clippy::identity_op)] +#[macro_use] +extern crate alloc; + mod msg_routing; use codec::{Decode, Encode, MaxEncodedLen}; @@ -128,8 +131,8 @@ use pallet_ethereum::{ TransactionData, }; use pallet_evm::{Account as EVMAccount, FeeCalculator, Runner}; -use precompiles::FrontierPrecompiles; -mod precompiles; +use evm_precompiles::FrontierPrecompiles; +mod evm_precompiles; // Make the WASM binary available. #[cfg(all(feature = "std", feature = "include-wasm"))] diff --git a/standalone/runtime/src/precompiles.rs b/standalone/runtime/src/precompiles.rs deleted file mode 100644 index df6dbb78a..000000000 --- a/standalone/runtime/src/precompiles.rs +++ /dev/null @@ -1,70 +0,0 @@ -use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; -use pallet_evm::{ - IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet, -}; -use sp_core::H160; -use sp_runtime::traits::Dispatchable; -use sp_std::marker::PhantomData; - -use pallet_evm_precompile_dispatch::Dispatch; -use pallet_evm_precompile_modexp::Modexp; -use pallet_evm_precompile_sha3fips::Sha3FIPS256; -use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; - -pub struct FrontierPrecompiles(PhantomData); -impl Default for FrontierPrecompiles { - fn default() -> Self { - Self(PhantomData) - } -} - -impl FrontierPrecompiles -where - R: pallet_evm::Config, -{ - pub fn used_addresses() -> [H160; 8] { - [ - hash(1), - hash(2), - hash(3), - hash(4), - hash(5), - hash(1024), - hash(1025), - hash(2048), - ] - } -} -impl PrecompileSet for FrontierPrecompiles -where - R: pallet_evm::Config, - R::RuntimeCall: Dispatchable + GetDispatchInfo, - ::RuntimeOrigin: From>, -{ - fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { - match handle.code_address() { - // Ethereum precompiles : - a if a == hash(1) => Some(ECRecover::execute(handle)), - a if a == hash(2) => Some(Sha256::execute(handle)), - a if a == hash(3) => Some(Ripemd160::execute(handle)), - a if a == hash(4) => Some(Identity::execute(handle)), - a if a == hash(5) => Some(Modexp::execute(handle)), - // Non-Frontier specific nor Ethereum precompiles : - a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), - a if a == hash(2048) => Some(Dispatch::::execute(handle)), - _ => None, - } - } - - fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult { - IsPrecompileResult::Answer { - is_precompile: Self::used_addresses().contains(&address), - extra_cost: 0, - } - } -} - -fn hash(a: u64) -> H160 { - H160::from_low_u64_be(a) -}