diff --git a/Cargo.lock b/Cargo.lock index fdf624718b..15151d988f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,7 @@ dependencies = [ "bstringify", "enumflags2", "frame-support", + "hex", "hex-literal", "log", "module-evm-utility", @@ -6865,6 +6866,7 @@ version = "2.23.0" dependencies = [ "acala-primitives", "env_logger", + "environmental", "frame-benchmarking", "frame-support", "frame-system", diff --git a/modules/evm/Cargo.toml b/modules/evm/Cargo.toml index 4c08c81caf..f0fe7e1e58 100644 --- a/modules/evm/Cargo.toml +++ b/modules/evm/Cargo.toml @@ -23,6 +23,7 @@ serde_json = { workspace = true, features = ["alloc"], optional = true } hex = { workspace = true, features = ["alloc"], optional = true } num = { workspace = true, features = ["alloc"] } bn = { workspace = true } +environmental = { version = "1.1.4", default-features = false, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -60,7 +61,7 @@ pallet-utility = { workspace = true, features = ["std"] } default = ["std"] std = [ "serde/std", - + "environmental/std", "parity-scale-codec/std", "frame-support/std", "frame-system/std", @@ -100,7 +101,7 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-timestamp/try-runtime", ] -tracing = ["module-evm-utility/tracing"] +tracing = ["environmental", "primitives/tracing", "module-evm-utility/tracing"] wasm-bench = [ "wasm-bencher/wasm-bench", "hex", diff --git a/modules/evm/rpc/runtime-api/Cargo.toml b/modules/evm/rpc/runtime-api/Cargo.toml index 98945996cb..39b2d4a058 100644 --- a/modules/evm/rpc/runtime-api/Cargo.toml +++ b/modules/evm/rpc/runtime-api/Cargo.toml @@ -20,3 +20,4 @@ std = [ "sp-core/std", "primitives/std", ] +tracing = ["primitives/tracing"] diff --git a/modules/evm/rpc/runtime-api/src/lib.rs b/modules/evm/rpc/runtime-api/src/lib.rs index 81e14aef93..438a70a262 100644 --- a/modules/evm/rpc/runtime-api/src/lib.rs +++ b/modules/evm/rpc/runtime-api/src/lib.rs @@ -56,5 +56,27 @@ sp_api::decl_runtime_apis! { fn get_estimate_resources_request(data: Vec) -> Result; fn block_limits() -> BlockLimits; + + #[cfg(feature = "tracing")] + fn trace_call( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError>; + + #[cfg(feature = "tracing")] + fn trace_vm( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError>; } } diff --git a/modules/evm/src/runner/mod.rs b/modules/evm/src/runner/mod.rs index 20a22082e6..1c425f54c0 100644 --- a/modules/evm/src/runner/mod.rs +++ b/modules/evm/src/runner/mod.rs @@ -19,6 +19,8 @@ pub mod stack; pub mod state; pub mod storage_meter; +#[cfg(feature = "tracing")] +pub mod tracing; use crate::{BalanceOf, CallInfo, Config, CreateInfo}; use module_evm_utility::evm; diff --git a/modules/evm/src/runner/state.rs b/modules/evm/src/runner/state.rs index ec5f2d726d..20b70929fd 100644 --- a/modules/evm/src/runner/state.rs +++ b/modules/evm/src/runner/state.rs @@ -47,47 +47,18 @@ use sp_std::{ }; macro_rules! event { - ($x:expr) => {}; -} - -#[cfg(feature = "tracing")] -mod tracing { - pub struct Tracer; - impl module_evm_utility::evm::tracing::EventListener for Tracer { - fn event(&mut self, event: module_evm_utility::evm::tracing::Event) { - frame_support::log::debug!( - target: "evm", "evm tracing: {:?}", event - ); - } - } - impl module_evm_utility::evm_runtime::tracing::EventListener for Tracer { - fn event(&mut self, event: module_evm_utility::evm_runtime::tracing::Event) { - frame_support::log::debug!( - target: "evm", "evm_runtime tracing: {:?}", event - ); - } - } - impl module_evm_utility::evm_gasometer::tracing::EventListener for Tracer { - fn event(&mut self, event: module_evm_utility::evm_gasometer::tracing::Event) { - frame_support::log::debug!( - target: "evm", "evm_gasometer tracing: {:?}", event - ); + ($event:expr) => {{ + #[cfg(feature = "tracing")] + { + use crate::runner::tracing::{self, Event::*, EventListener}; + tracing::call_tracer_with(|tracer| { + EventListener::event(tracer, $event); + }); } - } + }}; } -#[cfg(feature = "tracing")] -use tracing::*; - macro_rules! emit_exit { - ($reason:expr) => {{ - let reason = $reason; - event!(Exit { - reason: &reason, - return_value: &Vec::new(), - }); - reason - }}; ($reason:expr, $return_value:expr) => {{ let reason = $reason; let return_value = $return_value; @@ -209,6 +180,12 @@ impl<'config> StackSubstateMetadata<'config> { } pub fn spit_child(&self, gas_limit: u64, is_static: bool) -> Self { + event!(Enter { + depth: match self.depth { + None => 0, + Some(n) => n + 1, + } as u32 + }); Self { gasometer: Gasometer::new(gas_limit, self.gasometer.config()), storage_meter: StorageMeter::new(self.storage_meter.available_storage()), @@ -516,7 +493,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu value, init_code: &init_code, gas_limit, - address: self.create_address(CreateScheme::Legacy { caller }), + address: self.create_address(CreateScheme::Legacy { caller }).unwrap_or_default(), }); if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { @@ -554,11 +531,13 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu init_code: &init_code, salt, gas_limit, - address: self.create_address(CreateScheme::Create2 { - caller, - code_hash, - salt, - }), + address: self + .create_address(CreateScheme::Create2 { + caller, + code_hash, + salt, + }) + .unwrap_or_default(), }); if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { @@ -1063,13 +1042,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu let mut runtime = Runtime::new(Rc::new(code), Rc::new(input), context, self.config); - #[cfg(not(feature = "tracing"))] let reason = self.execute(&mut runtime); - #[cfg(feature = "tracing")] - //let reason = module_evm_utility::evm::tracing::using(&mut Tracer, || self.execute(&mut runtime)); - let reason = module_evm_utility::evm_runtime::tracing::using(&mut Tracer, || self.execute(&mut runtime)); - //let reason = module_evm_utility::evm_gasometer::tracing::using(&mut Tracer, || self.execute(&mut - // runtime)); log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, reason); diff --git a/modules/evm/src/runner/tracing.rs b/modules/evm/src/runner/tracing.rs new file mode 100644 index 0000000000..6ab8a6c6d6 --- /dev/null +++ b/modules/evm/src/runner/tracing.rs @@ -0,0 +1,548 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use module_evm_utility::{ + evm::{Context, CreateScheme, ExitError, ExitFatal, ExitReason, ExitSucceed, Opcode, Transfer}, + evm_gasometer, evm_runtime, +}; +use sp_core::{H160, H256, U256}; +use sp_std::prelude::*; + +pub use primitives::evm::tracing::{CallTrace, CallType, Step}; + +#[derive(Debug, Copy, Clone)] +pub enum Event<'a> { + Call { + code_address: H160, + transfer: &'a Option, + input: &'a [u8], + target_gas: Option, + is_static: bool, + context: &'a Context, + }, + Create { + caller: H160, + address: H160, + scheme: CreateScheme, + value: U256, + init_code: &'a [u8], + target_gas: Option, + }, + Suicide { + address: H160, + target: H160, + balance: U256, + }, + Exit { + reason: &'a ExitReason, + return_value: &'a [u8], + }, + TransactCall { + caller: H160, + address: H160, + value: U256, + data: &'a [u8], + gas_limit: u64, + }, + TransactCreate { + caller: H160, + value: U256, + init_code: &'a [u8], + gas_limit: u64, + address: H160, + }, + TransactCreate2 { + caller: H160, + value: U256, + init_code: &'a [u8], + salt: H256, + gas_limit: u64, + address: H160, + }, + Enter { + depth: u32, + }, +} + +pub struct CallTracer { + events: Vec, + stack: Vec, + opcode: Option, + snapshot: Option, +} + +impl CallTracer { + pub fn new() -> Self { + Self { + events: Vec::new(), + stack: Vec::new(), + opcode: None, + snapshot: None, + } + } + + pub fn finalize(&mut self) -> Vec { + self.events.drain(..).rev().collect() + } + + fn call_type(&self) -> CallType { + match self.opcode { + Some(Opcode::CALLCODE) => CallType::CALLCODE, + Some(Opcode::DELEGATECALL) => CallType::DELEGATECALL, + Some(Opcode::STATICCALL) => CallType::STATICCALL, + Some(Opcode::CREATE) | Some(Opcode::CREATE2) => CallType::CREATE, + Some(Opcode::SUICIDE) => CallType::SUICIDE, + _ => CallType::CALL, + } + } + + fn evm_runtime_event(&mut self, event: evm_runtime::tracing::Event) { + match event { + evm_runtime::tracing::Event::Step { opcode, .. } => { + self.opcode = Some(opcode); + } + _ => {} + }; + } + + fn evm_gasometer_event(&mut self, event: evm_gasometer::tracing::Event) { + match event { + evm_gasometer::tracing::Event::RecordCost { snapshot, .. } + | evm_gasometer::tracing::Event::RecordDynamicCost { snapshot, .. } + | evm_gasometer::tracing::Event::RecordTransaction { snapshot, .. } + | evm_gasometer::tracing::Event::RecordRefund { snapshot, .. } + | evm_gasometer::tracing::Event::RecordStipend { snapshot, .. } => self.snapshot = snapshot, + }; + } +} + +pub struct OpcodeTracer { + pub steps: Vec, +} + +impl OpcodeTracer { + pub fn new() -> Self { + Self { steps: Vec::new() } + } +} + +impl evm_runtime::tracing::EventListener for OpcodeTracer { + fn event(&mut self, event: evm_runtime::tracing::Event) { + match event { + evm_runtime::tracing::Event::Step { + context: _, + opcode, + position, + stack, + memory, + } => self.steps.push(Step { + op: opcode.stringify().as_bytes().to_vec(), + pc: position.clone().unwrap_or_default() as u64, + stack: stack.data().clone(), + memory: memory.data().clone(), + }), + _ => {} + } + } +} + +pub trait EventListener { + fn event(&mut self, event: Event); +} + +impl EventListener for CallTracer { + fn event(&mut self, event: Event) { + match event { + Event::Call { + code_address, + transfer, + input, + target_gas, + is_static, + context, + } => { + let call_type = if is_static { + CallType::STATICCALL + } else { + self.call_type() + }; + self.stack.push(CallTrace { + call_type, + from: context.caller, + to: code_address, + input: input.to_vec(), + value: transfer.clone().map(|x| x.value).unwrap_or_default(), + gas: target_gas.unwrap_or_default(), + gas_used: 0, + output: None, + error: None, + revert_reason: None, + calls: Vec::new(), + depth: 0, + }); + } + Event::TransactCall { + caller, + address, + value, + data, + gas_limit, + } => { + self.stack.push(CallTrace { + call_type: CallType::CALL, + from: caller, + to: address, + input: data.to_vec(), + value, + gas: gas_limit, + gas_used: 0, + output: None, + error: None, + revert_reason: None, + calls: Vec::new(), + depth: 0, + }); + } + Event::Create { + caller, + address, + value, + init_code, + target_gas, + .. + } => { + self.stack.push(CallTrace { + call_type: CallType::CREATE, + from: caller, + to: address, + input: init_code.to_vec(), + value, + gas: target_gas.unwrap_or_default(), + gas_used: 0, + output: None, + error: None, + revert_reason: None, + calls: Vec::new(), + depth: 0, + }); + } + Event::TransactCreate { + caller, + value, + init_code, + gas_limit, + address, + } + | Event::TransactCreate2 { + caller, + value, + init_code, + gas_limit, + address, + .. + } => { + self.stack.push(CallTrace { + call_type: CallType::CREATE, + from: caller, + to: address, + input: init_code.to_vec(), + value, + gas: gas_limit, + gas_used: 0, + output: None, + error: None, + revert_reason: None, + calls: Vec::new(), + depth: 0, + }); + } + Event::Suicide { + address, + target, + balance, + } => { + self.stack.push(CallTrace { + call_type: CallType::SUICIDE, + from: address, + to: target, + input: vec![], + value: balance, + gas: 0, + gas_used: 0, + output: None, + error: None, + revert_reason: None, + calls: Vec::new(), + depth: 0, + }); + } + Event::Exit { reason, return_value } => { + if let Some(mut trace) = self.stack.pop() { + match reason { + ExitReason::Succeed(ExitSucceed::Returned) => trace.output = Some(return_value.to_vec()), + ExitReason::Succeed(_) => {} + ExitReason::Error(e) => trace.error = Some(e.stringify().as_bytes().to_vec()), + ExitReason::Revert(_) => trace.revert_reason = Some(return_value.to_vec()), + ExitReason::Fatal(e) => trace.error = Some(e.stringify().as_bytes().to_vec()), + } + + if let Some(snapshot) = self.snapshot { + trace.gas_used = trace.gas.saturating_sub(snapshot.gas()); + } + + if let Some(index) = self.events.iter().position(|x| x.depth > trace.depth) { + trace.calls = self.events.drain(index..).collect(); + } + + self.events.push(trace); + } + } + Event::Enter { depth } => { + if let Some(event) = self.stack.last_mut() { + event.depth = depth; + } + } + } + } +} + +pub struct EvmRuntimeTracer; + +impl evm_runtime::tracing::EventListener for EvmRuntimeTracer { + fn event(&mut self, event: evm_runtime::tracing::Event) { + call_tracer::with(|tracer| { + tracer.evm_runtime_event(event); + }); + } +} + +pub struct EvmGasometerTracer; + +impl evm_gasometer::tracing::EventListener for EvmGasometerTracer { + fn event(&mut self, event: evm_gasometer::tracing::Event) { + call_tracer::with(|tracer| { + tracer.evm_gasometer_event(event); + }); + } +} + +environmental::environmental!(call_tracer: CallTracer); + +pub fn call_tracer_using R>(new: &mut CallTracer, f: F) -> R { + call_tracer::using(new, || { + evm_gasometer::tracing::using(&mut EvmGasometerTracer, || { + evm_runtime::tracing::using(&mut EvmRuntimeTracer, f) + }) + }) +} + +pub(crate) fn call_tracer_with(f: F) { + call_tracer::with(f); +} + +pub fn opcode_tracer_using R>(new: &mut OpcodeTracer, f: F) -> R { + evm_runtime::tracing::using(new, f) +} + +trait Stringify { + fn stringify(&self) -> &str; +} + +impl Stringify for Opcode { + fn stringify(&self) -> &str { + match self { + &Opcode::STOP => "STOP", + &Opcode::ADD => "ADD", + &Opcode::MUL => "MUL", + &Opcode::SUB => "SUB", + &Opcode::DIV => "DIV", + &Opcode::SDIV => "SDIV", + &Opcode::MOD => "MOD", + &Opcode::SMOD => "SMOD", + &Opcode::ADDMOD => "ADDMOD", + &Opcode::MULMOD => "MULMOD", + &Opcode::EXP => "EXP", + &Opcode::SIGNEXTEND => "SIGNEXTEND", + &Opcode::LT => "LT", + &Opcode::GT => "GT", + &Opcode::SLT => "SLT", + &Opcode::SGT => "SGT", + &Opcode::EQ => "EQ", + &Opcode::ISZERO => "ISZERO", + &Opcode::AND => "AND", + &Opcode::OR => "OR", + &Opcode::XOR => "XOR", + &Opcode::NOT => "NOT", + &Opcode::BYTE => "BYTE", + &Opcode::SHL => "SHL", + &Opcode::SHR => "SHR", + &Opcode::SAR => "SAR", + &Opcode::SHA3 => "SHA3", + &Opcode::ADDRESS => "ADDRESS", + &Opcode::BALANCE => "BALANCE", + &Opcode::ORIGIN => "ORIGIN", + &Opcode::CALLER => "CALLER", + &Opcode::CALLVALUE => "CALLVALUE", + &Opcode::CALLDATALOAD => "CALLDATALOAD", + &Opcode::CALLDATASIZE => "CALLDATASIZE", + &Opcode::CALLDATACOPY => "CALLDATACOPY", + &Opcode::CODESIZE => "CODESIZE", + &Opcode::CODECOPY => "CODECOPY", + &Opcode::GASPRICE => "GASPRICE", + &Opcode::EXTCODESIZE => "EXTCODESIZE", + &Opcode::EXTCODECOPY => "EXTCODECOPY", + &Opcode::RETURNDATASIZE => "RETURNDATASIZE", + &Opcode::RETURNDATACOPY => "RETURNDATACOPY", + &Opcode::EXTCODEHASH => "EXTCODEHASH", + &Opcode::BLOCKHASH => "BLOCKHASH", + &Opcode::COINBASE => "COINBASE", + &Opcode::TIMESTAMP => "TIMESTAMP", + &Opcode::NUMBER => "NUMBER", + &Opcode::DIFFICULTY => "DIFFICULTY", + &Opcode::GASLIMIT => "GASLIMIT", + &Opcode::CHAINID => "CHAINID", + &Opcode::SELFBALANCE => "SELFBALANCE", + &Opcode::POP => "POP", + &Opcode::MLOAD => "MLOAD", + &Opcode::MSTORE => "MSTORE", + &Opcode::MSTORE8 => "MSTORE8", + &Opcode::SLOAD => "SLOAD", + &Opcode::SSTORE => "SSTORE", + &Opcode::JUMP => "JUMP", + &Opcode::JUMPI => "JUMPI", + &Opcode::PC => "PC", + &Opcode::MSIZE => "MSIZE", + &Opcode::GAS => "GAS", + &Opcode::JUMPDEST => "JUMPDEST", + &Opcode::PUSH1 => "PUSH1", + &Opcode::PUSH2 => "PUSH2", + &Opcode::PUSH3 => "PUSH3", + &Opcode::PUSH4 => "PUSH4", + &Opcode::PUSH5 => "PUSH5", + &Opcode::PUSH6 => "PUSH6", + &Opcode::PUSH7 => "PUSH7", + &Opcode::PUSH8 => "PUSH8", + &Opcode::PUSH9 => "PUSH9", + &Opcode::PUSH10 => "PUSH10", + &Opcode::PUSH11 => "PUSH11", + &Opcode::PUSH12 => "PUSH12", + &Opcode::PUSH13 => "PUSH13", + &Opcode::PUSH14 => "PUSH14", + &Opcode::PUSH15 => "PUSH15", + &Opcode::PUSH16 => "PUSH16", + &Opcode::PUSH17 => "PUSH17", + &Opcode::PUSH18 => "PUSH18", + &Opcode::PUSH19 => "PUSH19", + &Opcode::PUSH20 => "PUSH20", + &Opcode::PUSH21 => "PUSH21", + &Opcode::PUSH22 => "PUSH22", + &Opcode::PUSH23 => "PUSH23", + &Opcode::PUSH24 => "PUSH24", + &Opcode::PUSH25 => "PUSH25", + &Opcode::PUSH26 => "PUSH26", + &Opcode::PUSH27 => "PUSH27", + &Opcode::PUSH28 => "PUSH28", + &Opcode::PUSH29 => "PUSH29", + &Opcode::PUSH30 => "PUSH30", + &Opcode::PUSH31 => "PUSH31", + &Opcode::PUSH32 => "PUSH32", + &Opcode::DUP1 => "DUP1", + &Opcode::DUP2 => "DUP2", + &Opcode::DUP3 => "DUP3", + &Opcode::DUP4 => "DUP4", + &Opcode::DUP5 => "DUP5", + &Opcode::DUP6 => "DUP6", + &Opcode::DUP7 => "DUP7", + &Opcode::DUP8 => "DUP8", + &Opcode::DUP9 => "DUP9", + &Opcode::DUP10 => "DUP10", + &Opcode::DUP11 => "DUP11", + &Opcode::DUP12 => "DUP12", + &Opcode::DUP13 => "DUP13", + &Opcode::DUP14 => "DUP14", + &Opcode::DUP15 => "DUP15", + &Opcode::DUP16 => "DUP16", + &Opcode::SWAP1 => "SWAP1", + &Opcode::SWAP2 => "SWAP2", + &Opcode::SWAP3 => "SWAP3", + &Opcode::SWAP4 => "SWAP4", + &Opcode::SWAP5 => "SWAP5", + &Opcode::SWAP6 => "SWAP6", + &Opcode::SWAP7 => "SWAP7", + &Opcode::SWAP8 => "SWAP8", + &Opcode::SWAP9 => "SWAP9", + &Opcode::SWAP10 => "SWAP10", + &Opcode::SWAP11 => "SWAP11", + &Opcode::SWAP12 => "SWAP12", + &Opcode::SWAP13 => "SWAP13", + &Opcode::SWAP14 => "SWAP14", + &Opcode::SWAP15 => "SWAP15", + &Opcode::SWAP16 => "SWAP16", + &Opcode::LOG0 => "LOG0", + &Opcode::LOG1 => "LOG1", + &Opcode::LOG2 => "LOG2", + &Opcode::LOG3 => "LOG3", + &Opcode::LOG4 => "LOG4", + &Opcode::CREATE => "CREATE", + &Opcode::CALL => "CALL", + &Opcode::CALLCODE => "CALLCODE", + &Opcode::RETURN => "RETURN", + &Opcode::DELEGATECALL => "DELEGATECALL", + &Opcode::STATICCALL => "STATICCALL", + &Opcode::REVERT => "REVERT", + &Opcode::INVALID => "INVALID", + &Opcode::CREATE2 => "CREATE2", + &Opcode::EOFMAGIC => "EOFMAGIC", + &Opcode::SUICIDE => "SUICIDE", + _ => "UNKNOWN", + } + } +} + +impl Stringify for ExitError { + fn stringify(&self) -> &str { + match self { + ExitError::StackUnderflow => "StackUnderflow", + ExitError::StackOverflow => "StackOverflow", + ExitError::InvalidJump => "InvalidJump", + ExitError::InvalidRange => "InvalidRange", + ExitError::DesignatedInvalid => "DesignatedInvalid", + ExitError::CallTooDeep => "CallTooDeep", + ExitError::CreateCollision => "CreateCollision", + ExitError::CreateContractLimit => "CreateContractLimit", + ExitError::InvalidCode(_) => "InvalidCode", + ExitError::OutOfOffset => "OutOfOffset", + ExitError::OutOfGas => "OutOfGas", + ExitError::OutOfFund => "OutOfFund", + ExitError::PCUnderflow => "PCUnderflow", + ExitError::CreateEmpty => "CreateEmpty", + ExitError::Other(msg) => msg, + } + } +} + +impl Stringify for ExitFatal { + fn stringify(&self) -> &str { + match self { + ExitFatal::NotSupported => "NotSupported", + ExitFatal::UnhandledInterrupt => "UnhandledInterrupt", + ExitFatal::CallErrorAsFatal(e) => e.stringify(), + ExitFatal::Other(msg) => msg, + } + } +} diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 6975f0fd24..b62d3aaac2 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -14,6 +14,7 @@ num_enum = { workspace = true } scale-info = { workspace = true } enumflags2 = { workspace = true, features = ["serde"] } paste = { workspace = true } +hex = { workspace = true, features = ["alloc"], optional = true } sp-core = { workspace = true } sp-runtime = { workspace = true } @@ -36,6 +37,7 @@ serde_json = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ + "hex/std", "serde/std", "parity-scale-codec/std", "num_enum/std", @@ -55,3 +57,4 @@ std = [ ] evm-tests = [] try-runtime = [] +tracing = [] \ No newline at end of file diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs index e4110bede7..fac3eb387f 100644 --- a/primitives/src/evm.rs +++ b/primitives/src/evm.rs @@ -331,3 +331,78 @@ mod convert { } pub use convert::*; + +#[cfg(feature = "tracing")] +pub mod tracing { + use parity_scale_codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_core::{H160, H256, U256}; + use sp_runtime::RuntimeDebug; + use sp_std::vec::Vec; + + #[cfg(feature = "std")] + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + pub enum CallType { + CALL, + CALLCODE, + STATICCALL, + DELEGATECALL, + CREATE, + SUICIDE, + } + + impl sp_std::fmt::Display for CallType { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + CallType::CALL => write!(f, "CALL"), + CallType::CALLCODE => write!(f, "CALLCODE"), + CallType::STATICCALL => write!(f, "STATICCALL"), + CallType::DELEGATECALL => write!(f, "DELEGATECALL"), + CallType::CREATE => write!(f, "CREATE"), + CallType::SUICIDE => write!(f, "SUICIDE"), + } + } + } + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] + pub struct CallTrace { + #[cfg_attr(feature = "std", serde(rename = "type"))] + pub call_type: CallType, + pub from: H160, + pub to: H160, + pub input: Vec, + pub value: U256, + // gas limit + #[codec(compact)] + pub gas: u64, + #[codec(compact)] + pub gas_used: u64, + // value returned from EVM, if any + pub output: Option>, + // evm error, if any + pub error: Option>, + // revert reason, if any + pub revert_reason: Option>, + // depth of the call + #[codec(compact)] + pub depth: u32, + // List of sub-calls + pub calls: Vec, + } + + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] + pub struct Step { + pub op: Vec, + #[codec(compact)] + pub pc: u64, + pub stack: Vec, + pub memory: Vec, + } +} diff --git a/runtime/acala/Cargo.toml b/runtime/acala/Cargo.toml index e5b2e3fbf1..936287e306 100644 --- a/runtime/acala/Cargo.toml +++ b/runtime/acala/Cargo.toml @@ -409,3 +409,5 @@ no-metadata-docs = ["frame-support/no-metadata-docs"] # By default some types have documentation, `full-metadata-docs` allows to add documentation to # more types in the metadata. full-metadata-docs = ["frame-support/full-metadata-docs"] + +tracing = ["module-evm/tracing", "module-evm-rpc-runtime-api/tracing"] diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 11d43d1051..962c63e83b 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -2176,6 +2176,46 @@ sp_api::impl_runtime_apis! { request.ok_or(sp_runtime::DispatchError::Other("Invalid parameter extrinsic, not evm Call")) } + + #[cfg(feature = "tracing")] + fn trace_call( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError> { + let mut tracer = module_evm::runner::tracing::CallTracer::new(); + module_evm::runner::tracing::call_tracer_using(&mut tracer, || { + if to == H160::zero() { + Self::create(from, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } else { + Self::call(from, to, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } + }).map(|_| tracer.finalize()) + } + + #[cfg(feature = "tracing")] + fn trace_vm( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError> { + let mut tracer = module_evm::runner::tracing::OpcodeTracer::new(); + module_evm::runner::tracing::opcode_tracer_using(&mut tracer, || { + if to == H160::zero() { + Self::create(from, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } else { + Self::call(from, to, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } + }).map(|_| tracer.steps) + } } impl cumulus_primitives_core::CollectCollationInfo for Runtime { diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index 5b97257589..5cca603bca 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -412,3 +412,5 @@ no-metadata-docs = ["frame-support/no-metadata-docs"] # By default some types have documentation, `full-metadata-docs` allows to add documentation to # more types in the metadata. full-metadata-docs = ["frame-support/full-metadata-docs"] + +tracing = ["module-evm/tracing", "module-evm-rpc-runtime-api/tracing"] diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 87a269fb21..93cbb8ae1b 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -2190,6 +2190,46 @@ impl_runtime_apis! { request.ok_or(sp_runtime::DispatchError::Other("Invalid parameter extrinsic, not evm Call")) } + + #[cfg(feature = "tracing")] + fn trace_call( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError> { + let mut tracer = module_evm::runner::tracing::CallTracer::new(); + module_evm::runner::tracing::call_tracer_using(&mut tracer, || { + if to == H160::zero() { + Self::create(from, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } else { + Self::call(from, to, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } + }).map(|_| tracer.finalize()) + } + + #[cfg(feature = "tracing")] + fn trace_vm( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError> { + let mut tracer = module_evm::runner::tracing::OpcodeTracer::new(); + module_evm::runner::tracing::opcode_tracer_using(&mut tracer, || { + if to == H160::zero() { + Self::create(from, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } else { + Self::call(from, to, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } + }).map(|_| tracer.steps) + } } impl cumulus_primitives_core::CollectCollationInfo for Runtime { diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index 403a81211e..d961b2c736 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -442,3 +442,5 @@ no-metadata-docs = ["frame-support/no-metadata-docs"] # By default some types have documentation, `full-metadata-docs` allows to add documentation to # more types in the metadata. full-metadata-docs = ["frame-support/full-metadata-docs"] + +tracing = ["module-evm/tracing", "module-evm-rpc-runtime-api/tracing"] diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index a5f2a84985..ff0d313e81 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -2383,6 +2383,46 @@ impl_runtime_apis! { request.ok_or(sp_runtime::DispatchError::Other("Invalid parameter extrinsic, not evm Call")) } + + #[cfg(feature = "tracing")] + fn trace_call( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError> { + let mut tracer = module_evm::runner::tracing::CallTracer::new(); + module_evm::runner::tracing::call_tracer_using(&mut tracer, || { + if to == H160::zero() { + Self::create(from, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } else { + Self::call(from, to, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } + }).map(|_| tracer.finalize()) + } + + #[cfg(feature = "tracing")] + fn trace_vm( + from: H160, + to: H160, + data: Vec, + value: Balance, + gas_limit: u64, + storage_limit: u32, + access_list: Option>, + ) -> Result, sp_runtime::DispatchError> { + let mut tracer = module_evm::runner::tracing::OpcodeTracer::new(); + module_evm::runner::tracing::opcode_tracer_using(&mut tracer, || { + if to == H160::zero() { + Self::create(from, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } else { + Self::call(from, to, data, value, gas_limit, storage_limit, access_list, false).map(drop) + } + }).map(|_| tracer.steps) + } } impl cumulus_primitives_core::CollectCollationInfo for Runtime {