From 83b57b0071e655359b569cb28184df8c08ee9583 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Tue, 3 Dec 2024 14:15:59 +0000 Subject: [PATCH 01/30] Duplicate stack trace logic in Rust --- Cargo.lock | 18 +- crates/edr_evm/src/trace.rs | 7 +- crates/edr_napi/index.d.ts | 2 + crates/edr_napi/src/cast.rs | 2 +- crates/edr_napi/src/provider.rs | 50 +- crates/edr_napi/src/trace.rs | 6 +- crates/edr_napi/src/trace/debug.rs | 4 +- crates/edr_napi/src/trace/error_inferrer.rs | 38 +- ...d_inlined_internal_functions_heuristics.rs | 2 +- crates/edr_napi/src/trace/message_trace.rs | 10 +- .../src/trace/solidity_stack_trace.rs | 220 ++ crates/edr_napi/src/trace/solidity_tracer.rs | 4 +- crates/edr_solidity/Cargo.toml | 6 +- crates/edr_solidity/src/build_model.rs | 20 +- crates/edr_solidity/src/error_inferrer.rs | 2159 +++++++++++++++++ crates/edr_solidity/src/lib.rs | 6 + ...ed_inline_internal_functions_heuristics.rs | 180 ++ crates/edr_solidity/src/message_trace.rs | 216 +- crates/edr_solidity/src/return_data.rs | 60 + .../edr_solidity/src/solidity_stack_trace.rs | 179 ++ crates/edr_solidity/src/solidity_tracer.rs | 274 +++ crates/edr_solidity/src/vm_trace_decoder.rs | 211 ++ crates/edr_solidity/src/vm_tracer.rs | 6 +- 23 files changed, 3627 insertions(+), 53 deletions(-) create mode 100644 crates/edr_solidity/src/error_inferrer.rs create mode 100644 crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs create mode 100644 crates/edr_solidity/src/return_data.rs create mode 100644 crates/edr_solidity/src/solidity_stack_trace.rs create mode 100644 crates/edr_solidity/src/solidity_tracer.rs create mode 100644 crates/edr_solidity/src/vm_trace_decoder.rs diff --git a/Cargo.lock b/Cargo.lock index f6bd66531..6fab749c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -802,9 +802,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.11.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -1179,7 +1179,7 @@ dependencies = [ "openssl-sys", "parking_lot 0.12.1", "rand", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "strum", @@ -1283,13 +1283,17 @@ version = "0.3.5" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", + "alloy-sol-types 0.7.7", "anyhow", "edr_eth", "edr_evm", + "either", "indexmap 2.2.6", + "semver 1.0.23", "serde", "serde_json", "strum", + "thiserror", ] [[package]] @@ -2187,7 +2191,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.22", + "semver 1.0.23", "syn 2.0.58", ] @@ -3094,7 +3098,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver 1.0.23", ] [[package]] @@ -3234,9 +3238,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "semver-parser" diff --git a/crates/edr_evm/src/trace.rs b/crates/edr_evm/src/trace.rs index 946ef9d3b..6c64e3144 100644 --- a/crates/edr_evm/src/trace.rs +++ b/crates/edr_evm/src/trace.rs @@ -192,7 +192,7 @@ pub struct Trace { #[derive(Clone, Debug)] pub struct Step { /// The program counter - pub pc: u64, + pub pc: u32, /// The call depth pub depth: u64, /// The executed op code @@ -491,7 +491,10 @@ impl TraceCollector { None }; self.current_trace_mut().add_step(Step { - pc: interp.program_counter() as u64, + pc: interp + .program_counter() + .try_into() + .expect("program counter fits into u32"), depth: data.journaled_state.depth(), opcode: interp.current_opcode(), stack, diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 4c27f3fd2..47e4c45a2 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -661,6 +661,8 @@ export declare class Response { /** Returns the response data as a JSON string or a JSON object. */ get data(): string | any get solidityTrace(): RawTrace | null + /**Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as fallback. */ + stackTrace(config: any): SolidityStackTrace | string | null get traces(): Array } /** diff --git a/crates/edr_napi/src/cast.rs b/crates/edr_napi/src/cast.rs index b9041fe5a..06c47aef9 100644 --- a/crates/edr_napi/src/cast.rs +++ b/crates/edr_napi/src/cast.rs @@ -1,6 +1,6 @@ use edr_eth::{Address, Bytes, B256, B64, U256}; use napi::{ - bindgen_prelude::{BigInt, Buffer}, + bindgen_prelude::{BigInt, Buffer, Uint8Array}, Status, }; diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 062fb3fa4..d42fae2d4 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -10,10 +10,11 @@ use napi_derive::napi; use self::config::ProviderConfig; use crate::{ call_override::CallOverrideCallback, + cast::TryCast, context::EdrContext, logger::{Logger, LoggerConfig, LoggerError}, subscribe::SubscriberCallback, - trace::RawTrace, + trace::{solidity_stack_trace::SolidityStackTrace, RawTrace}, }; /// A JSON-RPC provider for Ethereum. @@ -250,6 +251,53 @@ impl Response { .map(|trace| RawTrace::new(trace.clone())) } + // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590 + #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as fallback."] + #[napi] + pub fn stack_trace( + &self, + config: serde_json::Value, + ) -> napi::Result>> { + let Some(trace) = &self.solidity_trace else { + return Ok(None); + }; + let mut vm_tracer = edr_solidity::vm_tracer::VmTracer::new(); + vm_tracer.observe(trace); + + let mut vm_trace = vm_tracer.get_last_top_level_message_trace(); + let vm_tracer_error = vm_tracer.get_last_error(); + + // TODO get actual type as argument + let tracing_config: edr_solidity::vm_trace_decoder::TracingConfig = + serde_json::from_value(config)?; + let mut vm_trace_decoder = edr_solidity::vm_trace_decoder::VmTraceDecoder::new(); + edr_solidity::vm_trace_decoder::initialize_vm_trace_decoder( + &mut vm_trace_decoder, + tracing_config, + )?; + + vm_trace = vm_trace.map(|trace| vm_trace_decoder.try_to_decode_message_trace(trace)); + + if let Some(vm_trace) = vm_trace { + let stack_trace = + edr_solidity::solidity_tracer::get_stack_trace(vm_trace).map_err(|err| { + napi::Error::from_reason(format!( + "Error converting to solidity stack trace: {err}" + )) + })?; + let stack_trace = stack_trace + .into_iter() + .map(|stack_trace| stack_trace.try_cast()) + .collect::, _>>()?; + + Ok(Some(Either::A(stack_trace))) + } else if let Some(vm_tracer_error) = vm_tracer_error { + Ok(Some(Either::B(vm_tracer_error.to_string()))) + } else { + Ok(None) + } + } + #[napi(getter)] pub fn traces(&self) -> Vec { self.traces diff --git a/crates/edr_napi/src/trace.rs b/crates/edr_napi/src/trace.rs index a8cce8e7f..d7b7d0d10 100644 --- a/crates/edr_napi/src/trace.rs +++ b/crates/edr_napi/src/trace.rs @@ -26,7 +26,7 @@ mod exit; mod mapped_inlined_internal_functions_heuristics; mod message_trace; mod return_data; -mod solidity_stack_trace; +pub mod solidity_stack_trace; mod solidity_tracer; mod vm_trace_decoder; mod vm_tracer; @@ -149,7 +149,7 @@ impl TracingStep { Self { depth: step.depth as u8, - pc: BigInt::from(step.pc), + pc: BigInt::from(u64::from(step.pc)), opcode: OpCode::name_by_op(step.opcode).to_string(), stack, memory, @@ -157,7 +157,7 @@ impl TracingStep { } } -fn u256_to_bigint(v: &edr_evm::U256) -> BigInt { +pub(crate) fn u256_to_bigint(v: &edr_evm::U256) -> BigInt { BigInt { sign_bit: false, words: v.into_limbs().to_vec(), diff --git a/crates/edr_napi/src/trace/debug.rs b/crates/edr_napi/src/trace/debug.rs index a47977321..dff820dbd 100644 --- a/crates/edr_napi/src/trace/debug.rs +++ b/crates/edr_napi/src/trace/debug.rs @@ -5,7 +5,7 @@ use edr_evm::{hex, interpreter::OpCode}; use edr_solidity::build_model::JumpType; use napi::{ bindgen_prelude::{Either24, Either3, Either4}, - Either, Env, + Either, Env, Status, }; use napi_derive::napi; @@ -200,7 +200,7 @@ fn trace_steps( let pc = format!("{:>5}", format!("{:03}", step.pc)); if let Some(bytecode) = bytecode { - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; let location = inst .location diff --git a/crates/edr_napi/src/trace/error_inferrer.rs b/crates/edr_napi/src/trace/error_inferrer.rs index c99b54271..dbe74c075 100644 --- a/crates/edr_napi/src/trace/error_inferrer.rs +++ b/crates/edr_napi/src/trace/error_inferrer.rs @@ -331,7 +331,7 @@ impl ErrorInferrer { _ => return Ok(Heuristic::Miss(stacktrace)), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; if let (OpCode::CALL | OpCode::CREATE, Either4::A(EvmStep { .. })) = (inst.opcode, next_step) @@ -537,7 +537,7 @@ impl ErrorInferrer { _ => panic!("This should not happen: MessageTrace should be preceded by a EVM step"), }; - let call_inst = bytecode.get_instruction(call_step.pc)?; + let call_inst = bytecode.get_instruction_napi(call_step.pc)?; let call_stack_frame = instruction_to_callstack_stack_trace_entry(bytecode, call_inst)?; let (call_stack_frame_source_reference, call_stack_frame) = match call_stack_frame { @@ -765,7 +765,7 @@ impl ErrorInferrer { _ => panic!("This should not happen: MessageTrace ends with a subtrace"), }; - let last_instruction = bytecode.get_instruction(last_step.pc)?; + let last_instruction = bytecode.get_instruction_napi(last_step.pc)?; let revert_or_invalid_stacktrace = Self::check_revert_or_invalid_opcode( trace, @@ -989,7 +989,7 @@ impl ErrorInferrer { return Ok(false); }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = bytecode.get_instruction_napi(last_step.pc)?; if last_inst.opcode != OpCode::REVERT { return Ok(false); } @@ -999,7 +999,7 @@ impl ErrorInferrer { Some(Either4::A(step)) => step, _ => panic!("JS code asserts this is always an EvmStep"), }; - let call_inst = bytecode.get_instruction(call_opcode_step.pc)?; + let call_inst = bytecode.get_instruction_napi(call_opcode_step.pc)?; // Calls are always made from within functions let call_inst_location = call_inst @@ -1041,7 +1041,7 @@ impl ErrorInferrer { _ => return Ok(false), }; - let step_inst = bytecode.get_instruction(step.pc)?; + let step_inst = bytecode.get_instruction_napi(step.pc)?; if let Some(step_inst_location) = &step_inst.location { if **step_inst_location != *location { @@ -1133,7 +1133,7 @@ impl ErrorInferrer { _ => return Ok(false), }; - let call_inst = bytecode.get_instruction(call_step.pc)?; + let call_inst = bytecode.get_instruction_napi(call_step.pc)?; if call_inst.opcode != OpCode::DELEGATECALL { return Ok(false); @@ -1173,7 +1173,7 @@ impl ErrorInferrer { _ => return Ok(false), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; // All the remaining locations should be valid, as they are part of the inline // asm @@ -1193,7 +1193,7 @@ impl ErrorInferrer { Some(Either4::A(step)) => step, _ => panic!("Expected last step to be an EvmStep"), }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = bytecode.get_instruction_napi(last_step.pc)?; Ok(last_inst.opcode == OpCode::REVERT) } @@ -1449,7 +1449,7 @@ impl ErrorInferrer { return Ok(false); }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = bytecode.get_instruction_napi(last_step.pc)?; if last_inst.opcode != OpCode::REVERT || last_inst.location.is_some() { return Ok(false); @@ -1462,7 +1462,7 @@ impl ErrorInferrer { _ => return Ok(false), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; if let Some(inst_location) = &inst.location { if contract.location != *inst_location && constructor.location != *inst_location { @@ -1548,7 +1548,7 @@ impl ErrorInferrer { _ => continue, }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; let Some(location) = &inst.location else { continue; @@ -1602,7 +1602,7 @@ impl ErrorInferrer { .bytecode .as_ref() .expect("The TS code type-checks this to always have bytecode") - .get_instruction(last_step.pc)?; + .get_instruction_napi(last_step.pc)?; Ok(match &last_instruction.location { Some(last_instruction_location) => { @@ -1737,7 +1737,7 @@ impl ErrorInferrer { _ => return Ok(false), }; - let last_instruction = bytecode.get_instruction(last_step.pc)?; + let last_instruction = bytecode.get_instruction_napi(last_step.pc)?; let Ok(version) = Version::parse(&bytecode.compiler_version) else { return Ok(false); @@ -1845,7 +1845,7 @@ impl ErrorInferrer { let has_next_inst = bytecode.has_instruction(next_inst_pc); if has_next_inst { - let next_inst = bytecode.get_instruction(next_inst_pc)?; + let next_inst = bytecode.get_instruction_napi(next_inst_pc)?; let prev_loc = prev_inst.and_then(|i| i.location.as_deref()); let next_loc = next_inst.location.as_deref(); @@ -2019,7 +2019,7 @@ impl ErrorInferrer { _ => panic!("We know this is an EVM step"), }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = bytecode.get_instruction_napi(last_step.pc)?; if last_inst.opcode != OpCode::ISZERO { return Ok(false); @@ -2030,7 +2030,7 @@ impl ErrorInferrer { _ => panic!("We know this is an EVM step"), }; - let prev_inst = bytecode.get_instruction(prev_step.pc)?; + let prev_inst = bytecode.get_instruction_napi(prev_step.pc)?; Ok(prev_inst.opcode == OpCode::EXTCODESIZE) } @@ -2053,7 +2053,7 @@ impl ErrorInferrer { _ => return Ok(None), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; if inst.location.is_some() { return Ok(Some(i as u32)); @@ -2083,7 +2083,7 @@ impl ErrorInferrer { match &steps.get(last_location_index as usize) { Some(Either4::A(step)) => { - let inst = bytecode.get_instruction(step.pc)?; + let inst = bytecode.get_instruction_napi(step.pc)?; Ok(Some(inst)) } diff --git a/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs b/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs index 4c7658f2c..eafeef902 100644 --- a/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs +++ b/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs @@ -167,7 +167,7 @@ fn match_opcodes( return Ok(false); }; - let instruction = bytecode.get_instruction(*pc)?; + let instruction = bytecode.get_instruction_napi(*pc)?; if instruction.opcode != *opcode { return Ok(false); diff --git a/crates/edr_napi/src/trace/message_trace.rs b/crates/edr_napi/src/trace/message_trace.rs index 45616ea55..c44f1165f 100644 --- a/crates/edr_napi/src/trace/message_trace.rs +++ b/crates/edr_napi/src/trace/message_trace.rs @@ -77,20 +77,20 @@ pub struct CallMessageTrace { pub code_address: Uint8Array, } -/// Converts [`edr_solidity::message_trace::MessageTraceStep`] to the N-API -/// representation. +/// Converts [`edr_solidity::message_trace::VmTracerMessageTraceStep`] to the +/// N-API representation. /// /// # Panics /// This function will panic if the value is mutably borrowed. pub fn message_trace_step_to_napi( - value: edr_solidity::message_trace::MessageTraceStep, + value: edr_solidity::message_trace::VmTracerMessageTraceStep, env: Env, ) -> napi::Result> { Ok(match value { - edr_solidity::message_trace::MessageTraceStep::Evm(step) => { + edr_solidity::message_trace::VmTracerMessageTraceStep::Evm(step) => { Either4::A(EvmStep { pc: step.pc as u32 }) } - edr_solidity::message_trace::MessageTraceStep::Message(msg) => { + edr_solidity::message_trace::VmTracerMessageTraceStep::Message(msg) => { // Immediately drop the borrow lock to err on the safe side as we // may be recursing. let owned = msg.borrow().clone(); diff --git a/crates/edr_napi/src/trace/solidity_stack_trace.rs b/crates/edr_napi/src/trace/solidity_stack_trace.rs index c090e4cea..1a6bf8039 100644 --- a/crates/edr_napi/src/trace/solidity_stack_trace.rs +++ b/crates/edr_napi/src/trace/solidity_stack_trace.rs @@ -8,6 +8,7 @@ use napi_derive::napi; use serde::{Serialize, Serializer}; use super::model::ContractFunctionType; +use crate::{cast::TryCast, trace::u256_to_bigint}; #[napi] #[repr(u8)] @@ -77,6 +78,20 @@ pub struct SourceReference { pub range: Vec, } +impl From for SourceReference { + fn from(value: edr_solidity::solidity_stack_trace::SourceReference) -> Self { + let (range_start, range_end) = value.range; + Self { + source_name: value.source_name, + source_content: value.source_content, + contract: value.contract, + function: value.function, + line: value.line, + range: vec![range_start, range_end], + } + } +} + /// A [`StackTraceEntryType`] constant that is convertible to/from a /// `napi_value`. /// @@ -599,6 +614,211 @@ pub type SolidityStackTraceEntry = Either24< ContractCallRunOutOfGasError, >; +impl TryCast for edr_solidity::solidity_stack_trace::StackTraceEntry { + type Error = napi::Error; + + fn try_cast(self) -> Result { + use edr_solidity::solidity_stack_trace::StackTraceEntry; + let result = match self { + StackTraceEntry::CallstackEntry { + source_reference, + function_type, + } => CallstackEntryStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + function_type: function_type.into(), + } + .into(), + StackTraceEntry::UnrecognizedCreateCallstackEntry => { + UnrecognizedCreateCallstackEntryStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: None, + } + .into() + } + StackTraceEntry::UnrecognizedContractCallstackEntry { address } => { + UnrecognizedContractCallstackEntryStackTraceEntry { + type_: StackTraceEntryTypeConst, + address: Uint8Array::from(address.as_slice()), + source_reference: None, + } + .into() + } + StackTraceEntry::PrecompileError { precompile } => PrecompileErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + precompile: precompile.into(), + source_reference: None, + } + .into(), + StackTraceEntry::RevertError { + return_data, + source_reference, + is_invalid_opcode_error, + } => RevertErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + return_data: return_data.into(), + source_reference: source_reference.into(), + is_invalid_opcode_error, + } + .into(), + StackTraceEntry::PanicError { + error_code, + source_reference, + } => PanicErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + error_code: u256_to_bigint(&error_code), + source_reference: source_reference.map(|x| x.into()), + } + .into(), + StackTraceEntry::CustomError { + message, + source_reference, + } => CustomErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + message: message.into(), + source_reference: source_reference.into(), + } + .into(), + StackTraceEntry::FunctionNotPayableError { + value, + source_reference, + } => FunctionNotPayableErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + value: u256_to_bigint(&value), + source_reference: source_reference.into(), + } + .into(), + StackTraceEntry::InvalidParamsError { source_reference } => { + InvalidParamsErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::FallbackNotPayableError { + value, + source_reference, + } => FallbackNotPayableErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + value: u256_to_bigint(&value), + source_reference: source_reference.into(), + } + .into(), + StackTraceEntry::FallbackNotPayableAndNoReceiveError { + value, + source_reference, + } => FallbackNotPayableAndNoReceiveErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + value: u256_to_bigint(&value), + source_reference: source_reference.into(), + } + .into(), + StackTraceEntry::UnrecognizedFunctionWithoutFallbackError { source_reference } => { + UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::MissingFallbackOrReceiveError { source_reference } => { + MissingFallbackOrReceiveErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::ReturndataSizeError { source_reference } => { + ReturndataSizeErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::NoncontractAccountCalledError { source_reference } => { + NonContractAccountCalledErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::CallFailedError { source_reference } => { + CallFailedErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::DirectLibraryCallError { source_reference } => { + DirectLibraryCallErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.into(), + } + .into() + } + StackTraceEntry::UnrecognizedCreateError { + return_data, + is_invalid_opcode_error, + } => UnrecognizedCreateErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + return_data: return_data.into(), + is_invalid_opcode_error, + source_reference: None, + } + .into(), + StackTraceEntry::UnrecognizedContractError { + address, + return_data, + is_invalid_opcode_error, + } => UnrecognizedContractErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + address: Uint8Array::from(address.as_slice()), + return_data: return_data.into(), + is_invalid_opcode_error, + source_reference: None, + } + .into(), + StackTraceEntry::OtherExecutionError { source_reference } => { + OtherExecutionErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.map(|x| x.into()), + } + .into() + } + StackTraceEntry::UnmappedSolc0_6_3RevertError { source_reference } => { + UnmappedSolc063RevertErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.map(|x| x.into()), + } + .into() + } + StackTraceEntry::ContractTooLargeError { source_reference } => { + ContractTooLargeErrorStackTraceEntry { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.map(|x| x.into()), + } + .into() + } + StackTraceEntry::InternalFunctionCallstackEntry { + pc, + source_reference, + } => InternalFunctionCallStackEntry { + type_: StackTraceEntryTypeConst, + pc: pc.into(), + source_reference: source_reference.into(), + } + .into(), + StackTraceEntry::ContractCallRunOutOfGasError { source_reference } => { + ContractCallRunOutOfGasError { + type_: StackTraceEntryTypeConst, + source_reference: source_reference.map(|x| x.into()), + } + .into() + } + }; + Ok(result) + } +} + #[allow(dead_code)] // Same as above, but for the `SolidityStackTrace` type. pub type SolidityStackTrace = Vec; diff --git a/crates/edr_napi/src/trace/solidity_tracer.rs b/crates/edr_napi/src/trace/solidity_tracer.rs index 895e25cbc..bf15a89c6 100644 --- a/crates/edr_napi/src/trace/solidity_tracer.rs +++ b/crates/edr_napi/src/trace/solidity_tracer.rs @@ -253,14 +253,14 @@ impl SolidityTracer { let mut iter = steps.iter().enumerate().peekable(); while let Some((step_index, step)) = iter.next() { if let Either4::A(EvmStep { pc }) = step { - let inst = bytecode.get_instruction(*pc)?; + let inst = bytecode.get_instruction_napi(*pc)?; if inst.jump_type == JumpType::IntoFunction && iter.peek().is_some() { let (_, next_step) = iter.peek().unwrap(); let Either4::A(next_evm_step) = next_step else { unreachable!("JS code asserted that"); }; - let next_inst = bytecode.get_instruction(next_evm_step.pc)?; + let next_inst = bytecode.get_instruction_napi(next_evm_step.pc)?; if next_inst.opcode == OpCode::JUMPDEST { let frame = instruction_to_callstack_stack_trace_entry(bytecode, inst)?; diff --git a/crates/edr_solidity/Cargo.toml b/crates/edr_solidity/Cargo.toml index 7d87600b3..411b22c3b 100644 --- a/crates/edr_solidity/Cargo.toml +++ b/crates/edr_solidity/Cargo.toml @@ -5,14 +5,18 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.75", default-features = false } -alloy-dyn-abi = { version = "0.7.6", default-features = false } +alloy-dyn-abi = { version = "0.7.6", default-features = false, features = ["eip712"] } alloy-json-abi = { version = "0.7.4", default-features = false } +alloy-sol-types = { version = "0.7.4", default-features = false, features = ["std"] } edr_eth = { version = "0.3.5", path = "../edr_eth" } edr_evm = { version = "0.3.5", path = "../edr_evm" } indexmap = { version = "2", features = ["serde"] } serde = { version = "1.0.158", default-features = false, features = ["std"] } serde_json = { version = "1.0.89", features = ["preserve_order"] } strum = { version = "0.26.0", features = ["derive"] } +thiserror = "1.0.58" +semver = "1.0.23" +either = "1.10.0" [lints] workspace = true diff --git a/crates/edr_solidity/src/build_model.rs b/crates/edr_solidity/src/build_model.rs index da0ca4a04..c745dfe53 100644 --- a/crates/edr_solidity/src/build_model.rs +++ b/crates/edr_solidity/src/build_model.rs @@ -17,7 +17,6 @@ use std::{ }; use alloy_dyn_abi::ErrorExt; -use anyhow::{self, Context as _}; use edr_evm::{hex, interpreter::OpCode}; use indexmap::IndexMap; use serde::Serialize; @@ -322,6 +321,12 @@ pub enum JumpType { InternalJump, } +#[derive(Clone, Debug, thiserror::Error)] +pub enum BytecodeError { + #[error("Instruction not found at PC {pc}")] + InstructionNotFound { pc: u32 }, +} + /// A resolved bytecode. #[derive(Debug)] pub struct Bytecode { @@ -376,10 +381,19 @@ impl Bytecode { } /// Returns the [`Instruction`] at the provided program counter (PC). - pub fn get_instruction(&self, pc: u32) -> anyhow::Result<&Instruction> { + pub fn get_instruction(&self, pc: u32) -> Result<&Instruction, BytecodeError> { self.pc_to_instruction .get(&pc) - .with_context(|| format!("Instruction at PC {pc} not found")) + .ok_or_else(|| BytecodeError::InstructionNotFound { pc }) + } + + /// Returns the [`Instruction`] at the provided program counter (PC). The + /// error type is `anyhow::Error` which can be converted to + /// `napi::Error` automatically. Usage of this method is deprecated and + /// call sites in `edr_napi` will be removed. + #[deprecated = "Use `get_instruction` instead"] + pub fn get_instruction_napi(&self, pc: u32) -> anyhow::Result<&Instruction> { + self.get_instruction(pc).map_err(anyhow::Error::from) } /// Whether the bytecode has an instruction at the provided program counter diff --git a/crates/edr_solidity/src/error_inferrer.rs b/crates/edr_solidity/src/error_inferrer.rs new file mode 100644 index 000000000..7d29023dd --- /dev/null +++ b/crates/edr_solidity/src/error_inferrer.rs @@ -0,0 +1,2159 @@ +use std::{borrow::Cow, collections::HashSet, mem}; + +use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; +use edr_eth::U256; +use edr_evm::{hex, interpreter::OpCode, HaltReason}; +use either::Either; +use semver::{Version, VersionReq}; + +use crate::{ + build_model::{ + Bytecode, BytecodeError, ContractFunction, ContractFunctionType, ContractKind, Instruction, + JumpType, SourceLocation, + }, + message_trace::{ + CallMessageTrace, CreateMessageTrace, ExitCode, MessageTrace, MessageTraceStep, + }, + return_data::ReturnData, + solidity_stack_trace::{ + SourceReference, StackTraceEntry, CONSTRUCTOR_FUNCTION_NAME, FALLBACK_FUNCTION_NAME, + RECEIVE_FUNCTION_NAME, + }, +}; + +const FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION: Version = Version::new(0, 5, 9); +const FIRST_SOLC_VERSION_RECEIVE_FUNCTION: Version = Version::new(0, 6, 0); +const FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS: &str = "0.6.3"; + +/// Specifies whether a heuristic was applied and modified the stack trace. +/// +/// Think of it as happy [`Result`] - the [`Heuristic::Hit`] should be +/// propagated to the caller. +#[must_use] +pub enum Heuristic { + /// The heuristic was applied and modified the stack trace. + Hit(Vec), + /// The heuristic did not apply; the stack trace is unchanged. + Miss(Vec), +} + +#[derive(Clone, Debug)] +pub struct SubmessageData { + pub message_trace: MessageTrace, + pub stacktrace: Vec, + pub step_index: u32, +} + +#[derive(Debug, thiserror::Error)] +pub enum InferrerError { + #[error("{0}")] + Abi(String), + #[error(transparent)] + BytecodeError(#[from] BytecodeError), + #[error("Expected EVM step")] + ExpectedEvmStep, + #[error("Missing contract")] + MissingContract, + #[error("call trace has no functionJumpdest but has already jumped into a function")] + MissingFunctionJumpDest(CallMessageTrace), + #[error("Missing source reference")] + MissingSourceReference, + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + #[error(transparent)] + SolidityTypes(#[from] alloy_sol_types::Error), +} + +// Automatic conversion from `alloy_dyn_abi::Error` to `InferrerError` is not +// possible due to unsatisifed trait bounds. +impl From for InferrerError { + fn from(err: alloy_dyn_abi::Error) -> Self { + Self::Abi(err.to_string()) + } +} + +pub fn filter_redundant_frames( + stacktrace: Vec, +) -> Result, InferrerError> { + // To work around the borrow checker, we'll collect the indices of the frames we + // want to keep. We can't clone the frames, because some of them contain + // non-Clone `ClassInstance`s` + let retained_indices: HashSet<_> = stacktrace + .iter() + .enumerate() + .filter(|(idx, frame)| { + let next_frame = stacktrace.get(idx + 1); + let next_next_frame = stacktrace.get(idx + 2); + + let Some(next_frame) = next_frame else { + return true; + }; + + // we can only filter frames if we know their sourceReference + // and the one from the next frame + let (Some(frame_source), Some(next_frame_source)) = + (frame.source_reference(), next_frame.source_reference()) + else { + return true; + }; + + // look TWO frames ahead to determine if this is a specific occurrence of + // a redundant CALLSTACK_ENTRY frame observed when using Solidity 0.8.5: + match (&frame, next_next_frame) { + ( + StackTraceEntry::CallstackEntry { + source_reference, .. + }, + Some(StackTraceEntry::ReturndataSizeError { + source_reference: next_next_source_reference, + .. + }), + ) if source_reference.range == next_next_source_reference.range + && source_reference.line == next_next_source_reference.line => + { + return false; + } + _ => {} + } + + if frame_source.function.as_deref() == Some("constructor") + && next_frame_source.function.as_deref() != Some("constructor") + { + return true; + } + + // this is probably a recursive call + if *idx > 0 + && mem::discriminant(*frame) == mem::discriminant(next_frame) + && frame_source.range == next_frame_source.range + && frame_source.line == next_frame_source.line + { + return true; + } + + if frame_source.range.0 <= next_frame_source.range.0 + && frame_source.range.1 >= next_frame_source.range.1 + { + return false; + } + + true + }) + .map(|(idx, _)| idx) + .collect(); + + Ok(stacktrace + .into_iter() + .enumerate() + .filter(|(idx, _)| retained_indices.contains(idx)) + .map(|(_, frame)| frame) + .collect()) +} + +pub fn infer_after_tracing( + trace: &Either, + stacktrace: Vec, + function_jumpdests: &[&Instruction], + jumped_into_function: bool, + last_submessage_data: Option, +) -> Result, InferrerError> { + /// Convenience macro to early return the result if a heuristic hits. + macro_rules! return_if_hit { + ($heuristic: expr) => { + match $heuristic { + Heuristic::Hit(stacktrace) => return Ok(stacktrace), + Heuristic::Miss(stacktrace) => stacktrace, + } + }; + } + + // TODO clean this up + let trace = match trace { + Either::Left(call) => Either::Left(call), + Either::Right(create) => Either::Right(create), + }; + + let result = check_last_submessage(trace, stacktrace, last_submessage_data)?; + let stacktrace = return_if_hit!(result); + + let result = check_failed_last_call(trace, stacktrace)?; + let stacktrace = return_if_hit!(result); + + let result = + check_last_instruction(trace, stacktrace, function_jumpdests, jumped_into_function)?; + let stacktrace = return_if_hit!(result); + + let result = check_non_contract_called(trace, stacktrace)?; + let stacktrace = return_if_hit!(result); + + let result = check_solidity_0_6_3_unmapped_revert(trace, stacktrace)?; + let stacktrace = return_if_hit!(result); + + if let Some(result) = check_contract_too_large(trace)? { + return Ok(result); + } + + let stacktrace = other_execution_error_stacktrace(trace, stacktrace)?; + Ok(stacktrace) +} + +pub fn infer_before_tracing_call_message( + trace: &CallMessageTrace, +) -> Result>, InferrerError> { + if is_direct_library_call(trace)? { + return Ok(Some(get_direct_library_call_error_stack_trace(trace)?)); + } + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + let called_function = + contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); + + if let Some(called_function) = called_function { + if is_function_not_payable_error(trace, called_function)? { + return Ok(Some(vec![StackTraceEntry::FunctionNotPayableError { + source_reference: get_function_start_source_reference( + Either::Left(trace), + called_function, + )?, + value: trace.value().clone(), + } + .into()])); + } + } + + let called_function = called_function.map(AsRef::as_ref); + + if is_missing_function_and_fallback_error(trace, called_function)? { + let source_reference = + get_contract_start_without_function_source_reference(Either::Left(trace))?; + + if empty_calldata_and_no_receive(trace)? { + return Ok(Some(vec![StackTraceEntry::MissingFallbackOrReceiveError { + source_reference, + }])); + } + + return Ok(Some(vec![ + StackTraceEntry::UnrecognizedFunctionWithoutFallbackError { source_reference }, + ])); + } + + if is_fallback_not_payable_error(trace, called_function)? { + let source_reference = get_fallback_start_source_reference(trace)?; + + if empty_calldata_and_no_receive(trace)? { + return Ok(Some(vec![ + StackTraceEntry::FallbackNotPayableAndNoReceiveError { + source_reference, + value: trace.value().clone(), + }, + ])); + } + + return Ok(Some(vec![StackTraceEntry::FallbackNotPayableError { + source_reference, + value: trace.value().clone(), + }])); + } + + Ok(None) +} + +pub fn infer_before_tracing_create_message( + trace: &CreateMessageTrace, +) -> Result>, InferrerError> { + if is_constructor_not_payable_error(trace)? { + return Ok(Some(vec![StackTraceEntry::FunctionNotPayableError { + source_reference: get_constructor_start_source_reference(trace)?, + value: trace.value().clone(), + }])); + } + + if is_constructor_invalid_arguments_error(trace)? { + return Ok(Some(vec![StackTraceEntry::InvalidParamsError { + source_reference: get_constructor_start_source_reference(trace)?, + }])); + } + + Ok(None) +} + +pub fn instruction_to_callstack_stack_trace_entry( + bytecode: &Bytecode, + inst: &Instruction, +) -> Result { + let contract = bytecode.contract.borrow(); + + // This means that a jump is made from within an internal solc function. + // These are normally made from yul code, so they don't map to any Solidity + // function + let inst_location = match &inst.location { + None => { + let location = &contract.location; + let file = location.file(); + let file = file.borrow(); + + return Ok(StackTraceEntry::InternalFunctionCallstackEntry { + pc: inst.pc, + source_reference: SourceReference { + source_name: file.source_name.clone(), + source_content: file.content.clone(), + contract: Some(contract.name.clone()), + function: None, + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }, + }); + } + Some(inst_location) => inst_location, + }; + + if let Some(func) = inst_location.get_containing_function() { + let source_reference = source_location_to_source_reference(bytecode, Some(inst_location)) + .ok_or(InferrerError::MissingSourceReference)?; + + return Ok(StackTraceEntry::CallstackEntry { + source_reference, + function_type: func.r#type.into(), + }); + }; + + let file = inst_location.file(); + let file = file.borrow(); + + Ok(StackTraceEntry::CallstackEntry { + source_reference: SourceReference { + function: None, + contract: Some(contract.name.clone()), + source_name: file.source_name.clone(), + source_content: file.content.clone(), + line: inst_location.get_starting_line_number(), + range: ( + inst_location.offset, + inst_location.offset + inst_location.length, + ), + }, + function_type: ContractFunctionType::Function.into(), + }) +} + +fn call_instruction_to_call_failed_to_execute_stack_trace_entry( + bytecode: &Bytecode, + call_inst: &Instruction, +) -> Result { + let location = call_inst.location.as_deref(); + + let source_reference = source_location_to_source_reference(bytecode, location) + .ok_or(InferrerError::MissingSourceReference)?; + + // Calls only happen within functions + Ok(StackTraceEntry::CallFailedError { source_reference }) +} + +fn check_contract_too_large( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result>, InferrerError> { + if let Either::Right(create) = trace { + if matches!( + create.exit(), + ExitCode::Halt(HaltReason::CreateContractSizeLimit) + ) { + return Ok(Some(vec![StackTraceEntry::ContractTooLargeError { + source_reference: Some(get_constructor_start_source_reference(create)?), + }])); + } + } + Ok(None) +} + +fn check_custom_errors( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + stacktrace: Vec, + last_instruction: &Instruction, +) -> Result { + let (bytecode, return_data) = match &trace { + Either::Left(call) => (call.bytecode(), call.return_data()), + Either::Right(create) => (create.bytecode(), create.return_data()), + }; + + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + let return_data = ReturnData::new(return_data.clone()); + + if return_data.is_empty() || return_data.is_error_return_data() { + // if there is no return data, or if it's a Error(string), + // then it can't be a custom error + return Ok(Heuristic::Miss(stacktrace)); + } + + let raw_return_data = hex::encode(&*return_data.value); + let mut error_message = + format!("reverted with an unrecognized custom error (return data: 0x{raw_return_data})",); + + for custom_error in &contract.custom_errors { + if return_data.matches_selector(custom_error.selector) { + // if the return data matches a custom error in the called contract, + // we format the message using the returnData and the custom error instance + let decoded = custom_error.decode_error_data(&return_data.value)?; + + let params = decoded + .body + .iter() + .map(format_dyn_sol_value) + .collect::>(); + + error_message = format!( + "reverted with custom error '{name}({params})'", + name = custom_error.name, + params = params.join(", ") + ); + + break; + } + } + + let mut stacktrace = stacktrace; + stacktrace.push( + instruction_within_function_to_custom_error_stack_trace_entry( + trace, + last_instruction, + error_message, + )? + .into(), + ); + + fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit) +} + +/// Check if the last call/create that was done failed. +fn check_failed_last_call( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + stacktrace: Vec, +) -> Result { + let (bytecode, steps) = match &trace { + Either::Left(call) => (call.bytecode(), call.steps()), + Either::Right(create) => (create.bytecode(), create.steps()), + }; + + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + if steps.is_empty() { + return Ok(Heuristic::Miss(stacktrace)); + } + + for step_index in (0..steps.len() - 1).rev() { + let (step, next_step) = match &steps[step_index..][..2] { + &[MessageTraceStep::Evm(ref step), ref next_step] => (step, next_step), + _ => return Ok(Heuristic::Miss(stacktrace)), + }; + + let inst = bytecode.get_instruction(step.pc)?; + + if let (OpCode::CALL | OpCode::CREATE, MessageTraceStep::Evm(_)) = (inst.opcode, next_step) + { + if is_call_failed_error(trace, step_index as u32, inst)? { + let mut inferred_stacktrace = stacktrace.clone(); + inferred_stacktrace.push( + call_instruction_to_call_failed_to_execute_stack_trace_entry(bytecode, inst)?, + ); + + return Ok(Heuristic::Hit(fix_initial_modifier( + trace, + inferred_stacktrace, + )?)); + } + } + } + + Ok(Heuristic::Miss(stacktrace)) +} + +fn check_last_instruction( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + stacktrace: Vec, + function_jumpdests: &[&Instruction], + jumped_into_function: bool, +) -> Result { + let (bytecode, steps) = match &trace { + Either::Left(call) => (call.bytecode(), call.steps()), + Either::Right(create) => (create.bytecode(), create.steps()), + }; + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + + if steps.is_empty() { + return Ok(Heuristic::Miss(stacktrace)); + } + + let last_step = match steps.last() { + Some(MessageTraceStep::Evm(step)) => step, + _ => panic!("This should not happen: MessageTrace ends with a subtrace"), + }; + + let last_instruction = bytecode.get_instruction(last_step.pc)?; + + let revert_or_invalid_stacktrace = check_revert_or_invalid_opcode( + trace, + stacktrace, + last_instruction, + function_jumpdests, + jumped_into_function, + )?; + let stacktrace = match revert_or_invalid_stacktrace { + hit @ Heuristic::Hit(..) => return Ok(hit), + Heuristic::Miss(stacktrace) => stacktrace, + }; + + let (Either::Left(trace @ CallMessageTrace { ref calldata, .. }), false) = + (&trace, jumped_into_function) + else { + return Ok(Heuristic::Miss(stacktrace)); + }; + + if has_failed_inside_the_fallback_function(trace)? + || has_failed_inside_the_receive_function(trace)? + { + let frame = instruction_within_function_to_revert_stack_trace_entry( + Either::Left(trace), + last_instruction, + )?; + + return Ok(Heuristic::Hit(vec![frame.into()])); + } + + // Sometimes we do fail inside of a function but there's no jump into + if let Some(location) = &last_instruction.location { + let failing_function = location.get_containing_function(); + + if let Some(failing_function) = failing_function { + let frame = StackTraceEntry::RevertError { + source_reference: get_function_start_source_reference( + Either::Left(trace), + &failing_function, + )?, + return_data: trace.return_data().clone(), + is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID, + }; + + return Ok(Heuristic::Hit(vec![frame.into()])); + } + } + + let contract = bytecode.contract.borrow(); + + let selector = calldata.get(..4).unwrap_or(&calldata[..]); + let calldata = &calldata.get(4..).unwrap_or(&[]); + + let called_function = contract.get_function_from_selector(selector); + + if let Some(called_function) = called_function { + let abi = alloy_json_abi::Function::try_from(&**called_function)?; + + let is_valid_calldata = match &called_function.param_types { + Some(_) => abi.abi_decode_input(calldata, true).is_ok(), + // if we don't know the param types, we just assume that the call is valid + None => true, + }; + + if !is_valid_calldata { + let frame = StackTraceEntry::InvalidParamsError { + source_reference: get_function_start_source_reference( + Either::Left(trace), + called_function, + )?, + }; + + return Ok(Heuristic::Hit(vec![frame.into()])); + } + } + + if solidity_0_6_3_maybe_unmapped_revert(Either::Left(trace))? { + let revert_frame = solidity_0_6_3_get_frame_for_unmapped_revert_before_function(trace)?; + + if let Some(revert_frame) = revert_frame { + return Ok(Heuristic::Hit(vec![revert_frame.into()])); + } + } + + let frame = get_other_error_before_called_function_stack_trace_entry(trace)?; + + Ok(Heuristic::Hit(vec![frame.into()])) +} + +/// Check if the last submessage can be used to generate the stack trace. +fn check_last_submessage( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + stacktrace: Vec, + last_submessage_data: Option, +) -> Result { + let (bytecode, steps) = match &trace { + Either::Left(call) => (call.bytecode(), call.steps()), + Either::Right(create) => (create.bytecode(), create.steps()), + }; + + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + let Some(last_submessage_data) = last_submessage_data else { + return Ok(Heuristic::Miss(stacktrace)); + }; + + let mut inferred_stacktrace = Cow::from(&stacktrace); + + // get the instruction before the submessage and add it to the stack trace + let call_step = match steps.get(last_submessage_data.step_index as usize - 1) { + Some(MessageTraceStep::Evm(call_step)) => call_step, + _ => panic!("This should not happen: MessageTrace should be preceded by a EVM step"), + }; + + let call_inst = bytecode.get_instruction(call_step.pc)?; + let call_stack_frame = instruction_to_callstack_stack_trace_entry(bytecode, call_inst)?; + // TODO: remove this expect + let call_stack_frame_source_reference = call_stack_frame + .source_reference() + .cloned() + .expect("Callstack entry must have source reference"); + + let last_message_failed = match &last_submessage_data.message_trace { + MessageTrace::Create(create) => create.exit().is_error(), + MessageTrace::Call(call) => call.exit().is_error(), + MessageTrace::Precompile(precompile) => precompile.exit().is_error(), + }; + if last_message_failed { + // add the call/create that generated the message to the stack trace + let inferred_stacktrace = inferred_stacktrace.to_mut(); + inferred_stacktrace.push(call_stack_frame); + + if is_subtrace_error_propagated(trace, last_submessage_data.step_index)? + || is_proxy_error_propagated(trace, last_submessage_data.step_index)? + { + inferred_stacktrace.extend(last_submessage_data.stacktrace); + + if is_contract_call_run_out_of_gas_error(trace, last_submessage_data.step_index)? { + let last_frame = match inferred_stacktrace.pop() { + Some(frame) => frame, + _ => panic!("Expected inferred stack trace to have at least one frame"), + }; + + inferred_stacktrace.push(StackTraceEntry::ContractCallRunOutOfGasError { + source_reference: last_frame.source_reference().cloned(), + }); + } + + return fix_initial_modifier(trace, inferred_stacktrace.to_owned()).map(Heuristic::Hit); + } + } else { + let is_return_data_size_error = + fails_right_after_call(trace, last_submessage_data.step_index)?; + if is_return_data_size_error { + inferred_stacktrace + .to_mut() + .push(StackTraceEntry::ReturndataSizeError { + source_reference: call_stack_frame_source_reference, + }); + + return fix_initial_modifier(trace, inferred_stacktrace.into_owned()) + .map(Heuristic::Hit); + } + } + + Ok(Heuristic::Miss(stacktrace)) +} + +/// Check if the trace reverted with a panic error. +fn check_panic( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + mut stacktrace: Vec, + last_instruction: &Instruction, +) -> Result { + let return_data = ReturnData::new( + match &trace { + Either::Left(call) => call.return_data(), + Either::Right(create) => create.return_data(), + } + .clone(), + ); + + if !return_data.is_panic_return_data() { + return Ok(Heuristic::Miss(stacktrace)); + } + + // If the last frame is an internal function, it means that the trace + // jumped there to return the panic. If that's the case, we remove that + // frame. + if let Some(StackTraceEntry::InternalFunctionCallstackEntry { .. }) = stacktrace.last() { + stacktrace.pop(); + } + + // if the error comes from a call to a zero-initialized function, + // we remove the last frame, which represents the call, to avoid + // having duplicated frames + let error_code = return_data.decode_panic()?; + if error_code == U256::from(0x51) { + stacktrace.pop(); + } + + stacktrace.push( + instruction_within_function_to_panic_stack_trace_entry( + trace, + last_instruction, + error_code, + )? + .into(), + ); + + fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit) +} + +fn check_non_contract_called( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + mut stacktrace: Vec, +) -> Result { + if is_called_non_contract_account_error(trace)? { + let source_reference = get_last_source_reference(trace)?; + + // We are sure this is not undefined because there was at least a call + // instruction + let source_reference = source_reference.expect("Expected source reference to be defined"); + + let non_contract_called_frame = + StackTraceEntry::NoncontractAccountCalledError { source_reference }; + + stacktrace.push(non_contract_called_frame); + + Ok(Heuristic::Hit(stacktrace)) + } else { + Ok(Heuristic::Miss(stacktrace)) + } +} + +/// Check if the execution stopped with a revert or an invalid opcode. +fn check_revert_or_invalid_opcode( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + stacktrace: Vec, + last_instruction: &Instruction, + function_jumpdests: &[&Instruction], + jumped_into_function: bool, +) -> Result { + match last_instruction.opcode { + OpCode::REVERT | OpCode::INVALID => {} + _ => return Ok(Heuristic::Miss(stacktrace)), + } + + let (bytecode, return_data) = match &trace { + Either::Left(call) => (call.bytecode(), call.return_data()), + Either::Right(create) => (create.bytecode(), create.return_data()), + }; + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + let mut inferred_stacktrace = stacktrace.clone(); + + if let Some(location) = &last_instruction.location { + if jumped_into_function || matches!(trace, Either::Right(CreateMessageTrace { .. })) { + // There should always be a function here, but that's not the case with + // optimizations. + // + // If this is a create trace, we already checked args and nonpayable failures + // before calling this function. + // + // If it's a call trace, we already jumped into a function. But optimizations + // can happen. + let failing_function = location.get_containing_function(); + + // If the failure is in a modifier we add an entry with the function/constructor + match failing_function { + Some(func) if func.r#type == ContractFunctionType::Modifier => { + let frame = get_entry_before_failure_in_modifier(trace, function_jumpdests)?; + + inferred_stacktrace.push(frame); + } + _ => {} + } + } + } + + let panic_stacktrace = check_panic(trace, inferred_stacktrace, last_instruction)?; + let inferred_stacktrace = match panic_stacktrace { + hit @ Heuristic::Hit(..) => return Ok(hit), + Heuristic::Miss(stacktrace) => stacktrace, + }; + + let custom_error_stacktrace = + check_custom_errors(trace, inferred_stacktrace, last_instruction)?; + let mut inferred_stacktrace = match custom_error_stacktrace { + hit @ Heuristic::Hit(..) => return Ok(hit), + Heuristic::Miss(stacktrace) => stacktrace, + }; + + if let Some(location) = &last_instruction.location { + if jumped_into_function || matches!(trace, Either::Right(CreateMessageTrace { .. })) { + let failing_function = location.get_containing_function(); + + if failing_function.is_some() { + let frame = instruction_within_function_to_revert_stack_trace_entry( + trace, + last_instruction, + )?; + + inferred_stacktrace.push(frame.into()); + } else { + let is_invalid_opcode_error = last_instruction.opcode == OpCode::INVALID; + + match &trace { + Either::Left(CallMessageTrace { calldata, .. }) => { + let contract = bytecode.contract.borrow(); + + // This is here because of the optimizations + let function_from_selector = contract + .get_function_from_selector(calldata.get(..4).unwrap_or(&calldata[..])); + + // in general this shouldn't happen, but it does when viaIR is enabled, + // "optimizerSteps": "u" is used, and the called function is fallback or + // receive + let Some(function) = function_from_selector else { + return Ok(Heuristic::Miss(inferred_stacktrace)); + }; + + let frame = StackTraceEntry::RevertError { + source_reference: get_function_start_source_reference(trace, function)?, + return_data: return_data.clone(), + is_invalid_opcode_error, + }; + + inferred_stacktrace.push(frame.into()); + } + Either::Right(trace @ CreateMessageTrace { .. }) => { + // This is here because of the optimizations + let frame = StackTraceEntry::RevertError { + source_reference: get_constructor_start_source_reference(trace)?, + return_data: return_data.clone(), + is_invalid_opcode_error, + }; + + inferred_stacktrace.push(frame.into()); + } + } + } + + return fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit); + } + } + + // If the revert instruction is not mapped but there is return data, + // we add the frame anyway, sith the best sourceReference we can get + if last_instruction.location.is_none() && !return_data.is_empty() { + let revert_frame = StackTraceEntry::RevertError { + source_reference: get_contract_start_without_function_source_reference(trace)?, + return_data: return_data.clone(), + is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID, + }; + + inferred_stacktrace.push(revert_frame.into()); + + return fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit); + } + + Ok(Heuristic::Miss(stacktrace)) +} + +fn check_solidity_0_6_3_unmapped_revert( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + mut stacktrace: Vec, +) -> Result { + if solidity_0_6_3_maybe_unmapped_revert(trace)? { + let revert_frame = solidity_0_6_3_get_frame_for_unmapped_revert_within_function(trace)?; + + if let Some(revert_frame) = revert_frame { + stacktrace.push(revert_frame.into()); + + return Ok(Heuristic::Hit(stacktrace)); + } + + return Ok(Heuristic::Hit(stacktrace)); + } + + Ok(Heuristic::Miss(stacktrace)) +} + +fn empty_calldata_and_no_receive(trace: &CallMessageTrace) -> Result { + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + let version = + Version::parse(&bytecode.compiler_version).expect("Failed to parse SemVer version"); + + // this only makes sense when receive functions are available + if version < FIRST_SOLC_VERSION_RECEIVE_FUNCTION { + return Ok(false); + } + + Ok(trace.calldata.is_empty() && contract.receive.is_none()) +} + +fn fails_right_after_call( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + call_subtrace_step_index: u32, +) -> Result { + let (bytecode, steps) = match &trace { + Either::Left(call) => (call.bytecode(), call.steps()), + Either::Right(create) => (create.bytecode(), create.steps()), + }; + + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + + let Some(MessageTraceStep::Evm(last_step)) = steps.last() else { + return Ok(false); + }; + + let last_inst = bytecode.get_instruction(last_step.pc)?; + if last_inst.opcode != OpCode::REVERT { + return Ok(false); + } + + let call_opcode_step = steps.get(call_subtrace_step_index as usize - 1); + let call_opcode_step = match call_opcode_step { + Some(MessageTraceStep::Evm(step)) => step, + _ => return Err(InferrerError::ExpectedEvmStep), + }; + let call_inst = bytecode.get_instruction(call_opcode_step.pc)?; + + // Calls are always made from within functions + let call_inst_location = call_inst + .location + .as_ref() + .expect("Expected call instruction location to be defined"); + + is_last_location(trace, call_subtrace_step_index + 1, call_inst_location) +} + +fn fix_initial_modifier( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + mut stacktrace: Vec, +) -> Result, InferrerError> { + if let Some(StackTraceEntry::CallstackEntry { + function_type: ContractFunctionType::Modifier, + .. + }) = stacktrace.first() + { + let entry_before_initial_modifier = + get_entry_before_initial_modifier_callstack_entry(trace)?; + + stacktrace.insert(0, entry_before_initial_modifier); + } + + Ok(stacktrace) +} + +// Rewrite of `AbiHelpers.formatValues` from Hardhat +fn format_dyn_sol_value(val: &DynSolValue) -> String { + match val { + // print nested values as [value1, value2, ...] + DynSolValue::Array(items) + | DynSolValue::Tuple(items) + | DynSolValue::FixedArray(items) + | DynSolValue::CustomStruct { tuple: items, .. } => { + let mut result = String::from("["); + for (i, val) in items.iter().enumerate() { + if i > 0 { + result.push_str(", "); + } + result.push_str(&format_dyn_sol_value(val)); + } + + result.push(']'); + result + } + // surround string values with quotes + DynSolValue::String(s) => format!("\"{s}\""), + + DynSolValue::Address(address) => format!("\"{address}\""), + DynSolValue::Bytes(bytes) => format!("\"{}\"", hex::encode_prefixed(bytes)), + DynSolValue::FixedBytes(word, size) => { + format!("\"{}\"", hex::encode_prefixed(&word.0.as_slice()[..*size])) + } + DynSolValue::Bool(b) => b.to_string(), + DynSolValue::Function(_) => "".to_string(), + DynSolValue::Int(int, _bits) => int.to_string(), + DynSolValue::Uint(uint, _bits) => uint.to_string(), + } +} + +fn get_constructor_start_source_reference( + trace: &CreateMessageTrace, +) -> Result { + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + let contract_location = &contract.location; + + let line = match &contract.constructor { + Some(constructor) => constructor.location.get_starting_line_number(), + None => contract_location.get_starting_line_number(), + }; + + let file = contract_location.file(); + let file = file.borrow(); + + Ok(SourceReference { + source_name: file.source_name.clone(), + source_content: file.content.clone(), + contract: Some(contract.name.clone()), + function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), + line, + range: ( + contract_location.offset, + contract_location.offset + contract_location.length, + ), + }) +} + +fn get_contract_start_without_function_source_reference( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result { + let bytecode = match &trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + }; + + let contract = &bytecode.ok_or(InferrerError::MissingContract)?.contract; + let contract = contract.borrow(); + + let location = &contract.location; + let file = location.file(); + let file = file.borrow(); + + Ok(SourceReference { + source_name: file.source_name.clone(), + source_content: file.content.clone(), + contract: Some(contract.name.clone()), + + function: None, + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }) +} + +fn get_direct_library_call_error_stack_trace( + trace: &CallMessageTrace, +) -> Result, InferrerError> { + let contract = &trace + .bytecode() + .ok_or(InferrerError::MissingContract)? + .contract; + let contract = contract.borrow(); + + let func = + contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); + + let source_reference = match func { + Some(func) => get_function_start_source_reference(Either::Left(trace), func)?, + None => get_contract_start_without_function_source_reference(Either::Left(trace))?, + }; + + Ok(vec![StackTraceEntry::DirectLibraryCallError { + source_reference, + }]) +} + +fn get_function_start_source_reference( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + func: &ContractFunction, +) -> Result { + let bytecode = match &trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + }; + + let contract = &bytecode.ok_or(InferrerError::MissingContract)?.contract; + let contract = contract.borrow(); + + let file = func.location.file(); + let file = file.borrow(); + + let location = &func.location; + + Ok(SourceReference { + source_name: file.source_name.clone(), + source_content: file.content.clone(), + contract: Some(contract.name.clone()), + + function: Some(func.name.clone()), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }) +} + +fn get_entry_before_initial_modifier_callstack_entry( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result { + let trace = match trace { + Either::Right(create) => { + return Ok(StackTraceEntry::CallstackEntry { + source_reference: get_constructor_start_source_reference(create)?, + function_type: ContractFunctionType::Constructor.into(), + }) + } + Either::Left(call) => call, + }; + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + let called_function = + contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); + + let source_reference = match called_function { + Some(called_function) => { + get_function_start_source_reference(Either::Left(trace), called_function)? + } + None => get_fallback_start_source_reference(trace)?, + }; + + let function_type = match called_function { + Some(_) => ContractFunctionType::Function, + None => ContractFunctionType::Fallback, + }; + + Ok(StackTraceEntry::CallstackEntry { + source_reference, + function_type: function_type.into(), + }) +} + +fn get_entry_before_failure_in_modifier( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + function_jumpdests: &[&Instruction], +) -> Result { + let bytecode = match &trace { + Either::Left(call) => call.bytecode(), + Either::Right(create) => create.bytecode(), + }; + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + // If there's a jumpdest, this modifier belongs to the last function that it + // represents + if let Some(last_jumpdest) = function_jumpdests.last() { + let entry = instruction_to_callstack_stack_trace_entry(bytecode, last_jumpdest)?; + + return Ok(entry); + } + + // This function is only called after we jumped into the initial function in + // call traces, so there should always be at least a function jumpdest. + let trace = match trace { + Either::Left(call) => return Err(InferrerError::MissingFunctionJumpDest(call.clone())), + Either::Right(create) => create, + }; + + // If there's no jump dest, we point to the constructor. + Ok(StackTraceEntry::CallstackEntry { + source_reference: get_constructor_start_source_reference(trace)?, + function_type: ContractFunctionType::Constructor.into(), + }) +} + +fn get_fallback_start_source_reference( + trace: &CallMessageTrace, +) -> Result { + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + let func = match &contract.fallback { + Some(func) => func, + None => panic!("This shouldn't happen: trying to get fallback source reference from a contract without fallback"), + }; + + let location = &func.location; + let file = location.file(); + let file = file.borrow(); + + Ok(SourceReference { + source_name: file.source_name.clone(), + source_content: file.content.clone(), + contract: Some(contract.name.clone()), + function: Some(FALLBACK_FUNCTION_NAME.to_string()), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }) +} + +fn get_last_instruction_with_valid_location_step_index( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result, InferrerError> { + let (bytecode, steps) = match &trace { + Either::Left(create) => (create.bytecode(), create.steps()), + Either::Right(call) => (call.bytecode(), call.steps()), + }; + + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + + for (i, step) in steps.iter().enumerate().rev() { + let step = match step { + MessageTraceStep::Evm(step) => step, + _ => return Ok(None), + }; + + let inst = bytecode.get_instruction(step.pc)?; + + if inst.location.is_some() { + return Ok(Some(i as u32)); + } + } + + Ok(None) +} + +fn get_last_instruction_with_valid_location<'a>( + trace: Either<&'a CallMessageTrace, &'a CreateMessageTrace>, +) -> Result, InferrerError> { + let last_location_index = get_last_instruction_with_valid_location_step_index(trace)?; + + let Some(last_location_index) = last_location_index else { + return Ok(None); + }; + + let (bytecode, steps) = match &trace { + Either::Left(create) => (create.bytecode(), create.steps()), + Either::Right(call) => (call.bytecode(), call.steps()), + }; + + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + + match &steps.get(last_location_index as usize) { + Some(MessageTraceStep::Evm(step)) => { + let inst = bytecode.get_instruction(step.pc)?; + + Ok(Some(inst)) + } + _ => Ok(None), + } +} +fn get_last_source_reference( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result, InferrerError> { + let (bytecode, steps) = match trace { + Either::Left(create) => (create.bytecode(), create.steps()), + Either::Right(call) => (call.bytecode(), call.steps()), + }; + + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + + for step in steps.iter().rev() { + let step = match step { + MessageTraceStep::Evm(step) => step, + _ => continue, + }; + + let inst = bytecode.get_instruction(step.pc)?; + + let Some(location) = &inst.location else { + continue; + }; + + let source_reference = source_location_to_source_reference(bytecode, Some(location)); + + if let Some(source_reference) = source_reference { + return Ok(Some(source_reference)); + } + } + + Ok(None) +} + +fn get_other_error_before_called_function_stack_trace_entry( + trace: &CallMessageTrace, +) -> Result { + let source_reference = + get_contract_start_without_function_source_reference(Either::Left(trace))?; + + Ok(StackTraceEntry::OtherExecutionError { + source_reference: Some(source_reference), + }) +} + +fn has_failed_inside_the_fallback_function( + trace: &CallMessageTrace, +) -> Result { + let contract = &trace + .bytecode() + .ok_or(InferrerError::MissingContract)? + .contract; + let contract = contract.borrow(); + + match &contract.fallback { + Some(fallback) => has_failed_inside_function(trace, fallback), + None => Ok(false), + } +} + +fn has_failed_inside_the_receive_function(trace: &CallMessageTrace) -> Result { + let contract = &trace + .bytecode() + .ok_or(InferrerError::MissingContract)? + .contract; + let contract = contract.borrow(); + + match &contract.receive { + Some(receive) => has_failed_inside_function(trace, receive), + None => Ok(false), + } +} + +fn has_failed_inside_function( + trace: &CallMessageTrace, + func: &ContractFunction, +) -> Result { + let last_step = trace + .steps() + .into_iter() + .last() + .expect("There should at least be one step"); + + let last_step = match last_step { + MessageTraceStep::Evm(step) => step, + _ => panic!("JS code asserted this is always an EvmStep"), + }; + + let last_instruction = trace + .bytecode() + .ok_or(InferrerError::MissingContract)? + .get_instruction(last_step.pc)?; + + Ok(match &last_instruction.location { + Some(last_instruction_location) => { + last_instruction.opcode == OpCode::REVERT + && func.location.contains(last_instruction_location) + } + _ => false, + }) +} + +fn instruction_within_function_to_custom_error_stack_trace_entry( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + inst: &Instruction, + message: String, +) -> Result { + let last_source_reference = get_last_source_reference(trace)?; + let last_source_reference = + last_source_reference.expect("Expected source reference to be defined"); + + let bytecode = match &trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + } + .ok_or(InferrerError::MissingContract)?; + + let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()); + + let source_reference = source_reference.unwrap_or(last_source_reference); + + Ok(StackTraceEntry::CustomError { + source_reference, + message, + }) +} + +fn instruction_within_function_to_panic_stack_trace_entry( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + inst: &Instruction, + error_code: U256, +) -> Result { + let last_source_reference = get_last_source_reference(trace)?; + + let bytecode = match &trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + } + .ok_or(InferrerError::MissingContract)?; + + let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()); + + let source_reference = source_reference.or(last_source_reference); + + Ok(StackTraceEntry::PanicError { + source_reference, + error_code, + }) +} + +fn instruction_within_function_to_revert_stack_trace_entry( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + inst: &Instruction, +) -> Result { + let bytecode = match &trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + } + .ok_or(InferrerError::MissingContract)?; + + let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()) + .ok_or(InferrerError::MissingSourceReference)?; + + let return_data = match &trace { + Either::Left(create) => create.return_data(), + Either::Right(call) => call.return_data(), + }; + + Ok(StackTraceEntry::RevertError { + source_reference, + is_invalid_opcode_error: inst.opcode == OpCode::INVALID, + return_data: return_data.clone(), + }) +} + +fn instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + inst: &Instruction, +) -> Result, InferrerError> { + let bytecode = match &trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + } + .ok_or(InferrerError::MissingContract)?; + + let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()); + + Ok(source_reference) +} + +fn is_called_non_contract_account_error( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result { + // We could change this to checking that the last valid location maps to a call, + // but it's way more complex as we need to get the ast node from that + // location. + + let (bytecode, steps) = match &trace { + Either::Left(create) => (create.bytecode(), create.steps()), + Either::Right(call) => (call.bytecode(), call.steps()), + }; + + let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + + let last_index = get_last_instruction_with_valid_location_step_index(trace)?; + + let last_index = match last_index { + None | Some(0) => return Ok(false), + Some(last_index) => last_index as usize, + }; + + let last_step = match &steps[last_index] { + MessageTraceStep::Evm(step) => step, + _ => panic!("We know this is an EVM step"), + }; + + let last_inst = bytecode.get_instruction(last_step.pc)?; + + if last_inst.opcode != OpCode::ISZERO { + return Ok(false); + } + + let prev_step = match &steps[last_index - 1] { + MessageTraceStep::Evm(step) => step, + _ => panic!("We know this is an EVM step"), + }; + + let prev_inst = bytecode.get_instruction(prev_step.pc)?; + + Ok(prev_inst.opcode == OpCode::EXTCODESIZE) +} + +fn is_call_failed_error( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + inst_index: u32, + call_instruction: &Instruction, +) -> Result { + let call_location = match &call_instruction.location { + Some(location) => location, + None => panic!("Expected call location to be defined"), + }; + + is_last_location(trace, inst_index, call_location) +} + +/// Returns a source reference pointing to the constructor if it exists, or +/// to the contract otherwise. +fn is_constructor_invalid_arguments_error( + trace: &CreateMessageTrace, +) -> Result { + if trace.return_data().len() > 0 { + return Ok(false); + } + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + // This function is only matters with contracts that have constructors defined. + // The ones that don't are abstract contracts, or their constructor + // doesn't take any argument. + let Some(constructor) = &contract.constructor else { + return Ok(false); + }; + + let Ok(version) = Version::parse(&bytecode.compiler_version) else { + return Ok(false); + }; + if version < FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION { + return Ok(false); + } + + let last_step = trace.steps().into_iter().last(); + let Some(MessageTraceStep::Evm(last_step)) = last_step else { + return Ok(false); + }; + + let last_inst = bytecode.get_instruction(last_step.pc)?; + + if last_inst.opcode != OpCode::REVERT || last_inst.location.is_some() { + return Ok(false); + } + + let mut has_read_deployment_code_size = false; + for step in trace.steps() { + let step = match step { + MessageTraceStep::Evm(step) => step, + _ => return Ok(false), + }; + + let inst = bytecode.get_instruction(step.pc)?; + + if let Some(inst_location) = &inst.location { + if contract.location != *inst_location && constructor.location != *inst_location { + return Ok(false); + } + } + + if inst.opcode == OpCode::CODESIZE { + has_read_deployment_code_size = true; + } + } + + Ok(has_read_deployment_code_size) +} + +fn is_constructor_not_payable_error(trace: &CreateMessageTrace) -> Result { + // This error doesn't return data + if !trace.return_data().is_empty() { + return Ok(false); + } + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + // This function is only matters with contracts that have constructors defined. + // The ones that don't are abstract contracts, or their constructor + // doesn't take any argument. + let constructor = match &contract.constructor { + Some(constructor) => constructor, + None => return Ok(false), + }; + + let value = trace.value(); + if value.is_zero() { + return Ok(false); + } + + Ok(constructor.is_payable != Some(true)) +} + +fn is_direct_library_call(trace: &CallMessageTrace) -> Result { + let contract = &trace + .bytecode() + .ok_or(InferrerError::MissingContract)? + .contract; + let contract = contract.borrow(); + + Ok(trace.depth() == 0 && contract.r#type == ContractKind::Library) +} + +fn is_contract_call_run_out_of_gas_error( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + call_step_index: u32, +) -> Result { + let (steps, return_data, exit_code) = match &trace { + Either::Left(call) => (call.steps(), call.return_data(), call.exit()), + Either::Right(create) => (create.steps(), create.return_data(), create.exit()), + }; + + if return_data.len() > 0 { + return Ok(false); + } + + if !exit_code.is_revert() { + return Ok(false); + } + + let call_exit = match steps.get(call_step_index as usize) { + None | Some(MessageTraceStep::Evm(_)) => panic!("Expected call to be a message trace"), + Some(MessageTraceStep::Precompile(precompile)) => precompile.exit(), + Some(MessageTraceStep::Call(call)) => call.exit(), + Some(MessageTraceStep::Create(create)) => create.exit(), + }; + + if !call_exit.is_out_of_gas_error() { + return Ok(false); + } + + fails_right_after_call(trace, call_step_index) +} + +fn is_fallback_not_payable_error( + trace: &CallMessageTrace, + called_function: Option<&ContractFunction>, +) -> Result { + // This error doesn't return data + if !trace.return_data().is_empty() { + return Ok(false); + } + + if trace.value().is_zero() { + return Ok(false); + } + + // the called function exists in the contract + if called_function.is_some() { + return Ok(false); + } + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + match &contract.fallback { + Some(fallback) => Ok(fallback.is_payable != Some(true)), + None => Ok(false), + } +} + +fn is_function_not_payable_error( + trace: &CallMessageTrace, + called_function: &ContractFunction, +) -> Result { + // This error doesn't return data + if !trace.return_data().is_empty() { + return Ok(false); + } + + if trace.value().is_zero() { + return Ok(false); + } + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + // Libraries don't have a nonpayable check + if contract.r#type == ContractKind::Library { + return Ok(false); + } + + Ok(called_function.is_payable != Some(true)) +} + +fn is_last_location( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + from_step: u32, + location: &SourceLocation, +) -> Result { + let (bytecode, steps) = match &trace { + Either::Left(call) => (call.bytecode(), call.steps()), + Either::Right(create) => (create.bytecode(), create.steps()), + }; + + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + for step in steps.iter().skip(from_step as usize) { + let step = match step { + MessageTraceStep::Evm(step) => step, + _ => return Ok(false), + }; + + let step_inst = bytecode.get_instruction(step.pc)?; + + if let Some(step_inst_location) = &step_inst.location { + if **step_inst_location != *location { + return Ok(false); + } + } + } + + Ok(true) +} + +fn is_missing_function_and_fallback_error( + trace: &CallMessageTrace, + called_function: Option<&ContractFunction>, +) -> Result { + // This error doesn't return data + if trace.return_data().len() > 0 { + return Ok(false); + } + + // the called function exists in the contract + if called_function.is_some() { + return Ok(false); + } + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + // there's a receive function and no calldata + if trace.calldata.len() == 0 && contract.receive.is_some() { + return Ok(false); + } + + Ok(contract.fallback.is_none()) +} + +fn is_proxy_error_propagated( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + call_subtrace_step_index: u32, +) -> Result { + let trace = match &trace { + Either::Left(call) => call, + Either::Right(_) => return Ok(false), + }; + + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + + let steps = trace.steps(); + let call_step = match steps.get(call_subtrace_step_index as usize - 1) { + Some(MessageTraceStep::Evm(step)) => step, + _ => return Ok(false), + }; + + let call_inst = bytecode.get_instruction(call_step.pc)?; + + if call_inst.opcode != OpCode::DELEGATECALL { + return Ok(false); + } + + let steps = trace.steps(); + let subtrace = match steps.get(call_subtrace_step_index as usize) { + None | Some(MessageTraceStep::Evm(_) | MessageTraceStep::Precompile(_)) => { + return Ok(false) + } + Some(MessageTraceStep::Call(call)) => Either::Left(call), + Some(MessageTraceStep::Create(create)) => Either::Right(create), + }; + + let (subtrace_bytecode, subtrace_return_data) = match &subtrace { + Either::Left(call) => (call.bytecode(), call.return_data()), + Either::Right(create) => (create.bytecode(), create.return_data()), + }; + let subtrace_bytecode = match subtrace_bytecode { + Some(bytecode) => bytecode, + // If we can't recognize the implementation we'd better don't consider it as such + None => return Ok(false), + }; + + if subtrace_bytecode.contract.borrow().r#type == ContractKind::Library { + return Ok(false); + } + + if trace.return_data().as_ref() != subtrace_return_data.as_ref() { + return Ok(false); + } + + for step in trace + .steps() + .iter() + .skip(call_subtrace_step_index as usize + 1) + { + let step = match step { + MessageTraceStep::Evm(step) => step, + _ => return Ok(false), + }; + + let inst = bytecode.get_instruction(step.pc)?; + + // All the remaining locations should be valid, as they are part of the inline + // asm + if inst.location.is_none() { + return Ok(false); + } + + if matches!( + inst.jump_type, + JumpType::IntoFunction | JumpType::OutofFunction + ) { + return Ok(false); + } + } + + let steps = trace.steps(); + let last_step = match steps.last() { + Some(MessageTraceStep::Evm(step)) => step, + _ => panic!("Expected last step to be an EvmStep"), + }; + let last_inst = bytecode.get_instruction(last_step.pc)?; + + Ok(last_inst.opcode == OpCode::REVERT) +} + +fn is_subtrace_error_propagated( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + call_subtrace_step_index: u32, +) -> Result { + let (return_data, exit, steps) = match &trace { + Either::Left(call) => (call.return_data(), call.exit(), call.steps()), + Either::Right(create) => (create.return_data(), create.exit(), create.steps()), + }; + + let (call_return_data, call_exit) = match steps.get(call_subtrace_step_index as usize) { + None | Some(MessageTraceStep::Evm(_)) => panic!("Expected call to be a message trace"), + Some(MessageTraceStep::Precompile(precompile)) => { + (precompile.return_data(), precompile.exit()) + } + Some(MessageTraceStep::Call(call)) => (call.return_data(), call.exit()), + Some(MessageTraceStep::Create(create)) => (create.return_data(), create.exit()), + }; + + if return_data.as_ref() != call_return_data.as_ref() { + return Ok(false); + } + + if exit.is_out_of_gas_error() && call_exit.is_out_of_gas_error() { + return Ok(true); + } + + // If the return data is not empty, and it's still the same, we assume it + // is being propagated + if return_data.len() > 0 { + return Ok(true); + } + + fails_right_after_call(trace, call_subtrace_step_index) +} + +fn other_execution_error_stacktrace( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, + mut stacktrace: Vec, +) -> Result, InferrerError> { + let other_execution_error_frame = StackTraceEntry::OtherExecutionError { + source_reference: get_last_source_reference(trace)?, + }; + + stacktrace.push(other_execution_error_frame.into()); + Ok(stacktrace) +} + +fn solidity_0_6_3_maybe_unmapped_revert( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result { + let (bytecode, steps) = match &trace { + Either::Left(create) => (create.bytecode(), create.steps()), + Either::Right(call) => (call.bytecode(), call.steps()), + }; + + let bytecode = bytecode + .as_ref() + .expect("JS code only accepted variants that had bytecode defined"); + + if steps.is_empty() { + return Ok(false); + } + + let last_step = steps.last(); + let last_step = match last_step { + Some(MessageTraceStep::Evm(step)) => step, + _ => return Ok(false), + }; + + let last_instruction = bytecode.get_instruction(last_step.pc)?; + + let Ok(version) = Version::parse(&bytecode.compiler_version) else { + return Ok(false); + }; + let req = VersionReq::parse(&format!("^{FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS}")) + .expect("valid semver"); + + Ok(req.matches(&version) && last_instruction.opcode == OpCode::REVERT) +} + +// Solidity 0.6.3 unmapped reverts special handling +// For more info: https://github.com/ethereum/solidity/issues/9006 +fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( + trace: &CallMessageTrace, +) -> Result, InferrerError> { + let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract = bytecode.contract.borrow(); + + let revert_frame = + solidity_0_6_3_get_frame_for_unmapped_revert_within_function(Either::Left(trace))?; + + let revert_frame = match revert_frame { + None + | Some(StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference: None, + .. + }) => { + if contract.receive.is_none() || trace.calldata.len() > 0 { + // Failed within the fallback + if let Some(fallback) = &contract.fallback { + let location = &fallback.location; + let file = location.file(); + let file = file.borrow(); + + let source_reference = SourceReference { + contract: Some(contract.name.clone()), + function: Some(FALLBACK_FUNCTION_NAME.to_string()), + source_name: file.source_name.clone(), + source_content: file.content.clone(), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }; + let revert_frame = StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference: Some(solidity_0_6_3_correct_line_number( + source_reference, + )), + }; + + Some(revert_frame) + } else { + None + } + } else { + let receive = contract + .receive + .as_ref() + .expect("None always hits branch above"); + + let location = &receive.location; + let file = location.file(); + let file = file.borrow(); + + let source_reference = SourceReference { + contract: Some(contract.name.clone()), + function: Some(RECEIVE_FUNCTION_NAME.to_string()), + source_name: file.source_name.clone(), + source_content: file.content.clone(), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }; + let revert_frame = StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference: Some(solidity_0_6_3_correct_line_number(source_reference)), + }; + + Some(revert_frame) + } + } + Some(revert_frame) => Some(revert_frame), + }; + + Ok(revert_frame) +} + +fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( + trace: Either<&CallMessageTrace, &CreateMessageTrace>, +) -> Result, InferrerError> { + let (bytecode, steps) = match &trace { + Either::Left(create) => (create.bytecode(), create.steps()), + Either::Right(call) => (call.bytecode(), call.steps()), + }; + + let bytecode = bytecode + .as_ref() + .expect("JS code only accepted variants that had bytecode defined"); + + let contract = bytecode.contract.borrow(); + + // If we are within a function there's a last valid location. It may + // be the entire contract. + let prev_inst = get_last_instruction_with_valid_location(trace)?; + let last_step = match steps.last() { + Some(MessageTraceStep::Evm(step)) => step, + _ => panic!("JS code asserts this is always an EvmStep"), + }; + let next_inst_pc = last_step.pc + 1; + let has_next_inst = bytecode.has_instruction(next_inst_pc); + + if has_next_inst { + let next_inst = bytecode.get_instruction(next_inst_pc)?; + + let prev_loc = prev_inst.and_then(|i| i.location.as_deref()); + let next_loc = next_inst.location.as_deref(); + + let prev_func = prev_loc.and_then(SourceLocation::get_containing_function); + let next_func = next_loc.and_then(SourceLocation::get_containing_function); + + // This is probably a require. This means that we have the exact + // line, but the stack trace may be degraded (e.g. missing our + // synthetic call frames when failing in a modifier) so we still + // add this frame as UNMAPPED_SOLC_0_6_3_REVERT_ERROR + match (&prev_func, &next_loc, &prev_loc) { + (Some(_), Some(next_loc), Some(prev_loc)) if prev_loc == next_loc => { + let source_reference = instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( + trace, + next_inst, + )?; + return Ok(Some(StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference, + })); + } + _ => {} + } + + let source_reference = if prev_func.is_some() && prev_inst.is_some() { + instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( + trace, + prev_inst.as_ref().unwrap(), + )? + } else if next_func.is_some() { + instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( + trace, next_inst, + )? + } else { + None + }; + + return Ok(Some(StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference: source_reference.map(solidity_0_6_3_correct_line_number), + })); + } + + if matches!(trace, Either::Right(CreateMessageTrace { .. })) && prev_inst.is_some() { + // Solidity is smart enough to stop emitting extra instructions after + // an unconditional revert happens in a constructor. If this is the case + // we just return a special error. + + let source_reference = + instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( + trace, + prev_inst.as_ref().unwrap(), + )? + .map(solidity_0_6_3_correct_line_number) + .unwrap_or_else(|| { + // When the latest instruction is not within a function we need + // some default sourceReference to show to the user + let location = &contract.location; + let file = location.file(); + let file = file.borrow(); + + let mut default_source_reference = SourceReference { + function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), + contract: Some(contract.name.clone()), + source_name: file.source_name.clone(), + source_content: file.content.clone(), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }; + + if let Some(constructor) = &contract.constructor { + default_source_reference.line = constructor.location.get_starting_line_number(); + } + + default_source_reference + }); + + return Ok(Some(StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference: Some(source_reference), + })); + } + + if let Some(prev_inst) = prev_inst { + // We may as well just be in a function or modifier and just happen + // to be at the last instruction of the runtime bytecode. + // In this case we just return whatever the last mapped intruction + // points to. + let source_reference = + instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( + trace, prev_inst, + )? + .map(solidity_0_6_3_correct_line_number); + + return Ok(Some(StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference, + })); + } + + Ok(None) +} + +fn solidity_0_6_3_correct_line_number(mut source_reference: SourceReference) -> SourceReference { + let lines: Vec<_> = source_reference.source_content.split('\n').collect(); + + let current_line = lines[source_reference.line as usize - 1]; + if current_line.contains("require") || current_line.contains("revert") { + return source_reference; + } + + let next_lines = &lines + .get(source_reference.line as usize..) + .unwrap_or_default(); + let first_non_empty_line = next_lines.iter().position(|l| !l.trim().is_empty()); + + let Some(first_non_empty_line) = first_non_empty_line else { + return source_reference; + }; + + let next_line = next_lines[first_non_empty_line]; + if next_line.contains("require") || next_line.contains("revert") { + source_reference.line += 1 + first_non_empty_line as u32; + } + + source_reference +} + +fn source_location_to_source_reference( + bytecode: &Bytecode, + location: Option<&SourceLocation>, +) -> Option { + let Some(location) = location else { + return None; + }; + let Some(func) = location.get_containing_function() else { + return None; + }; + + let func_name = match func.r#type { + ContractFunctionType::Constructor => CONSTRUCTOR_FUNCTION_NAME.to_string(), + ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(), + ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(), + _ => func.name.clone(), + }; + + let func_location_file = func.location.file(); + let func_location_file = func_location_file.borrow(); + + Some(SourceReference { + function: Some(func_name.clone()), + contract: if func.r#type == ContractFunctionType::FreeFunction { + None + } else { + Some(bytecode.contract.borrow().name.clone()) + }, + source_name: func_location_file.source_name.clone(), + source_content: func_location_file.content.clone(), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sol_value_to_string() { + assert_eq!( + format_dyn_sol_value(&DynSolValue::String("hello".to_string())), + "\"hello\"" + ); + // Uniform, 0-prefixed hex strings + assert_eq!( + format_dyn_sol_value(&DynSolValue::Address([0u8; 20].into())), + format!(r#""0x{}""#, "0".repeat(2 * 20)) + ); + assert_eq!( + format_dyn_sol_value(&DynSolValue::Bytes(vec![0u8; 32])), + format!(r#""0x{}""#, "0".repeat(2 * 32)) + ); + assert_eq!( + format_dyn_sol_value(&DynSolValue::FixedBytes([0u8; 32].into(), 10)), + format!(r#""0x{}""#, "0".repeat(2 * 10)) + ); + assert_eq!( + format_dyn_sol_value(&DynSolValue::FixedBytes([0u8; 32].into(), 32)), + format!(r#""0x{}""#, "0".repeat(2 * 32)) + ); + } +} diff --git a/crates/edr_solidity/src/lib.rs b/crates/edr_solidity/src/lib.rs index e86a076ed..9988cb426 100644 --- a/crates/edr_solidity/src/lib.rs +++ b/crates/edr_solidity/src/lib.rs @@ -14,4 +14,10 @@ pub mod message_trace; pub mod vm_tracer; pub mod compiler; +pub mod error_inferrer; +mod mapped_inline_internal_functions_heuristics; +pub mod return_data; +pub mod solidity_stack_trace; +pub mod solidity_tracer; pub mod source_map; +pub mod vm_trace_decoder; diff --git a/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs b/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs new file mode 100644 index 000000000..8576f511a --- /dev/null +++ b/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs @@ -0,0 +1,180 @@ +//! This file includes Solidity tracing heuristics for solc starting with +//! version 0.6.9. +//! +//! This solc version introduced a significant change to how sourcemaps are +//! handled for inline yul/internal functions. These were mapped to the +//! unmapped/-1 file before, which lead to many unmapped reverts. Now, they are +//! mapped to the part of the Solidity source that lead to their inlining. +//! +//! This change is a very positive change, as errors would point to the correct +//! line by default. The only problem is that we used to rely very heavily on +//! unmapped reverts to decide when our error detection heuristics were to be +//! run. In fact, these heuristics were first introduced because of unmapped +//! reverts. +//! +//! Instead of synthetically completing stack traces when unmapped reverts +//! occur, we now start from complete stack traces and adjust them if we can +//! provide more meaningful errors. + +use edr_evm::interpreter::OpCode; +use either::Either; +use semver::Version; + +use crate::{ + build_model::BytecodeError, + message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep, MessageTraceStep}, + solidity_stack_trace::StackTraceEntry, +}; + +const FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS: Version = Version::new(0, 6, 9); + +#[derive(Debug, thiserror::Error)] +pub enum HeuristicsError { + #[error(transparent)] + BytecodeError(#[from] BytecodeError), + #[error("Missing contract")] + MissingContract, +} + +pub fn stack_trace_may_require_adjustments( + stacktrace: &Vec, + decoded_trace: &Either, +) -> Result { + let bytecode = match &decoded_trace { + Either::Left(create) => create.bytecode(), + Either::Right(call) => call.bytecode(), + }; + let bytecode = bytecode.ok_or(HeuristicsError::MissingContract)?; + + let Some(last_frame) = stacktrace.last() else { + return Ok(false); + }; + + if let StackTraceEntry::RevertError { + is_invalid_opcode_error, + return_data, + .. + } = last_frame + { + let result = !is_invalid_opcode_error + && return_data.is_empty() + && Version::parse(&bytecode.compiler_version) + .map(|version| version >= FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS) + .unwrap_or(false); + return Ok(result); + } + + Ok(false) +} + +pub fn adjust_stack_trace( + mut stacktrace: Vec, + decoded_trace: &Either, +) -> Result, HeuristicsError> { + let Some(StackTraceEntry::RevertError { + source_reference, .. + }) = stacktrace.last() + else { + unreachable!("This should be only used immediately after we check with `stack_trace_may_require_adjustments` that the last frame is a revert frame"); + }; + + // Replace the last revert frame with an adjusted frame if needed + if is_non_contract_account_called_error(decoded_trace)? { + let last_revert_frame_source_reference = source_reference.clone(); + stacktrace.pop(); + stacktrace.push(StackTraceEntry::NoncontractAccountCalledError { + source_reference: last_revert_frame_source_reference, + }); + return Ok(stacktrace); + } + + if is_constructor_invalid_params_error(decoded_trace)? { + let last_revert_frame_source_reference = source_reference.clone(); + stacktrace.pop(); + stacktrace.push(StackTraceEntry::InvalidParamsError { + source_reference: last_revert_frame_source_reference, + }); + return Ok(stacktrace); + } + + if is_call_invalid_params_error(decoded_trace)? { + let last_revert_frame_source_reference = source_reference.clone(); + stacktrace.pop(); + stacktrace.push(StackTraceEntry::InvalidParamsError { + source_reference: last_revert_frame_source_reference, + }); + + return Ok(stacktrace); + } + + Ok(stacktrace) +} + +fn is_non_contract_account_called_error( + decoded_trace: &Either, +) -> Result { + match_opcodes( + decoded_trace, + -9, + &[ + OpCode::EXTCODESIZE, + OpCode::ISZERO, + OpCode::DUP1, + OpCode::ISZERO, + ], + ) +} + +fn is_constructor_invalid_params_error( + decoded_trace: &Either, +) -> Result { + Ok(match_opcodes(decoded_trace, -20, &[OpCode::CODESIZE])? + && match_opcodes(decoded_trace, -15, &[OpCode::CODECOPY])? + && match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?) +} + +fn is_call_invalid_params_error( + decoded_trace: &Either, +) -> Result { + Ok(match_opcodes(decoded_trace, -11, &[OpCode::CALLDATASIZE])? + && match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?) +} + +fn match_opcodes( + decoded_trace: &Either, + first_step_index: i32, + opcodes: &[OpCode], +) -> Result { + let (bytecode, steps) = match &decoded_trace { + Either::Left(call) => (call.bytecode(), call.steps()), + Either::Right(create) => (create.bytecode(), create.steps()), + }; + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + // If the index is negative, we start from the end of the trace, + // just like in the original JS code + let mut index = match first_step_index { + 0.. => first_step_index as usize, + ..=-1 if first_step_index.abs() < steps.len() as i32 => { + (steps.len() as i32 + first_step_index) as usize + } + // Out of bounds + _ => return Ok(false), + }; + + for opcode in opcodes { + let Some(MessageTraceStep::Evm(EvmStep { pc })) = steps.get(index) else { + return Ok(false); + }; + + let instruction = bytecode.get_instruction(*pc)?; + + if instruction.opcode != *opcode { + return Ok(false); + } + + index += 1; + } + + Ok(true) +} diff --git a/crates/edr_solidity/src/message_trace.rs b/crates/edr_solidity/src/message_trace.rs index d009538d2..f3804ef24 100644 --- a/crates/edr_solidity/src/message_trace.rs +++ b/crates/edr_solidity/src/message_trace.rs @@ -18,6 +18,33 @@ pub enum ExitCode { Halt(HaltReason), } +impl ExitCode { + pub fn is_error(&self) -> bool { + !matches!(self, Self::Success) + } + + pub fn is_contract_too_large_error(&self) -> bool { + matches!(self, Self::Halt(HaltReason::CreateContractSizeLimit)) + } + + pub fn is_invalid_opcode_error(&self) -> bool { + matches!( + self, + Self::Halt( + HaltReason::InvalidFEOpcode | HaltReason::OpcodeNotFound | HaltReason::NotActivated + ) + ) + } + + pub fn is_out_of_gas_error(&self) -> bool { + matches!(self, Self::Halt(HaltReason::OutOfGas(_))) + } + + pub fn is_revert(&self) -> bool { + matches!(self, Self::Revert) + } +} + /// Represents a message trace. Naive Rust port of the `MessageTrace` from /// Hardhat. #[derive(Clone, Debug)] @@ -39,6 +66,26 @@ impl MessageTrace { MessageTrace::Precompile(precompile) => &mut precompile.base, } } + + pub fn exit(&self) -> &ExitCode { + match self { + MessageTrace::Create(create) => &create.base.base.exit, + MessageTrace::Call(call) => &call.base.base.exit, + MessageTrace::Precompile(precompile) => &precompile.base.exit, + } + } +} + +/// Represents a message trace. Naive Rust port of the `MessageTrace` from +/// Hardhat. +#[derive(Clone, Debug)] +pub enum MessageTraceRef<'a> { + /// Represents a create message trace. + Create(&'a CreateMessageTrace), + /// Represents a call message trace. + Call(&'a CallMessageTrace), + /// Represents a precompile message trace. + Precompile(PrecompileMessageTrace), } /// Represents the common fields of a message trace. @@ -67,6 +114,16 @@ pub struct PrecompileMessageTrace { pub calldata: Bytes, } +impl PrecompileMessageTrace { + pub fn exit(&self) -> &ExitCode { + &self.base.exit + } + + pub fn return_data(&self) -> &Bytes { + &self.base.return_data + } +} + /// Represents a base EVM message trace. #[derive(Clone, Debug)] pub struct BaseEvmMessageTrace { @@ -75,7 +132,7 @@ pub struct BaseEvmMessageTrace { /// Code of the contract that is being executed. pub code: Bytes, /// Children message traces. - pub steps: Vec, + pub steps: Vec, /// Resolved metadata of the contract that is being executed. /// Filled in the JS side by `ContractsIdentifier`. pub bytecode: Option>, @@ -95,6 +152,60 @@ pub struct CreateMessageTrace { pub deployed_contract: Option, } +impl CreateMessageTrace { + /// Returns a reference to the metadata of the contract that is being + /// executed. + pub fn bytecode(&self) -> Option<&Rc> { + self.base.bytecode.as_ref() + } + + pub fn set_bytecode(&mut self, bytecode: Option>) { + self.base.bytecode = bytecode + } + + pub fn code(&self) -> &Bytes { + &self.base.code + } + + pub fn depth(&self) -> usize { + self.base.base.depth + } + + pub fn exit(&self) -> &ExitCode { + &self.base.base.exit + } + + pub fn number_of_subtraces(&self) -> u32 { + self.base.number_of_subtraces + } + + pub fn return_data(&self) -> &Bytes { + &self.base.base.return_data + } + + // TODO avoid clone + pub fn steps(&self) -> Vec { + self.base + .steps + .iter() + .cloned() + .map(MessageTraceStep::from) + .collect() + } + + // TODO avoid conversion + pub fn set_steps(&mut self, steps: impl IntoIterator) { + self.base.steps = steps + .into_iter() + .map(VmTracerMessageTraceStep::from) + .collect(); + } + + pub fn value(&self) -> &U256 { + &self.base.base.value + } +} + /// Represents a call message trace. #[derive(Clone, Debug)] pub struct CallMessageTrace { @@ -108,10 +219,64 @@ pub struct CallMessageTrace { pub code_address: Address, } +impl CallMessageTrace { + /// Returns a reference to the metadata of the contract that is being + /// executed. + pub fn bytecode(&self) -> Option<&Rc> { + self.base.bytecode.as_ref() + } + + pub fn set_bytecode(&mut self, bytecode: Option>) { + self.base.bytecode = bytecode + } + + pub fn code(&self) -> &Bytes { + &self.base.code + } + + pub fn depth(&self) -> usize { + self.base.base.depth + } + + pub fn exit(&self) -> &ExitCode { + &self.base.base.exit + } + + pub fn number_of_subtraces(&self) -> u32 { + self.base.number_of_subtraces + } + + pub fn return_data(&self) -> &Bytes { + &self.base.base.return_data + } + + // TODO avoid clone + pub fn steps(&self) -> Vec { + self.base + .steps + .iter() + .cloned() + .map(MessageTraceStep::from) + .collect() + } + + // TODO avoid conversion + pub fn set_steps(&mut self, steps: impl IntoIterator) { + self.base.steps = steps + .into_iter() + .map(VmTracerMessageTraceStep::from) + .collect(); + } + + pub fn value(&self) -> &U256 { + &self.base.base.value + } +} + /// Represents a message trace step. Naive Rust port of the `MessageTraceStep` /// from Hardhat. #[derive(Clone, Debug)] -pub enum MessageTraceStep { +pub enum VmTracerMessageTraceStep { /// [`MessageTrace`] variant. // It's both read and written to (updated) by the `VmTracer`. Message(Rc>), @@ -119,9 +284,54 @@ pub enum MessageTraceStep { Evm(EvmStep), } +pub enum MessageTraceStep { + /// Represents a create message trace. + Create(CreateMessageTrace), + /// Represents a call message trace. + Call(CallMessageTrace), + /// Represents a precompile message trace. + Precompile(PrecompileMessageTrace), + /// Minimal EVM step that contains only PC (program counter). + Evm(EvmStep), +} + +impl From for MessageTraceStep { + fn from(step: VmTracerMessageTraceStep) -> Self { + match step { + // TODO avoid clone + VmTracerMessageTraceStep::Message(trace) => match trace.as_ref().borrow().clone() { + MessageTrace::Create(create_trace) => MessageTraceStep::Create(create_trace), + MessageTrace::Call(call_trace) => MessageTraceStep::Call(call_trace), + MessageTrace::Precompile(precompile_trace) => { + MessageTraceStep::Precompile(precompile_trace) + } + }, + VmTracerMessageTraceStep::Evm(evm_step) => MessageTraceStep::Evm(evm_step), + } + } +} + +impl From for VmTracerMessageTraceStep { + fn from(step: MessageTraceStep) -> Self { + match step { + MessageTraceStep::Evm(evm_step) => VmTracerMessageTraceStep::Evm(evm_step), + // message => VmTracerMessageTraceStep::Message(Rc::new(RefCell::new(message))), + MessageTraceStep::Create(create) => VmTracerMessageTraceStep::Message(Rc::new( + RefCell::new(MessageTrace::Create(create)), + )), + MessageTraceStep::Call(call) => { + VmTracerMessageTraceStep::Message(Rc::new(RefCell::new(MessageTrace::Call(call)))) + } + MessageTraceStep::Precompile(precompile) => VmTracerMessageTraceStep::Message(Rc::new( + RefCell::new(MessageTrace::Precompile(precompile)), + )), + } + } +} + /// Minimal EVM step that contains only PC (program counter). #[derive(Clone, Debug)] pub struct EvmStep { /// Program counter - pub pc: u64, + pub pc: u32, } diff --git a/crates/edr_solidity/src/return_data.rs b/crates/edr_solidity/src/return_data.rs new file mode 100644 index 000000000..3161279f8 --- /dev/null +++ b/crates/edr_solidity/src/return_data.rs @@ -0,0 +1,60 @@ +//! Rewrite of `hardhat-network/provider/return-data.ts` from Hardhat. + +use alloy_sol_types::SolError; +use edr_eth::{Bytes, U256}; + +// Built-in error types +// See +alloy_sol_types::sol! { + error Error(string); + error Panic(uint256); +} + +pub struct ReturnData { + pub value: Bytes, + selector: Option<[u8; 4]>, +} + +impl ReturnData { + pub fn new(value: Bytes) -> Self { + let selector = if value.len() >= 4 { + Some(value[0..4].try_into().expect("checked length")) + } else { + None + }; + + Self { value, selector } + } + + pub fn is_empty(&self) -> bool { + self.value.is_empty() + } + + pub fn matches_selector(&self, selector: impl AsRef<[u8]>) -> bool { + self.selector + .map_or(false, |value| value == selector.as_ref()) + } + + pub fn is_error_return_data(&self) -> bool { + self.selector == Some(Error::SELECTOR) + } + + pub fn is_panic_return_data(&self) -> bool { + self.selector == Some(Panic::SELECTOR) + } + + pub fn decode_error(&self) -> Result { + if self.is_empty() { + return Ok(String::new()); + } + + let result = Error::abi_decode(&self.value[..], false)?; + + Ok(result._0) + } + + /// Decodes the panic error code from the return data. + pub fn decode_panic(&self) -> Result { + Panic::abi_decode(&self.value[..], false).map(|p| p._0) + } +} diff --git a/crates/edr_solidity/src/solidity_stack_trace.rs b/crates/edr_solidity/src/solidity_stack_trace.rs new file mode 100644 index 000000000..3dde638a7 --- /dev/null +++ b/crates/edr_solidity/src/solidity_stack_trace.rs @@ -0,0 +1,179 @@ +use edr_eth::{Address, Bytes, U256}; + +use crate::build_model::ContractFunctionType; + +pub const FALLBACK_FUNCTION_NAME: &str = ""; +pub const RECEIVE_FUNCTION_NAME: &str = ""; +pub const CONSTRUCTOR_FUNCTION_NAME: &str = "constructor"; +pub const UNRECOGNIZED_FUNCTION_NAME: &str = ""; +pub const UNKNOWN_FUNCTION_NAME: &str = ""; +pub const PRECOMPILE_FUNCTION_NAME: &str = ""; +pub const UNRECOGNIZED_CONTRACT_NAME: &str = ""; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SourceReference { + pub source_name: String, + pub source_content: String, + pub contract: Option, + pub function: Option, + pub line: u32, + pub range: (u32, u32), +} + +#[derive(Debug, Clone)] +pub enum StackTraceEntry { + CallstackEntry { + source_reference: SourceReference, + function_type: ContractFunctionType, + }, + UnrecognizedCreateCallstackEntry, + UnrecognizedContractCallstackEntry { + address: Address, + }, + PrecompileError { + precompile: u32, + }, + RevertError { + return_data: Bytes, + source_reference: SourceReference, + is_invalid_opcode_error: bool, + }, + PanicError { + error_code: U256, + source_reference: Option, + }, + CustomError { + message: String, + source_reference: SourceReference, + }, + FunctionNotPayableError { + value: U256, + source_reference: SourceReference, + }, + InvalidParamsError { + source_reference: SourceReference, + }, + FallbackNotPayableError { + value: U256, + source_reference: SourceReference, + }, + FallbackNotPayableAndNoReceiveError { + value: U256, + source_reference: SourceReference, + }, + // TODO: Should trying to call a private/internal be a special case of this? + UnrecognizedFunctionWithoutFallbackError { + source_reference: SourceReference, + }, + MissingFallbackOrReceiveError { + source_reference: SourceReference, + }, + ReturndataSizeError { + source_reference: SourceReference, + }, + NoncontractAccountCalledError { + source_reference: SourceReference, + }, + CallFailedError { + source_reference: SourceReference, + }, + DirectLibraryCallError { + source_reference: SourceReference, + }, + UnrecognizedCreateError { + return_data: Bytes, + is_invalid_opcode_error: bool, + }, + UnrecognizedContractError { + address: Address, + return_data: Bytes, + is_invalid_opcode_error: bool, + }, + OtherExecutionError { + source_reference: Option, + }, + // This is a special case to handle a regression introduced in solc 0.6.3 + // For more info: https://github.com/ethereum/solidity/issues/9006 + UnmappedSolc0_6_3RevertError { + source_reference: Option, + }, + ContractTooLargeError { + source_reference: Option, + }, + InternalFunctionCallstackEntry { + pc: u32, + source_reference: SourceReference, + }, + ContractCallRunOutOfGasError { + source_reference: Option, + }, +} + +impl StackTraceEntry { + pub fn source_reference(&self) -> Option<&SourceReference> { + match self { + StackTraceEntry::CallstackEntry { + source_reference, .. + } + | StackTraceEntry::RevertError { + source_reference, .. + } + | StackTraceEntry::CustomError { + source_reference, .. + } + | StackTraceEntry::FunctionNotPayableError { + source_reference, .. + } + | StackTraceEntry::InvalidParamsError { + source_reference, .. + } + | StackTraceEntry::FallbackNotPayableError { + source_reference, .. + } + | StackTraceEntry::MissingFallbackOrReceiveError { + source_reference, .. + } + | StackTraceEntry::ReturndataSizeError { + source_reference, .. + } + | StackTraceEntry::NoncontractAccountCalledError { + source_reference, .. + } + | StackTraceEntry::CallFailedError { + source_reference, .. + } + | StackTraceEntry::DirectLibraryCallError { + source_reference, .. + } + | StackTraceEntry::UnrecognizedFunctionWithoutFallbackError { + source_reference, .. + } + | StackTraceEntry::InternalFunctionCallstackEntry { + source_reference, .. + } + | StackTraceEntry::FallbackNotPayableAndNoReceiveError { + source_reference, .. + } => Some(source_reference), + StackTraceEntry::PanicError { + source_reference, .. + } + | StackTraceEntry::OtherExecutionError { + source_reference, .. + } + | StackTraceEntry::UnmappedSolc0_6_3RevertError { + source_reference, .. + } + | StackTraceEntry::ContractTooLargeError { + source_reference, .. + } + | StackTraceEntry::ContractCallRunOutOfGasError { + source_reference, .. + } => source_reference.as_ref(), + StackTraceEntry::PrecompileError { .. } + | StackTraceEntry::UnrecognizedCreateError { .. } + | StackTraceEntry::UnrecognizedCreateCallstackEntry + | StackTraceEntry::UnrecognizedContractCallstackEntry { .. } + | StackTraceEntry::UnrecognizedContractError { .. } => None, + } + } +} diff --git a/crates/edr_solidity/src/solidity_tracer.rs b/crates/edr_solidity/src/solidity_tracer.rs new file mode 100644 index 000000000..f78be0481 --- /dev/null +++ b/crates/edr_solidity/src/solidity_tracer.rs @@ -0,0 +1,274 @@ +use edr_evm::interpreter::OpCode; +use either::Either; + +use crate::{ + build_model::{BytecodeError, Instruction, JumpType}, + error_inferrer, + error_inferrer::{instruction_to_callstack_stack_trace_entry, InferrerError, SubmessageData}, + mapped_inline_internal_functions_heuristics::{ + adjust_stack_trace, stack_trace_may_require_adjustments, HeuristicsError, + }, + message_trace::{ + CallMessageTrace, CreateMessageTrace, EvmStep, MessageTrace, MessageTraceStep, + PrecompileMessageTrace, + }, + solidity_stack_trace::StackTraceEntry, +}; + +pub struct SolidityTracer; + +#[derive(Debug, thiserror::Error)] +pub enum SolidityTracerError { + #[error(transparent)] + BytecodeError(#[from] BytecodeError), + #[error(transparent)] + ErrorInferrer(#[from] InferrerError), + #[error(transparent)] + Heuristics(#[from] HeuristicsError), +} + +pub fn get_stack_trace(trace: MessageTrace) -> Result, SolidityTracerError> { + if !trace.exit().is_error() { + return Ok(vec![]); + } + + match trace { + MessageTrace::Precompile(precompile) => { + Ok(get_precompile_message_stack_trace(&precompile)?) + } + MessageTrace::Call(call) if call.bytecode().is_some() => { + Ok(get_call_message_stack_trace(call)?) + } + MessageTrace::Create(create) if create.bytecode().is_some() => { + Ok(get_create_message_stack_trace(create)?) + } + // No bytecode is present + MessageTrace::Call(call) => Ok(get_unrecognized_message_stack_trace(Either::Left(call))?), + MessageTrace::Create(create) => { + Ok(get_unrecognized_message_stack_trace(Either::Right(create))?) + } + } +} + +fn get_last_subtrace<'a>( + trace: &'a Either, +) -> Option { + let (number_of_subtraces, steps) = match trace { + Either::Left(create) => (create.number_of_subtraces(), create.steps()), + Either::Right(call) => (call.number_of_subtraces(), call.steps()), + }; + + if number_of_subtraces == 0 { + return None; + } + + steps.into_iter().rev().find_map(|step| match step { + MessageTraceStep::Evm(EvmStep { .. }) => None, + MessageTraceStep::Precompile(precompile) => Some(MessageTrace::Precompile(precompile)), + MessageTraceStep::Call(call) => Some(MessageTrace::Call(call)), + MessageTraceStep::Create(create) => Some(MessageTrace::Create(create)), + }) +} + +fn get_precompile_message_stack_trace( + trace: &PrecompileMessageTrace, +) -> Result, SolidityTracerError> { + Ok(vec![StackTraceEntry::PrecompileError { + precompile: trace.precompile, + }]) +} + +fn get_create_message_stack_trace( + trace: CreateMessageTrace, +) -> Result, SolidityTracerError> { + let inferred_error = error_inferrer::infer_before_tracing_create_message(&trace)?; + + if let Some(inferred_error) = inferred_error { + return Ok(inferred_error); + } + + trace_evm_execution(Either::Right(trace)) +} + +fn get_call_message_stack_trace( + trace: CallMessageTrace, +) -> Result, SolidityTracerError> { + let inferred_error = error_inferrer::infer_before_tracing_call_message(&trace)?; + + if let Some(inferred_error) = inferred_error { + return Ok(inferred_error); + } + + trace_evm_execution(Either::Left(trace)) +} + +fn get_unrecognized_message_stack_trace( + trace: Either, +) -> Result, SolidityTracerError> { + let (trace_exit_kind, trace_return_data) = match &trace { + Either::Left(call) => (call.exit(), call.return_data()), + Either::Right(create) => (create.exit(), create.return_data()), + }; + + let subtrace = get_last_subtrace(&trace); + + if let Some(subtrace) = subtrace { + let (is_error, return_data) = match &subtrace { + MessageTrace::Precompile(precompile) => ( + precompile.exit().is_error(), + precompile.return_data().clone(), + ), + MessageTrace::Call(call) => (call.exit().is_error(), call.return_data().clone()), + MessageTrace::Create(create) => { + (create.exit().is_error(), create.return_data().clone()) + } + }; + + // This is not a very exact heuristic, but most of the time it will be right, as + // solidity reverts if a call fails, and most contracts are in + // solidity + if is_error && trace_return_data.as_ref() == return_data.as_ref() { + let unrecognized_entry: StackTraceEntry = match trace { + Either::Left(CallMessageTrace { address, .. }) => { + StackTraceEntry::UnrecognizedContractCallstackEntry { + address: address.clone(), + } + } + Either::Right(CreateMessageTrace { .. }) => { + StackTraceEntry::UnrecognizedCreateCallstackEntry + } + }; + + let mut stacktrace = vec![unrecognized_entry]; + stacktrace.extend(get_stack_trace(subtrace)?); + + return Ok(stacktrace); + } + } + + if trace_exit_kind.is_contract_too_large_error() { + return Ok(vec![StackTraceEntry::ContractTooLargeError { + source_reference: None, + }]); + } + + let is_invalid_opcode_error = trace_exit_kind.is_invalid_opcode_error(); + + match trace { + Either::Left(trace @ CallMessageTrace { .. }) => { + Ok(vec![StackTraceEntry::UnrecognizedContractError { + address: trace.address.clone(), + return_data: trace.return_data().clone(), + is_invalid_opcode_error, + } + .into()]) + } + Either::Right(trace @ CreateMessageTrace { .. }) => { + Ok(vec![StackTraceEntry::UnrecognizedCreateError { + return_data: trace.return_data().clone(), + is_invalid_opcode_error, + } + .into()]) + } + } +} + +fn trace_evm_execution( + trace: Either, +) -> Result, SolidityTracerError> { + let stack_trace = raw_trace_evm_execution(&trace)?; + + if stack_trace_may_require_adjustments(&stack_trace, &trace)? { + return adjust_stack_trace(stack_trace, &trace).map_err(SolidityTracerError::from); + } + + Ok(stack_trace) +} + +fn raw_trace_evm_execution( + trace: &Either, +) -> Result, SolidityTracerError> { + let (bytecode, steps, number_of_subtraces) = match &trace { + Either::Left(call) => (call.bytecode(), call.steps(), call.number_of_subtraces()), + Either::Right(create) => ( + create.bytecode(), + create.steps(), + create.number_of_subtraces(), + ), + }; + let bytecode = bytecode.as_ref().expect("JS code asserts"); + + let mut stacktrace: Vec = vec![]; + + let mut subtraces_seen = 0; + + // There was a jump into a function according to the sourcemaps + let mut jumped_into_function = false; + + let mut function_jumpdests: Vec<&Instruction> = vec![]; + + let mut last_submessage_data: Option = None; + + let mut iter = steps.iter().enumerate().peekable(); + while let Some((step_index, step)) = iter.next() { + if let MessageTraceStep::Evm(EvmStep { pc }) = step { + let inst = bytecode.get_instruction(*pc)?; + + if inst.jump_type == JumpType::IntoFunction && iter.peek().is_some() { + let (_, next_step) = iter.peek().unwrap(); + let MessageTraceStep::Evm(next_evm_step) = next_step else { + unreachable!("JS code asserted that"); + }; + let next_inst = bytecode.get_instruction(next_evm_step.pc)?; + + if next_inst.opcode == OpCode::JUMPDEST { + let frame = instruction_to_callstack_stack_trace_entry(bytecode, inst)?; + stacktrace.push(frame); + if next_inst.location.is_some() { + jumped_into_function = true; + } + function_jumpdests.push(next_inst); + } + } else if inst.jump_type == JumpType::OutofFunction { + stacktrace.pop(); + function_jumpdests.pop(); + } + } else { + let message_trace = match step { + MessageTraceStep::Evm(_) => unreachable!("branch is taken above"), + // TODO avoid clones + MessageTraceStep::Precompile(precompile) => { + MessageTrace::Precompile(precompile.clone()) + } + MessageTraceStep::Call(call) => MessageTrace::Call(call.clone()), + MessageTraceStep::Create(create) => MessageTrace::Create(create.clone()), + }; + + subtraces_seen += 1; + + // If there are more subtraces, this one didn't terminate the execution + if subtraces_seen < number_of_subtraces { + continue; + } + + let submessage_trace = get_stack_trace(message_trace.clone())?; + + last_submessage_data = Some(SubmessageData { + message_trace, + step_index: step_index as u32, + stacktrace: submessage_trace, + }); + } + } + + let stacktrace_with_inferred_error = error_inferrer::infer_after_tracing( + trace, + stacktrace, + &function_jumpdests, + jumped_into_function, + last_submessage_data, + )?; + + error_inferrer::filter_redundant_frames(stacktrace_with_inferred_error) + .map_err(SolidityTracerError::from) +} diff --git a/crates/edr_solidity/src/vm_trace_decoder.rs b/crates/edr_solidity/src/vm_trace_decoder.rs new file mode 100644 index 000000000..1e4317856 --- /dev/null +++ b/crates/edr_solidity/src/vm_trace_decoder.rs @@ -0,0 +1,211 @@ +use std::rc::Rc; + +use edr_eth::Bytes; +use serde::{Deserialize, Serialize}; + +use super::{ + message_trace::CreateMessageTrace, + solidity_stack_trace::{ + FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, + UNRECOGNIZED_FUNCTION_NAME, + }, +}; +use crate::{ + artifacts::BuildInfo, + build_model::{Bytecode, ContractFunctionType}, + compiler::create_models_and_decode_bytecodes, + contracts_identifier::ContractsIdentifier, + message_trace::{MessageTrace, MessageTraceStep}, +}; + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TracingConfig { + pub build_infos: Option>, + pub ignore_contracts: Option, +} + +#[derive(Default)] +pub struct VmTraceDecoder { + contracts_identifier: ContractsIdentifier, +} + +impl VmTraceDecoder { + pub fn new() -> Self { + Self::default() + } + + pub fn add_bytecode(&mut self, bytecode: Bytecode) { + self.add_bytecode_inner(Rc::new(bytecode)); + } + + pub fn add_bytecode_inner(&mut self, bytecode: Rc) { + self.contracts_identifier.add_bytecode(bytecode); + } + + pub fn try_to_decode_message_trace(&mut self, message_trace: MessageTrace) -> MessageTrace { + match message_trace { + precompile @ MessageTrace::Precompile(..) => precompile, + // NOTE: The branches below are the same with the difference of `is_create` + MessageTrace::Call(mut call) => { + let is_create = false; + + let bytecode = self + .contracts_identifier + .get_bytecode_for_call(call.code().as_ref(), is_create); + + let steps = call.steps().into_iter().map(|step| { + let trace = match step { + MessageTraceStep::Evm(step) => return MessageTraceStep::Evm(step), + MessageTraceStep::Precompile(precompile) => { + MessageTrace::Precompile(precompile) + } + MessageTraceStep::Create(create) => MessageTrace::Create(create), + MessageTraceStep::Call(call) => MessageTrace::Call(call), + }; + + match self.try_to_decode_message_trace(trace) { + MessageTrace::Precompile(precompile) => { + MessageTraceStep::Precompile(precompile) + } + MessageTrace::Create(create) => MessageTraceStep::Create(create), + MessageTrace::Call(call) => MessageTraceStep::Call(call), + } + }); + + call.set_bytecode(bytecode); + call.set_steps(steps); + + MessageTrace::Call(call) + } + MessageTrace::Create(mut create @ CreateMessageTrace { .. }) => { + let is_create = true; + + let bytecode = self + .contracts_identifier + .get_bytecode_for_call(create.code().as_ref(), is_create); + + let steps = create + .steps() + .into_iter() + .map(|step| { + let trace = match step { + MessageTraceStep::Evm(step) => return MessageTraceStep::Evm(step), + MessageTraceStep::Precompile(precompile) => { + MessageTrace::Precompile(precompile) + } + MessageTraceStep::Create(create) => MessageTrace::Create(create), + MessageTraceStep::Call(call) => MessageTrace::Call(call), + }; + + match self.try_to_decode_message_trace(trace) { + MessageTrace::Precompile(precompile) => { + MessageTraceStep::Precompile(precompile) + } + MessageTrace::Create(create) => MessageTraceStep::Create(create), + MessageTrace::Call(call) => MessageTraceStep::Call(call), + } + }) + .collect::>(); + + create.set_bytecode(bytecode); + create.set_steps(steps); + + MessageTrace::Create(create) + } + } + } + + pub fn get_contract_and_function_names_for_call( + &mut self, + code: &Bytes, + calldata: Option<&Bytes>, + ) -> ContractAndFunctionName { + let is_create = calldata.is_none(); + let bytecode = self + .contracts_identifier + .get_bytecode_for_call(code.as_ref(), is_create); + + let contract = bytecode.map(|bytecode| bytecode.contract.clone()); + let contract = contract.as_ref().map(|c| c.borrow()); + + let contract_name = contract.as_ref().map_or_else( + || UNRECOGNIZED_CONTRACT_NAME.to_string(), + |c| c.name.clone(), + ); + + if is_create { + ContractAndFunctionName { + contract_name, + function_name: None, + } + } else { + match contract { + None => ContractAndFunctionName { + contract_name, + function_name: Some("".to_string()), + }, + Some(contract) => { + let calldata = match calldata { + Some(calldata) => calldata, + None => { + unreachable!("calldata should be Some if is_create is false") + } + }; + + let selector = &calldata.get(..4).unwrap_or(&calldata[..]); + + let func = contract.get_function_from_selector(selector); + + let function_name = match func { + Some(func) => match func.r#type { + ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(), + ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(), + _ => func.name.clone(), + }, + None => UNRECOGNIZED_FUNCTION_NAME.to_string(), + }; + + ContractAndFunctionName { + contract_name, + function_name: Some(function_name), + } + } + } + } + } +} + +pub struct ContractAndFunctionName { + pub contract_name: String, + pub function_name: Option, +} + +pub fn initialize_vm_trace_decoder( + vm_trace_decoder: &mut VmTraceDecoder, + config: TracingConfig, +) -> anyhow::Result<()> { + let Some(build_infos) = config.build_infos else { + return Ok(()); + }; + + for build_info in &build_infos { + let bytecodes = create_models_and_decode_bytecodes( + build_info.solc_version.clone(), + &build_info.input, + &build_info.output, + )?; + + for bytecode in bytecodes { + if config.ignore_contracts == Some(true) + && bytecode.contract.borrow().name.starts_with("Ignored") + { + continue; + } + + vm_trace_decoder.add_bytecode_inner(Rc::new(bytecode)); + } + } + + Ok(()) +} diff --git a/crates/edr_solidity/src/vm_tracer.rs b/crates/edr_solidity/src/vm_tracer.rs index c5c5f53a7..10c7cd541 100644 --- a/crates/edr_solidity/src/vm_tracer.rs +++ b/crates/edr_solidity/src/vm_tracer.rs @@ -11,7 +11,7 @@ use edr_evm::{ use crate::message_trace::{ BaseEvmMessageTrace, BaseMessageTrace, CallMessageTrace, CreateMessageTrace, EvmStep, ExitCode, - MessageTrace, MessageTraceStep, PrecompileMessageTrace, + MessageTrace, PrecompileMessageTrace, VmTracerMessageTraceStep, }; type MessageTraceRefCell = Rc>; @@ -199,7 +199,7 @@ impl VmTracer { parent_trace .steps - .push(MessageTraceStep::Message(Rc::clone(&trace))); + .push(VmTracerMessageTraceStep::Message(Rc::clone(&trace))); parent_trace.number_of_subtraces += 1; } @@ -226,7 +226,7 @@ impl VmTracer { parent_trace .steps - .push(MessageTraceStep::Evm(EvmStep { pc: step.pc })); + .push(VmTracerMessageTraceStep::Evm(EvmStep { pc: step.pc })); } self.tracing_steps.push(step); From 3f87c6f0d4027a161e48f075ed2e024da42dfd30 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 4 Dec 2024 14:46:05 +0100 Subject: [PATCH 02/30] wip --- .../hardhat-network/stack-traces/execution.ts | 86 ++++++++++++------- .../hardhat-network/stack-traces/test.ts | 68 +++++++-------- 2 files changed, 87 insertions(+), 67 deletions(-) diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 8a0652242..9abe810de 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -14,6 +14,8 @@ import { import { EdrProviderWrapper } from "hardhat/internal/hardhat-network/provider/provider"; import { VMTracer } from "hardhat/internal/hardhat-network/stack-traces/vm-tracer"; import { LoggerConfig } from "hardhat/internal/hardhat-network/provider/modules/logger"; +import { SolidityStackTrace } from "hardhat/internal/hardhat-network/stack-traces/solidity-stack-trace"; +import { Response } from "@nomicfoundation/edr"; function toBuffer(x: Parameters[0]) { return Buffer.from(toBytes(x)); @@ -104,34 +106,60 @@ interface TxData { export async function traceTransaction( provider: EdrProviderWrapper, - txData: TxData -): Promise { - const vmTracer = new VMTracer(); - provider.setVmTracer(vmTracer); - - try { - await provider.request({ - method: "eth_sendTransaction", - params: [ - { - from: senderAddress, - data: bytesToHex(txData.data), - to: txData.to !== undefined ? bytesToHex(txData.to) : undefined, - value: bigIntToHex(txData.value ?? 0n), - // If the test didn't define a gasLimit, we assume 4M is enough - gas: bigIntToHex(txData.gas ?? 4000000n), - gasPrice: bigIntToHex(10n), - }, - ], - }); - - const trace = vmTracer.getLastTopLevelMessageTrace(); - if (trace === undefined) { - const error = vmTracer.getLastError(); - throw error ?? new Error("Cannot get last top level message trace"); - } - return trace; - } finally { - provider.setVmTracer(undefined); + txData: TxData, + tracingConfig: TracingConfig, +): Promise { + const stringifiedArgs = JSON.stringify({ + method: "eth_sendTransaction", + params: [ + { + from: senderAddress, + data: bytesToHex(txData.data), + to: txData.to !== undefined ? bytesToHex(txData.to) : undefined, + value: bigIntToHex(txData.value ?? 0n), + // If the test didn't define a gasLimit, we assume 4M is enough + gas: bigIntToHex(txData.gas ?? 4000000n), + gasPrice: bigIntToHex(10n), + }, + ], + }); + + if (txData.to !== undefined) { + const code = await provider.request({ + method: "eth_getCode", + params: [bytesToHex(txData.to), "latest"], + }) + + // uncomment to see code and calldata + // console.log(code) + // console.log(bytesToHex(txData.data)) + } + + const responseObject: Response = await provider["_provider"].handleRequest(stringifiedArgs) + + let response; + if (typeof responseObject.data === "string") { + response = JSON.parse(responseObject.data); + } else { + response = responseObject.data; } + + const receipt: any = await provider.request({ + method: "eth_getTransactionReceipt", + params: [response.result], + }) + + const stackTrace = responseObject.stackTrace(tracingConfig); + + const contractAddress = receipt.contractAddress?.slice(2); + + if (typeof stackTrace === "string") { + throw new Error("shouldn't happen"); // FVTODO + } + + if (stackTrace === null) { + return contractAddress; + } + + return stackTrace; } diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index f422393f9..97ad5ee8a 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -20,6 +20,7 @@ import { MessageTrace, } from "hardhat/internal/hardhat-network/stack-traces/message-trace"; import { + SolidityStackTrace, SolidityStackTraceEntry, StackTraceEntryType, } from "hardhat/internal/hardhat-network/stack-traces/solidity-stack-trace"; @@ -492,22 +493,23 @@ async function runTest( const txIndexToContract: Map = new Map(); for (const [txIndex, tx] of testDefinition.transactions.entries()) { - let trace: MessageTrace; + let stackTraceOrContractAddress: SolidityStackTrace | string | undefined; if ("file" in tx) { - trace = await runDeploymentTransactionTest( + stackTraceOrContractAddress = await runDeploymentTransactionTest( txIndex, tx, provider, compilerOutput, - txIndexToContract + txIndexToContract, + tracingConfig, ); - if (trace.deployedContract !== undefined) { + if (typeof stackTraceOrContractAddress === "string") { txIndexToContract.set(txIndex, { file: tx.file, name: tx.contract, - address: Buffer.from(trace.deployedContract), + address: Buffer.from(stackTraceOrContractAddress, "hex"), }); } } else { @@ -518,54 +520,50 @@ async function runTest( `No contract was deployed in tx ${tx.to} but transaction ${txIndex} is trying to call it` ); - trace = await runCallTransactionTest( + stackTraceOrContractAddress = await runCallTransactionTest( txIndex, tx, provider, compilerOutput, - contract! + contract!, + tracingConfig, ); } - // eslint-disable-next-line @typescript-eslint/dot-notation - const vmTraceDecoder = provider["_vmTraceDecoder"] as VmTraceDecoderT; - const decodedTrace = vmTraceDecoder.tryToDecodeMessageTrace(trace); - try { if (tx.stackTrace === undefined) { - assert.isFalse( - trace.exit.isError(), - `Transaction ${txIndex} shouldn't have failed (${trace.exit.getReason()})` + assert.isTrue( + stackTraceOrContractAddress === undefined || typeof stackTraceOrContractAddress === "string", + // FVTODO + `Transaction ${txIndex} shouldn't have failed` ); } else { - assert.isDefined( - trace.exit.isError(), + assert.isFalse( + stackTraceOrContractAddress === undefined || typeof stackTraceOrContractAddress === "string", `Transaction ${txIndex} should have failed` ); } } catch (error) { - printMessageTrace(decodedTrace); + // printMessageTrace(decodedTrace); FVTODo throw error; } - if (trace.exit.isError()) { - const stackTrace = solidityTracer.getStackTrace(decodedTrace); - + if (stackTraceOrContractAddress !== undefined && typeof stackTraceOrContractAddress !== "string") { try { compareStackTraces( txIndex, - stackTrace, + stackTraceOrContractAddress, tx.stackTrace!, compilerOptions.optimizer ); if (testDefinition.print !== undefined && testDefinition.print) { console.log(`Transaction ${txIndex} stack trace`); - printStackTrace(stackTrace); + printStackTrace(stackTraceOrContractAddress); } } catch (err) { - printMessageTrace(decodedTrace); - printStackTrace(stackTrace); + // printMessageTrace(decodedTrace); TODO + printStackTrace(stackTraceOrContractAddress); throw err; } @@ -632,8 +630,9 @@ async function runDeploymentTransactionTest( tx: DeploymentTransaction, provider: EdrProviderWrapper, compilerOutput: CompilerOutput, - txIndexToContract: Map -): Promise { + txIndexToContract: Map, + tracingConfig: TracingConfig, +): Promise { const file = compilerOutput.contracts[tx.file]; assert.isDefined( @@ -666,11 +665,7 @@ async function runDeploymentTransactionTest( value: tx.value !== undefined ? BigInt(tx.value) : undefined, data, gas: tx.gas !== undefined ? BigInt(tx.gas) : undefined, - }); - - if ("precompile" in trace || "calldata" in trace) { - assert.fail("Expected trace to be a deployment trace"); - } + }, tracingConfig); return trace; } @@ -680,8 +675,9 @@ async function runCallTransactionTest( tx: CallTransaction, provider: EdrProviderWrapper, compilerOutput: CompilerOutput, - contract: DeployedContract -): Promise { + contract: DeployedContract, + tracingConfig: TracingConfig, +): Promise { const compilerContract = compilerOutput.contracts[contract.file][contract.name]; @@ -704,11 +700,7 @@ async function runCallTransactionTest( value: tx.value !== undefined ? BigInt(tx.value) : undefined, data, gas: tx.gas !== undefined ? BigInt(tx.gas) : undefined, - }); - - if (!("calldata" in trace) || "precompile" in trace) { - assert.fail("Expected trace to be a call trace"); - } + }, tracingConfig); return trace; } From fad13586582d2d829bbcdb957514f09dbd7b994d Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Wed, 4 Dec 2024 17:29:48 +0000 Subject: [PATCH 03/30] Set throwOnTransactionFailures true and get tx hash from error --- .../internal/hardhat-network/\0010\023@8\031\255@8" | 0 .../hardhat-network/stack-traces/execution.ts | 13 +++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 "hardhat-tests/test/internal/hardhat-network/\0010\023@8\031\255@8" diff --git "a/hardhat-tests/test/internal/hardhat-network/\0010\023@8\031\255@8" "b/hardhat-tests/test/internal/hardhat-network/\0010\023@8\031\255@8" new file mode 100644 index 000000000..e69de29bb diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 9abe810de..9abb53591 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -49,7 +49,7 @@ export async function instantiateProvider( }, ], allowUnlimitedContractSize: false, - throwOnTransactionFailures: false, + throwOnTransactionFailures: true, throwOnCallFailures: false, allowBlocksWithSameTimestamp: false, coinbase: "0x0000000000000000000000000000000000000000", @@ -107,7 +107,7 @@ interface TxData { export async function traceTransaction( provider: EdrProviderWrapper, txData: TxData, - tracingConfig: TracingConfig, + tracingConfig: TracingConfig ): Promise { const stringifiedArgs = JSON.stringify({ method: "eth_sendTransaction", @@ -128,14 +128,15 @@ export async function traceTransaction( const code = await provider.request({ method: "eth_getCode", params: [bytesToHex(txData.to), "latest"], - }) + }); // uncomment to see code and calldata // console.log(code) // console.log(bytesToHex(txData.data)) } - const responseObject: Response = await provider["_provider"].handleRequest(stringifiedArgs) + const responseObject: Response = + await provider["_provider"].handleRequest(stringifiedArgs); let response; if (typeof responseObject.data === "string") { @@ -146,8 +147,8 @@ export async function traceTransaction( const receipt: any = await provider.request({ method: "eth_getTransactionReceipt", - params: [response.result], - }) + params: [response.result ?? response.error.data.transactionHash], + }); const stackTrace = responseObject.stackTrace(tracingConfig); From 70ee8de204c24fc1fe643381a98bac6613b2aa51 Mon Sep 17 00:00:00 2001 From: Agost Biro <5764438+agostbiro@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:20:04 +0100 Subject: [PATCH 04/30] refactor: pass tracing config to provider (#738) * wip: make tracing config a provider argument * Patch Hardhat to pass tracing config to provider --------- Co-authored-by: Franco Victorio --- crates/edr_napi/index.d.ts | 6 ++--- crates/edr_napi/src/provider.rs | 23 +++++++++------- crates/edr_solidity/src/vm_trace_decoder.rs | 6 ++--- .../hardhat-network/stack-traces/execution.ts | 5 ++-- .../hardhat-network/stack-traces/test.ts | 27 ++++++++++--------- package.json | 3 +++ patches/hardhat@2.22.15.patch | 13 +++++++++ pnpm-lock.yaml | 27 +++++++++++-------- 8 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 patches/hardhat@2.22.15.patch diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 47e4c45a2..b6c71a625 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -645,7 +645,7 @@ export declare class EdrContext { /** A JSON-RPC provider for Ethereum. */ export declare class Provider { /**Constructs a new provider with the provided configuration. */ - static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, subscriberCallback: (event: SubscriptionEvent) => void): Promise + static withConfig(context: EdrContext, config: ProviderConfig, loggerConfig: LoggerConfig, tracingConfig: any, subscriberCallback: (event: SubscriptionEvent) => void): Promise /**Handles a JSON-RPC request and returns a JSON-RPC response. */ handleRequest(jsonRequest: string): Promise setCallOverrideCallback(callOverrideCallback: (contract_address: Buffer, data: Buffer) => Promise): void @@ -661,8 +661,8 @@ export declare class Response { /** Returns the response data as a JSON string or a JSON object. */ get data(): string | any get solidityTrace(): RawTrace | null - /**Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as fallback. */ - stackTrace(config: any): SolidityStackTrace | string | null + /**Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback. */ + stackTrace(): SolidityStackTrace | string | null get traces(): Array } /** diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index d42fae2d4..d56f4197c 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -22,6 +22,7 @@ use crate::{ pub struct Provider { provider: Arc>, runtime: runtime::Handle, + tracing_config: Arc, #[cfg(feature = "scenarios")] scenario_file: Option>, } @@ -36,6 +37,8 @@ impl Provider { _context: &EdrContext, config: ProviderConfig, logger_config: LoggerConfig, + // TODO avoid opaque type + tracing_config: serde_json::Value, #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction, ) -> napi::Result { let config = edr_provider::ProviderConfig::try_from(config)?; @@ -45,6 +48,10 @@ impl Provider { let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?; let subscriber_callback = Box::new(move |event| subscriber_callback.call(event)); + // TODO get actual type as argument + let tracing_config: edr_solidity::vm_trace_decoder::TracingConfig = + serde_json::from_value(tracing_config)?; + let (deferred, promise) = env.create_deferred()?; runtime.clone().spawn_blocking(move || { #[cfg(feature = "scenarios")] @@ -67,6 +74,7 @@ impl Provider { Ok(Provider { provider: Arc::new(provider), runtime, + tracing_config: Arc::new(tracing_config), #[cfg(feature = "scenarios")] scenario_file, }) @@ -125,6 +133,7 @@ impl Provider { solidity_trace: None, data: Either::A(json), traces: Vec::new(), + tracing_config: Arc::clone(&self.tracing_config), }); } }; @@ -189,6 +198,7 @@ impl Provider { solidity_trace, data, traces: traces.into_iter().map(Arc::new).collect(), + tracing_config: Arc::clone(&self.tracing_config), }) } @@ -234,6 +244,7 @@ pub struct Response { solidity_trace: Option>, /// This may contain zero or more traces, depending on the (batch) request traces: Vec>, + tracing_config: Arc, } #[napi] @@ -252,12 +263,9 @@ impl Response { } // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590 - #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as fallback."] + #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback."] #[napi] - pub fn stack_trace( - &self, - config: serde_json::Value, - ) -> napi::Result>> { + pub fn stack_trace(&self) -> napi::Result>> { let Some(trace) = &self.solidity_trace else { return Ok(None); }; @@ -267,13 +275,10 @@ impl Response { let mut vm_trace = vm_tracer.get_last_top_level_message_trace(); let vm_tracer_error = vm_tracer.get_last_error(); - // TODO get actual type as argument - let tracing_config: edr_solidity::vm_trace_decoder::TracingConfig = - serde_json::from_value(config)?; let mut vm_trace_decoder = edr_solidity::vm_trace_decoder::VmTraceDecoder::new(); edr_solidity::vm_trace_decoder::initialize_vm_trace_decoder( &mut vm_trace_decoder, - tracing_config, + &self.tracing_config, )?; vm_trace = vm_trace.map(|trace| vm_trace_decoder.try_to_decode_message_trace(trace)); diff --git a/crates/edr_solidity/src/vm_trace_decoder.rs b/crates/edr_solidity/src/vm_trace_decoder.rs index 1e4317856..50ca6858c 100644 --- a/crates/edr_solidity/src/vm_trace_decoder.rs +++ b/crates/edr_solidity/src/vm_trace_decoder.rs @@ -183,13 +183,13 @@ pub struct ContractAndFunctionName { pub fn initialize_vm_trace_decoder( vm_trace_decoder: &mut VmTraceDecoder, - config: TracingConfig, + config: &TracingConfig, ) -> anyhow::Result<()> { - let Some(build_infos) = config.build_infos else { + let Some(build_infos) = &config.build_infos else { return Ok(()); }; - for build_info in &build_infos { + for build_info in build_infos { let bytecodes = create_models_and_decode_bytecodes( build_info.solc_version.clone(), &build_info.input, diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 9abb53591..06ca2fad1 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -106,8 +106,7 @@ interface TxData { export async function traceTransaction( provider: EdrProviderWrapper, - txData: TxData, - tracingConfig: TracingConfig + txData: TxData ): Promise { const stringifiedArgs = JSON.stringify({ method: "eth_sendTransaction", @@ -150,7 +149,7 @@ export async function traceTransaction( params: [response.result ?? response.error.data.transactionHash], }); - const stackTrace = responseObject.stackTrace(tracingConfig); + const stackTrace = responseObject.stackTrace(); const contractAddress = receipt.contractAddress?.slice(2); diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index 97ad5ee8a..930e83c11 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -501,8 +501,7 @@ async function runTest( tx, provider, compilerOutput, - txIndexToContract, - tracingConfig, + txIndexToContract ); if (typeof stackTraceOrContractAddress === "string") { @@ -525,21 +524,22 @@ async function runTest( tx, provider, compilerOutput, - contract!, - tracingConfig, + contract! ); } try { if (tx.stackTrace === undefined) { assert.isTrue( - stackTraceOrContractAddress === undefined || typeof stackTraceOrContractAddress === "string", + stackTraceOrContractAddress === undefined || + typeof stackTraceOrContractAddress === "string", // FVTODO `Transaction ${txIndex} shouldn't have failed` ); } else { assert.isFalse( - stackTraceOrContractAddress === undefined || typeof stackTraceOrContractAddress === "string", + stackTraceOrContractAddress === undefined || + typeof stackTraceOrContractAddress === "string", `Transaction ${txIndex} should have failed` ); } @@ -549,7 +549,10 @@ async function runTest( throw error; } - if (stackTraceOrContractAddress !== undefined && typeof stackTraceOrContractAddress !== "string") { + if ( + stackTraceOrContractAddress !== undefined && + typeof stackTraceOrContractAddress !== "string" + ) { try { compareStackTraces( txIndex, @@ -630,8 +633,7 @@ async function runDeploymentTransactionTest( tx: DeploymentTransaction, provider: EdrProviderWrapper, compilerOutput: CompilerOutput, - txIndexToContract: Map, - tracingConfig: TracingConfig, + txIndexToContract: Map ): Promise { const file = compilerOutput.contracts[tx.file]; @@ -665,7 +667,7 @@ async function runDeploymentTransactionTest( value: tx.value !== undefined ? BigInt(tx.value) : undefined, data, gas: tx.gas !== undefined ? BigInt(tx.gas) : undefined, - }, tracingConfig); + }); return trace; } @@ -675,8 +677,7 @@ async function runCallTransactionTest( tx: CallTransaction, provider: EdrProviderWrapper, compilerOutput: CompilerOutput, - contract: DeployedContract, - tracingConfig: TracingConfig, + contract: DeployedContract ): Promise { const compilerContract = compilerOutput.contracts[contract.file][contract.name]; @@ -700,7 +701,7 @@ async function runCallTransactionTest( value: tx.value !== undefined ? BigInt(tx.value) : undefined, data, gas: tx.gas !== undefined ? BigInt(tx.gas) : undefined, - }, tracingConfig); + }); return trace; } diff --git a/package.json b/package.json index d7f2831ba..d01f5f0bb 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "pnpm": { "overrides": { "hardhat>@nomicfoundation/edr": "workspace:*" + }, + "patchedDependencies": { + "hardhat@2.22.15": "patches/hardhat@2.22.15.patch" } }, "private": true, diff --git a/patches/hardhat@2.22.15.patch b/patches/hardhat@2.22.15.patch new file mode 100644 index 000000000..ecb3587ad --- /dev/null +++ b/patches/hardhat@2.22.15.patch @@ -0,0 +1,13 @@ +diff --git a/internal/hardhat-network/provider/provider.js b/internal/hardhat-network/provider/provider.js +index a4b921c8a37b7d5967955d0449df3d05dbe725a8..7ff9eac18cb9a587aa6bd13ff69904cb4b610611 100644 +--- a/internal/hardhat-network/provider/provider.js ++++ b/internal/hardhat-network/provider/provider.js +@@ -196,7 +196,7 @@ class EdrProviderWrapper extends events_1.EventEmitter { + printLineFn(message); + } + }, +- }, (event) => { ++ }, tracingConfig ?? {}, (event) => { + eventAdapter.emit("ethEvent", event); + }); + const minimalEthereumJsNode = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72c72585c..d61c94f6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,11 @@ settings: overrides: hardhat>@nomicfoundation/edr: workspace:* +patchedDependencies: + hardhat@2.22.15: + hash: bv5kqmujiion6gbtrpeh3dexx4 + path: patches/hardhat@2.22.15.patch + importers: .: @@ -124,7 +129,7 @@ importers: version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) hardhat: specifier: 2.22.15 - version: 2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash: specifier: ^4.17.11 version: 4.17.21 @@ -250,7 +255,7 @@ importers: version: 7.0.1 hardhat: specifier: 2.22.15 - version: 2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -280,10 +285,10 @@ importers: devDependencies: '@defi-wonderland/smock': specifier: ^2.4.0 - version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@types/node': specifier: ^20.0.0 version: 20.16.1 @@ -295,7 +300,7 @@ importers: version: 5.7.2 hardhat: specifier: 2.22.15 - version: 2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -3351,16 +3356,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) diff: 5.0.0 ethers: 5.7.2 - hardhat: 2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash.isequal: 4.5.0 lodash.isequalwith: 4.4.0 rxjs: 7.8.1 @@ -3876,10 +3881,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: ethers: 5.7.2 - hardhat: 2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) '@pkgr/core@0.1.1': {} @@ -5193,7 +5198,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.15(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): + hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 From 36059bb3ac2dd97226d8266ecd8fc86c5e36b4b1 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 5 Dec 2024 09:56:31 +0000 Subject: [PATCH 05/30] Remove solidity_trace getter from Response --- crates/edr_napi/index.d.ts | 1 - crates/edr_napi/src/provider.rs | 39 ++++++++++----------- crates/edr_solidity/src/vm_trace_decoder.rs | 2 +- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index b6c71a625..57a034f8b 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -660,7 +660,6 @@ export declare class Provider { export declare class Response { /** Returns the response data as a JSON string or a JSON object. */ get data(): string | any - get solidityTrace(): RawTrace | null /**Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback. */ stackTrace(): SolidityStackTrace | string | null get traces(): Array diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index d56f4197c..3ab872266 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -133,7 +133,6 @@ impl Provider { solidity_trace: None, data: Either::A(json), traces: Vec::new(), - tracing_config: Arc::clone(&self.tracing_config), }); } }; @@ -194,11 +193,16 @@ impl Provider { } }) .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string())) - .map(|data| Response { - solidity_trace, - data, - traces: traces.into_iter().map(Arc::new).collect(), - tracing_config: Arc::clone(&self.tracing_config), + .map(|data| { + let solidity_trace = solidity_trace.map(|trace| SolidityTraceData { + trace, + config: Arc::clone(&self.tracing_config), + }); + Response { + solidity_trace, + data, + traces: traces.into_iter().map(Arc::new).collect(), + } }) } @@ -233,6 +237,12 @@ impl Provider { } } +#[derive(Debug)] +struct SolidityTraceData { + trace: Arc, + config: Arc, +} + #[napi] pub struct Response { // N-API is known to be slow when marshalling `serde_json::Value`s, so we try to return a @@ -241,10 +251,9 @@ pub struct Response { data: Either, /// When a transaction fails to execute, the provider returns a trace of the /// transaction. - solidity_trace: Option>, + solidity_trace: Option, /// This may contain zero or more traces, depending on the (batch) request traces: Vec>, - tracing_config: Arc, } #[napi] @@ -255,18 +264,11 @@ impl Response { self.data.clone() } - #[napi(getter)] - pub fn solidity_trace(&self) -> Option { - self.solidity_trace - .as_ref() - .map(|trace| RawTrace::new(trace.clone())) - } - // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590 #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback."] #[napi] pub fn stack_trace(&self) -> napi::Result>> { - let Some(trace) = &self.solidity_trace else { + let Some(SolidityTraceData { trace, config }) = &self.solidity_trace else { return Ok(None); }; let mut vm_tracer = edr_solidity::vm_tracer::VmTracer::new(); @@ -276,10 +278,7 @@ impl Response { let vm_tracer_error = vm_tracer.get_last_error(); let mut vm_trace_decoder = edr_solidity::vm_trace_decoder::VmTraceDecoder::new(); - edr_solidity::vm_trace_decoder::initialize_vm_trace_decoder( - &mut vm_trace_decoder, - &self.tracing_config, - )?; + edr_solidity::vm_trace_decoder::initialize_vm_trace_decoder(&mut vm_trace_decoder, config)?; vm_trace = vm_trace.map(|trace| vm_trace_decoder.try_to_decode_message_trace(trace)); diff --git a/crates/edr_solidity/src/vm_trace_decoder.rs b/crates/edr_solidity/src/vm_trace_decoder.rs index 50ca6858c..63bfbee92 100644 --- a/crates/edr_solidity/src/vm_trace_decoder.rs +++ b/crates/edr_solidity/src/vm_trace_decoder.rs @@ -18,7 +18,7 @@ use crate::{ message_trace::{MessageTrace, MessageTraceStep}, }; -#[derive(Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct TracingConfig { pub build_infos: Option>, From 781b57e420b82725f7112574e5dd1c195112832b Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 5 Dec 2024 10:30:14 +0000 Subject: [PATCH 06/30] Remove duplicated NAPI logic --- crates/edr_napi/index.d.ts | 97 +- crates/edr_napi/index.js | 12 +- crates/edr_napi/src/cast.rs | 2 +- crates/edr_napi/src/provider.rs | 16 +- crates/edr_napi/src/trace.rs | 11 +- crates/edr_napi/src/trace/compiler.rs | 27 - crates/edr_napi/src/trace/debug.rs | 264 +- crates/edr_napi/src/trace/error_inferrer.rs | 2255 ----------------- crates/edr_napi/src/trace/library_utils.rs | 6 - ...d_inlined_internal_functions_heuristics.rs | 180 -- crates/edr_napi/src/trace/message_trace.rs | 179 -- crates/edr_napi/src/trace/model.rs | 28 - .../src/trace/solidity_stack_trace.rs | 76 - crates/edr_napi/src/trace/solidity_tracer.rs | 315 --- crates/edr_napi/src/trace/vm_trace_decoder.rs | 234 -- crates/edr_napi/src/trace/vm_tracer.rs | 71 - .../hardhat-network/\0010\023@8\031\255@8" | 0 .../hardhat-network/stack-traces/test.ts | 13 +- 18 files changed, 24 insertions(+), 3762 deletions(-) delete mode 100644 crates/edr_napi/src/trace/compiler.rs delete mode 100644 crates/edr_napi/src/trace/error_inferrer.rs delete mode 100644 crates/edr_napi/src/trace/library_utils.rs delete mode 100644 crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs delete mode 100644 crates/edr_napi/src/trace/message_trace.rs delete mode 100644 crates/edr_napi/src/trace/solidity_tracer.rs delete mode 100644 crates/edr_napi/src/trace/vm_trace_decoder.rs delete mode 100644 crates/edr_napi/src/trace/vm_tracer.rs delete mode 100644 "hardhat-tests/test/internal/hardhat-network/\0010\023@8\031\255@8" diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 57a034f8b..6e4df5f0e 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -346,18 +346,6 @@ export interface SubscriptionEvent { filterId: bigint result: any } -export declare function createModelsAndDecodeBytecodes(solcVersion: string, compilerInput: any, compilerOutput: any): Array -export declare function linkHexStringBytecode(code: string, address: string, position: number): string -export const enum ContractFunctionType { - CONSTRUCTOR = 0, - FUNCTION = 1, - FALLBACK = 2, - RECEIVE = 3, - GETTER = 4, - MODIFIER = 5, - FREE_FUNCTION = 6 -} -export declare function printMessageTrace(trace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace, depth?: number | undefined | null): void export declare function printStackTrace(trace: SolidityStackTrace): void /** Represents the exit code of the EVM. */ export const enum ExitCode { @@ -380,51 +368,14 @@ export const enum ExitCode { /** Unknown halt reason. */ UNKNOWN_HALT_REASON = 8 } -export interface EvmStep { - pc: number -} -export interface PrecompileMessageTrace { - value: bigint - returnData: Uint8Array - exit: Exit - gasUsed: bigint - depth: number - precompile: number - calldata: Uint8Array -} -export interface CreateMessageTrace { - value: bigint - returnData: Uint8Array - exit: Exit - gasUsed: bigint - depth: number - code: Uint8Array - steps: Array - /** - * Reference to the resolved `Bytecode` EDR data. - * Only used on the JS side by the `VmTraceDecoder` class. - */ - bytecode?: BytecodeWrapper - numberOfSubtraces: number - deployedContract?: Uint8Array | undefined -} -export interface CallMessageTrace { - value: bigint - returnData: Uint8Array - exit: Exit - gasUsed: bigint - depth: number - code: Uint8Array - steps: Array - /** - * Reference to the resolved `Bytecode` EDR data. - * Only used on the JS side by the `VmTraceDecoder` class. - */ - bytecode?: BytecodeWrapper - numberOfSubtraces: number - calldata: Uint8Array - address: Uint8Array - codeAddress: Uint8Array +export const enum ContractFunctionType { + CONSTRUCTOR = 0, + FUNCTION = 1, + FALLBACK = 2, + RECEIVE = 3, + GETTER = 4, + MODIFIER = 5, + FREE_FUNCTION = 6 } export const enum StackTraceEntryType { CALLSTACK_ENTRY = 0, @@ -580,11 +531,6 @@ export interface ContractCallRunOutOfGasError { type: StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR sourceReference?: SourceReference } -export interface ContractAndFunctionName { - contractName: string - functionName: string | undefined -} -export declare function initializeVmTraceDecoder(vmTraceDecoder: VmTraceDecoder, tracingConfig: any): void export interface TracingMessage { /** Sender address */ readonly caller: Buffer @@ -660,15 +606,10 @@ export declare class Provider { export declare class Response { /** Returns the response data as a JSON string or a JSON object. */ get data(): string | any + get traces(): Array /**Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback. */ stackTrace(): SolidityStackTrace | string | null - get traces(): Array } -/** - * Opaque handle to the `Bytecode` struct. - * Only used on the JS side by the `VmTraceDecoder` class. - */ -export declare class BytecodeWrapper { } export declare class Exit { get kind(): ExitCode isError(): boolean @@ -683,26 +624,6 @@ export declare class ReturnData { decodeError(): string decodePanic(): bigint } -export declare class SolidityTracer { - - constructor() - getStackTrace(trace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace): SolidityStackTrace -} -export declare class VmTraceDecoder { - constructor() - addBytecode(bytecode: BytecodeWrapper): void - tryToDecodeMessageTrace(messageTrace: PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace): PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace - getContractAndFunctionNamesForCall(code: Uint8Array, calldata: Uint8Array | undefined): ContractAndFunctionName -} -export type VMTracer = VmTracer -/** N-API bindings for the Rust port of `VMTracer` from Hardhat. */ -export declare class VmTracer { - constructor() - /** Observes a trace, collecting information about the execution of the EVM. */ - observe(trace: RawTrace): void - getLastTopLevelMessageTrace(): PrecompileMessageTrace | CallMessageTrace | CreateMessageTrace | undefined - getLastError(): Error | undefined -} export declare class RawTrace { trace(): Array } diff --git a/crates/edr_napi/index.js b/crates/edr_napi/index.js index beca0fd50..480c1c1da 100644 --- a/crates/edr_napi/index.js +++ b/crates/edr_napi/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, createModelsAndDecodeBytecodes, linkHexStringBytecode, BytecodeWrapper, ContractFunctionType, printMessageTrace, printStackTrace, Exit, ExitCode, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, SolidityTracer, VmTraceDecoder, initializeVmTraceDecoder, VmTracer, RawTrace } = nativeBinding +const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, printStackTrace, Exit, ExitCode, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace } = nativeBinding module.exports.SpecId = SpecId module.exports.EdrContext = EdrContext @@ -319,14 +319,10 @@ module.exports.Provider = Provider module.exports.Response = Response module.exports.SuccessReason = SuccessReason module.exports.ExceptionalHalt = ExceptionalHalt -module.exports.createModelsAndDecodeBytecodes = createModelsAndDecodeBytecodes -module.exports.linkHexStringBytecode = linkHexStringBytecode -module.exports.BytecodeWrapper = BytecodeWrapper -module.exports.ContractFunctionType = ContractFunctionType -module.exports.printMessageTrace = printMessageTrace module.exports.printStackTrace = printStackTrace module.exports.Exit = Exit module.exports.ExitCode = ExitCode +module.exports.ContractFunctionType = ContractFunctionType module.exports.ReturnData = ReturnData module.exports.StackTraceEntryType = StackTraceEntryType module.exports.stackTraceEntryTypeToString = stackTraceEntryTypeToString @@ -337,8 +333,4 @@ module.exports.UNRECOGNIZED_FUNCTION_NAME = UNRECOGNIZED_FUNCTION_NAME module.exports.UNKNOWN_FUNCTION_NAME = UNKNOWN_FUNCTION_NAME module.exports.PRECOMPILE_FUNCTION_NAME = PRECOMPILE_FUNCTION_NAME module.exports.UNRECOGNIZED_CONTRACT_NAME = UNRECOGNIZED_CONTRACT_NAME -module.exports.SolidityTracer = SolidityTracer -module.exports.VmTraceDecoder = VmTraceDecoder -module.exports.initializeVmTraceDecoder = initializeVmTraceDecoder -module.exports.VmTracer = VmTracer module.exports.RawTrace = RawTrace diff --git a/crates/edr_napi/src/cast.rs b/crates/edr_napi/src/cast.rs index 06c47aef9..b9041fe5a 100644 --- a/crates/edr_napi/src/cast.rs +++ b/crates/edr_napi/src/cast.rs @@ -1,6 +1,6 @@ use edr_eth::{Address, Bytes, B256, B64, U256}; use napi::{ - bindgen_prelude::{BigInt, Buffer, Uint8Array}, + bindgen_prelude::{BigInt, Buffer}, Status, }; diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 3ab872266..56dd0fac8 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -264,6 +264,14 @@ impl Response { self.data.clone() } + #[napi(getter)] + pub fn traces(&self) -> Vec { + self.traces + .iter() + .map(|trace| RawTrace::new(trace.clone())) + .collect() + } + // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590 #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback."] #[napi] @@ -301,12 +309,4 @@ impl Response { Ok(None) } } - - #[napi(getter)] - pub fn traces(&self) -> Vec { - self.traces - .iter() - .map(|trace| RawTrace::new(trace.clone())) - .collect() - } } diff --git a/crates/edr_napi/src/trace.rs b/crates/edr_napi/src/trace.rs index d7b7d0d10..0113da9f7 100644 --- a/crates/edr_napi/src/trace.rs +++ b/crates/edr_napi/src/trace.rs @@ -16,20 +16,11 @@ use napi_derive::napi; use crate::result::ExecutionResult; -mod compiler; -mod library_utils; -mod model; - mod debug; -mod error_inferrer; mod exit; -mod mapped_inlined_internal_functions_heuristics; -mod message_trace; +mod model; mod return_data; pub mod solidity_stack_trace; -mod solidity_tracer; -mod vm_trace_decoder; -mod vm_tracer; #[napi(object)] pub struct TracingMessage { diff --git a/crates/edr_napi/src/trace/compiler.rs b/crates/edr_napi/src/trace/compiler.rs deleted file mode 100644 index 88956b160..000000000 --- a/crates/edr_napi/src/trace/compiler.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::rc::Rc; - -use edr_solidity::artifacts::{CompilerInput, CompilerOutput}; -use napi::{bindgen_prelude::ClassInstance, Env}; -use napi_derive::napi; - -use crate::trace::model::BytecodeWrapper; - -#[napi(catch_unwind)] -pub fn create_models_and_decode_bytecodes( - solc_version: String, - compiler_input: serde_json::Value, - compiler_output: serde_json::Value, - env: Env, -) -> napi::Result>> { - let compiler_input: CompilerInput = serde_json::from_value(compiler_input)?; - let compiler_output: CompilerOutput = serde_json::from_value(compiler_output)?; - - edr_solidity::compiler::create_models_and_decode_bytecodes( - solc_version, - &compiler_input, - &compiler_output, - )? - .into_iter() - .map(|bytecode| BytecodeWrapper::new(Rc::new(bytecode)).into_instance(env)) - .collect() -} diff --git a/crates/edr_napi/src/trace/debug.rs b/crates/edr_napi/src/trace/debug.rs index dff820dbd..18daef116 100644 --- a/crates/edr_napi/src/trace/debug.rs +++ b/crates/edr_napi/src/trace/debug.rs @@ -1,271 +1,11 @@ //! Port of `hardhat-network/stack-traces/debug.ts` from Hardhat. -use edr_eth::U256; -use edr_evm::{hex, interpreter::OpCode}; -use edr_solidity::build_model::JumpType; -use napi::{ - bindgen_prelude::{Either24, Either3, Either4}, - Either, Env, Status, -}; +use napi::bindgen_prelude::Either24; use napi_derive::napi; -use super::{ - message_trace::{CallMessageTrace, CreateMessageTrace, PrecompileMessageTrace}, - solidity_stack_trace::{RevertErrorStackTraceEntry, SolidityStackTrace}, -}; +use super::solidity_stack_trace::{RevertErrorStackTraceEntry, SolidityStackTrace}; use crate::trace::return_data::ReturnData; -const MARGIN_SPACE: usize = 6; - -#[napi] -fn print_message_trace( - trace: Either3, - depth: Option, - env: Env, -) -> napi::Result<()> { - let trace = match &trace { - Either3::A(precompile) => Either3::A(precompile), - Either3::B(call) => Either3::B(call), - Either3::C(create) => Either3::C(create), - }; - - let depth = depth.unwrap_or(0); - - print_message_trace_inner(trace, depth, env) -} - -fn print_message_trace_inner( - trace: Either3<&PrecompileMessageTrace, &CallMessageTrace, &CreateMessageTrace>, - depth: u32, - env: Env, -) -> napi::Result<()> { - println!(); - - match trace { - Either3::A(precompile) => print_precompile_trace(precompile, depth), - Either3::B(call) => print_call_trace(call, depth, env)?, - Either3::C(create) => print_create_trace(create, depth, env)?, - } - - println!(); - - Ok(()) -} - -fn print_precompile_trace(trace: &PrecompileMessageTrace, depth: u32) { - let margin = " ".repeat(depth as usize * MARGIN_SPACE); - - let value = U256::from_limbs_slice(&trace.value.words); - - println!("{margin}Precompile trace"); - - println!("{margin} precompile number: {}", trace.precompile); - println!("{margin} value: {value}"); - println!( - "{margin} calldata: {}", - hex::encode_prefixed(&*trace.calldata) - ); - - if trace.exit.is_error() { - println!("{margin} error: {}", trace.exit.get_reason()); - } - - println!( - "{margin} returnData: {}", - hex::encode_prefixed(&*trace.return_data) - ); -} - -fn print_call_trace(trace: &CallMessageTrace, depth: u32, env: Env) -> napi::Result<()> { - let margin = " ".repeat(depth as usize * MARGIN_SPACE); - - println!("{margin}Call trace"); - - if let Some(bytecode) = &trace.bytecode { - let contract = bytecode.contract.borrow(); - let file = contract.location.file(); - let file = file.borrow(); - - println!( - "{margin} calling contract: {}:{}", - file.source_name, contract.name - ); - } else { - println!( - "{margin} unrecognized contract code: {:?}", - hex::encode_prefixed(&*trace.code) - ); - println!( - "{margin} contract: {}", - hex::encode_prefixed(&*trace.address) - ); - } - - println!( - "{margin} value: {}", - U256::from_limbs_slice(&trace.value.words) - ); - println!( - "{margin} calldata: {}", - hex::encode_prefixed(&*trace.calldata) - ); - - if trace.exit.is_error() { - println!("{margin} error: {}", trace.exit.get_reason()); - } - - println!( - "{margin} returnData: {}", - hex::encode_prefixed(&*trace.return_data) - ); - - trace_steps(Either::A(trace), depth, env) -} - -fn print_create_trace(trace: &CreateMessageTrace, depth: u32, env: Env) -> napi::Result<()> { - let margin = " ".repeat(depth as usize * MARGIN_SPACE); - - println!("{margin}Create trace"); - - if let Some(bytecode) = &trace.bytecode { - let contract = bytecode.contract.borrow(); - - println!("{margin} deploying contract: {}", contract.name); - println!("{margin} code: {}", hex::encode_prefixed(&*trace.code)); - } else { - println!( - "{margin} unrecognized deployment code: {}", - hex::encode_prefixed(&*trace.code) - ); - } - - println!( - "{margin} value: {}", - U256::from_limbs_slice(&trace.value.words) - ); - - if let Some(Either::A(deployed_contract)) = &trace.deployed_contract { - println!( - "{margin} contract address: {}", - hex::encode_prefixed(deployed_contract) - ); - } - - if trace.exit.is_error() { - println!("{margin} error: {}", trace.exit.get_reason()); - // The return data is the deployed-bytecode if there was no error, so we don't - // show it - println!( - "{margin} returnData: {}", - hex::encode_prefixed(&*trace.return_data) - ); - } - - trace_steps(Either::B(trace), depth, env)?; - - Ok(()) -} - -fn trace_steps( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - depth: u32, - env: Env, -) -> napi::Result<()> { - let margin = " ".repeat(depth as usize * MARGIN_SPACE); - - println!("{margin} steps:"); - println!(); - - let (bytecode, steps) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - - for step in steps { - let step = match step { - Either4::A(step) => step, - trace @ (Either4::B(..) | Either4::C(..) | Either4::D(..)) => { - let trace = match trace { - Either4::A(..) => unreachable!(), - Either4::B(precompile) => Either3::A(precompile), - Either4::C(call) => Either3::B(call), - Either4::D(create) => Either3::C(create), - }; - - print_message_trace_inner(trace, depth + 1, env)?; - continue; - } - }; - - let pc = format!("{:>5}", format!("{:03}", step.pc)); - - if let Some(bytecode) = bytecode { - let inst = bytecode.get_instruction_napi(step.pc)?; - - let location = inst - .location - .as_ref() - .map(|inst_location| { - let inst_location = &inst_location; - let file = inst_location.file(); - let file = file.borrow(); - - let mut location_str = file.source_name.clone(); - - if let Some(func) = inst_location.get_containing_function() { - let file = func.location.file(); - let file = file.borrow(); - - let source_name = func - .contract_name - .as_ref() - .unwrap_or_else(|| &file.source_name); - - location_str += &format!(":{source_name}:{}", func.name); - } - location_str += - &format!(" - {}:{}", inst_location.offset, inst_location.length); - - napi::Result::Ok(location_str) - }) - .transpose()? - .unwrap_or_default(); - - if matches!(inst.opcode, OpCode::JUMP | OpCode::JUMPI) { - let jump = if inst.jump_type == JumpType::NotJump { - "".to_string() - } else { - format!("({})", inst.jump_type) - }; - - let entry = format!("{margin} {pc} {opcode} {jump}", opcode = inst.opcode); - - println!("{entry:<50}{location}"); - } else if inst.opcode.is_push() { - let entry = format!( - "{margin} {pc} {opcode} {push_data}", - opcode = inst.opcode, - push_data = inst - .push_data - .as_deref() - .map(hex::encode_prefixed) - .unwrap_or_default() - ); - - println!("{entry:<50}{location}"); - } else { - let entry = format!("{margin} {pc} {opcode}", opcode = inst.opcode); - - println!("{entry:<50}{location}"); - } - } else { - println!("{margin} {pc}"); - } - } - - Ok(()) -} - #[napi] fn print_stack_trace(trace: SolidityStackTrace) -> napi::Result<()> { let entry_values = trace diff --git a/crates/edr_napi/src/trace/error_inferrer.rs b/crates/edr_napi/src/trace/error_inferrer.rs deleted file mode 100644 index dbe74c075..000000000 --- a/crates/edr_napi/src/trace/error_inferrer.rs +++ /dev/null @@ -1,2255 +0,0 @@ -use std::{borrow::Cow, collections::HashSet}; - -use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; -use edr_evm::{hex, interpreter::OpCode}; -use edr_solidity::build_model::{ - Bytecode, ContractFunction, ContractFunctionType, ContractKind, Instruction, JumpType, - SourceLocation, -}; -use napi::{ - bindgen_prelude::{BigInt, Either24, Either3, Either4}, - Either, -}; -use semver::{Version, VersionReq}; - -use super::{ - exit::ExitCode, - message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep, PrecompileMessageTrace}, - model::ContractFunctionType as ContractFunctionTypeNapi, - return_data::ReturnData, - solidity_stack_trace::{ - CallFailedErrorStackTraceEntry, CallstackEntryStackTraceEntry, CustomErrorStackTraceEntry, - FallbackNotPayableErrorStackTraceEntry, InternalFunctionCallStackEntry, - InvalidParamsErrorStackTraceEntry, NonContractAccountCalledErrorStackTraceEntry, - PanicErrorStackTraceEntry, SolidityStackTrace, SolidityStackTraceEntry, - SolidityStackTraceEntryExt, SourceReference, UnmappedSolc063RevertErrorStackTraceEntry, - }, -}; -use crate::trace::solidity_stack_trace::{ - ContractCallRunOutOfGasError, ContractTooLargeErrorStackTraceEntry, - DirectLibraryCallErrorStackTraceEntry, FallbackNotPayableAndNoReceiveErrorStackTraceEntry, - FunctionNotPayableErrorStackTraceEntry, MissingFallbackOrReceiveErrorStackTraceEntry, - OtherExecutionErrorStackTraceEntry, ReturndataSizeErrorStackTraceEntry, - RevertErrorStackTraceEntry, StackTraceEntryTypeConst, - UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry, CONSTRUCTOR_FUNCTION_NAME, - FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, -}; - -/// Specifies whether a heuristic was applied and modified the stack trace. -/// -/// Think of it as happy [`Result`] - the [`Heuristic::Hit`] should be -/// propagated to the caller. -#[must_use] -pub enum Heuristic { - /// The heuristic was applied and modified the stack trace. - Hit(SolidityStackTrace), - /// The heuristic did not apply; the stack trace is unchanged. - Miss(SolidityStackTrace), -} - -const FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION: Version = Version::new(0, 5, 9); -const FIRST_SOLC_VERSION_RECEIVE_FUNCTION: Version = Version::new(0, 6, 0); -const FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS: &str = "0.6.3"; - -/// Port of `SubmessageData` from Hardhat to Rust -// However, it borrows the traces (instead of copying them) because the traces -// do not implement `Clone` due to inner references -pub struct SubmessageDataRef<'a> { - pub message_trace: - Either3<&'a PrecompileMessageTrace, &'a CallMessageTrace, &'a CreateMessageTrace>, - pub stacktrace: SolidityStackTrace, - pub step_index: u32, -} -#[derive(Default)] -pub struct ErrorInferrer; - -impl ErrorInferrer { - pub fn infer_before_tracing_call_message( - trace: &CallMessageTrace, - ) -> napi::Result> { - if Self::is_direct_library_call(trace)? { - return Ok(Some(Self::get_direct_library_call_error_stack_trace( - trace, - )?)); - } - - let bytecode = trace - .bytecode - .as_ref() - .expect("The TS code type-checks this to always have bytecode"); - let contract = bytecode.contract.borrow(); - - let called_function = contract - .get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); - - if let Some(called_function) = called_function { - if Self::is_function_not_payable_error(trace, called_function)? { - return Ok(Some(vec![FunctionNotPayableErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_function_start_source_reference( - Either::A(trace), - called_function, - )?, - value: trace.value.clone(), - } - .into()])); - } - } - - let called_function = called_function.map(AsRef::as_ref); - - if Self::is_missing_function_and_fallback_error(trace, called_function)? { - let source_reference = - Self::get_contract_start_without_function_source_reference(Either::A(trace))?; - - if Self::empty_calldata_and_no_receive(trace)? { - return Ok(Some(vec![MissingFallbackOrReceiveErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - } - .into()])); - } - - return Ok(Some(vec![ - UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - } - .into(), - ])); - } - - if Self::is_fallback_not_payable_error(trace, called_function)? { - let source_reference = Self::get_fallback_start_source_reference(trace)?; - - if Self::empty_calldata_and_no_receive(trace)? { - return Ok(Some(vec![ - FallbackNotPayableAndNoReceiveErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - value: trace.value.clone(), - } - .into(), - ])); - } - - return Ok(Some(vec![FallbackNotPayableErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - value: trace.value.clone(), - } - .into()])); - } - - Ok(None) - } - - pub fn infer_before_tracing_create_message( - trace: &CreateMessageTrace, - ) -> napi::Result> { - if Self::is_constructor_not_payable_error(trace)? { - return Ok(Some(vec![FunctionNotPayableErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_constructor_start_source_reference(trace)?, - value: trace.value.clone(), - } - .into()])); - } - - if Self::is_constructor_invalid_arguments_error(trace)? { - return Ok(Some(vec![InvalidParamsErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_constructor_start_source_reference(trace)?, - } - .into()])); - } - - Ok(None) - } - - pub fn infer_after_tracing( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - stacktrace: SolidityStackTrace, - function_jumpdests: &[&Instruction], - jumped_into_function: bool, - last_submessage_data: Option>, - ) -> napi::Result { - /// Convenience macro to early return the result if a heuristic hits. - macro_rules! return_if_hit { - ($heuristic: expr) => { - match $heuristic { - Heuristic::Hit(stacktrace) => return Ok(stacktrace), - Heuristic::Miss(stacktrace) => stacktrace, - } - }; - } - - let result = Self::check_last_submessage(trace, stacktrace, last_submessage_data)?; - let stacktrace = return_if_hit!(result); - - let result = Self::check_failed_last_call(trace, stacktrace)?; - let stacktrace = return_if_hit!(result); - - let result = Self::check_last_instruction( - trace, - stacktrace, - function_jumpdests, - jumped_into_function, - )?; - let stacktrace = return_if_hit!(result); - - let result = Self::check_non_contract_called(trace, stacktrace)?; - let stacktrace = return_if_hit!(result); - - let result = Self::check_solidity_0_6_3_unmapped_revert(trace, stacktrace)?; - let stacktrace = return_if_hit!(result); - - if let Some(result) = Self::check_contract_too_large(trace)? { - return Ok(result); - } - - let stacktrace = Self::other_execution_error_stacktrace(trace, stacktrace)?; - Ok(stacktrace) - } - - pub fn filter_redundant_frames( - stacktrace: SolidityStackTrace, - ) -> napi::Result { - // To work around the borrow checker, we'll collect the indices of the frames we - // want to keep. We can't clone the frames, because some of them contain - // non-Clone `ClassInstance`s` - let retained_indices: HashSet<_> = stacktrace - .iter() - .enumerate() - .filter(|(idx, frame)| { - let next_frame = stacktrace.get(idx + 1); - let next_next_frame = stacktrace.get(idx + 2); - - let Some(next_frame) = next_frame else { - return true; - }; - - // we can only filter frames if we know their sourceReference - // and the one from the next frame - let (Some(frame_source), Some(next_frame_source)) = - (frame.source_reference(), next_frame.source_reference()) - else { - return true; - }; - - // look TWO frames ahead to determine if this is a specific occurrence of - // a redundant CALLSTACK_ENTRY frame observed when using Solidity 0.8.5: - match (&frame, next_next_frame) { - ( - Either24::A(CallstackEntryStackTraceEntry { - source_reference, .. - }), - Some(Either24::N(ReturndataSizeErrorStackTraceEntry { - source_reference: next_next_source_reference, - .. - })), - ) if source_reference.range == next_next_source_reference.range - && source_reference.line == next_next_source_reference.line => - { - return false; - } - _ => {} - } - - if frame_source.function.as_deref() == Some("constructor") - && next_frame_source.function.as_deref() != Some("constructor") - { - return true; - } - - // this is probably a recursive call - if *idx > 0 - && frame.type_() == next_frame.type_() - && frame_source.range == next_frame_source.range - && frame_source.line == next_frame_source.line - { - return true; - } - - if frame_source.range[0] <= next_frame_source.range[0] - && frame_source.range[1] >= next_frame_source.range[1] - { - return false; - } - - true - }) - .map(|(idx, _)| idx) - .collect(); - - Ok(stacktrace - .into_iter() - .enumerate() - .filter(|(idx, _)| retained_indices.contains(idx)) - .map(|(_, frame)| frame) - .collect()) - } - - // Heuristics - - fn check_contract_too_large( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result> { - Ok(match trace { - Either::B(create @ CreateMessageTrace { .. }) - if create.exit.kind() == ExitCode::CODESIZE_EXCEEDS_MAXIMUM => - { - Some(vec![ContractTooLargeErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Some(Self::get_constructor_start_source_reference(create)?), - } - .into()]) - } - - _ => None, - }) - } - /// Check if the last call/create that was done failed. - fn check_failed_last_call( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - stacktrace: SolidityStackTrace, - ) -> napi::Result { - let (bytecode, steps) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - if steps.is_empty() { - return Ok(Heuristic::Miss(stacktrace)); - } - - for step_index in (0..steps.len() - 1).rev() { - let (step, next_step) = match &steps[step_index..][..2] { - &[Either4::A(ref step), ref next_step] => (step, next_step), - _ => return Ok(Heuristic::Miss(stacktrace)), - }; - - let inst = bytecode.get_instruction_napi(step.pc)?; - - if let (OpCode::CALL | OpCode::CREATE, Either4::A(EvmStep { .. })) = - (inst.opcode, next_step) - { - if Self::is_call_failed_error(trace, step_index as u32, inst)? { - let mut inferred_stacktrace = stacktrace.clone(); - inferred_stacktrace.push( - Self::call_instruction_to_call_failed_to_execute_stack_trace_entry( - bytecode, inst, - )? - .into(), - ); - - return Ok(Heuristic::Hit(Self::fix_initial_modifier( - trace, - inferred_stacktrace, - )?)); - } - } - } - - Ok(Heuristic::Miss(stacktrace)) - } - - /// Check if the trace reverted with a panic error. - fn check_panic( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - mut stacktrace: SolidityStackTrace, - last_instruction: &Instruction, - ) -> napi::Result { - let return_data = ReturnData::new( - match &trace { - Either::A(call) => &call.return_data, - Either::B(create) => &create.return_data, - } - .clone(), - ); - - if !return_data.is_panic_return_data() { - return Ok(Heuristic::Miss(stacktrace)); - } - - // If the last frame is an internal function, it means that the trace - // jumped there to return the panic. If that's the case, we remove that - // frame. - if let Some(Either24::W(InternalFunctionCallStackEntry { .. })) = stacktrace.last() { - stacktrace.pop(); - } - - // if the error comes from a call to a zero-initialized function, - // we remove the last frame, which represents the call, to avoid - // having duplicated frames - let error_code = return_data.decode_panic()?; - let (panic_error_code, lossless) = error_code.get_i64(); - if let (0x51, false) = (panic_error_code, lossless) { - stacktrace.pop(); - } - - stacktrace.push( - Self::instruction_within_function_to_panic_stack_trace_entry( - trace, - last_instruction, - error_code, - )? - .into(), - ); - - Self::fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit) - } - - fn check_custom_errors( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - stacktrace: SolidityStackTrace, - last_instruction: &Instruction, - ) -> napi::Result { - let (bytecode, return_data) = match &trace { - Either::A(call) => (&call.bytecode, &call.return_data), - Either::B(create) => (&create.bytecode, &create.return_data), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - let return_data = ReturnData::new(return_data.clone()); - - if return_data.is_empty() || return_data.is_error_return_data() { - // if there is no return data, or if it's a Error(string), - // then it can't be a custom error - return Ok(Heuristic::Miss(stacktrace)); - } - - let raw_return_data = hex::encode(&*return_data.value); - let mut error_message = format!( - "reverted with an unrecognized custom error (return data: 0x{raw_return_data})", - ); - - for custom_error in &contract.custom_errors { - if return_data.matches_selector(custom_error.selector) { - // if the return data matches a custom error in the called contract, - // we format the message using the returnData and the custom error instance - let decoded = custom_error - .decode_error_data(&return_data.value) - .map_err(|e| { - napi::Error::from_reason(format!("Error decoding custom error: {e}")) - })?; - - let params = decoded - .body - .iter() - .map(format_dyn_sol_value) - .collect::>(); - - error_message = format!( - "reverted with custom error '{name}({params})'", - name = custom_error.name, - params = params.join(", ") - ); - - break; - } - } - - let mut stacktrace = stacktrace; - stacktrace.push( - Self::instruction_within_function_to_custom_error_stack_trace_entry( - trace, - last_instruction, - error_message, - )? - .into(), - ); - - Self::fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit) - } - - fn check_solidity_0_6_3_unmapped_revert( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - mut stacktrace: SolidityStackTrace, - ) -> napi::Result { - if Self::solidity_0_6_3_maybe_unmapped_revert(trace)? { - let revert_frame = - Self::solidity_0_6_3_get_frame_for_unmapped_revert_within_function(trace)?; - - if let Some(revert_frame) = revert_frame { - stacktrace.push(revert_frame.into()); - - return Ok(Heuristic::Hit(stacktrace)); - } - - return Ok(Heuristic::Hit(stacktrace)); - } - - Ok(Heuristic::Miss(stacktrace)) - } - - fn check_non_contract_called( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - mut stacktrace: SolidityStackTrace, - ) -> napi::Result { - if Self::is_called_non_contract_account_error(trace)? { - let source_reference = Self::get_last_source_reference(trace)?; - - // We are sure this is not undefined because there was at least a call - // instruction - let source_reference = - source_reference.expect("Expected source reference to be defined"); - - let non_contract_called_frame = NonContractAccountCalledErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - }; - - stacktrace.push(non_contract_called_frame.into()); - - Ok(Heuristic::Hit(stacktrace)) - } else { - Ok(Heuristic::Miss(stacktrace)) - } - } - - /// Check if the last submessage can be used to generate the stack trace. - fn check_last_submessage( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - stacktrace: SolidityStackTrace, - last_submessage_data: Option>, - ) -> napi::Result { - let (bytecode, steps) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - let Some(last_submessage_data) = last_submessage_data else { - return Ok(Heuristic::Miss(stacktrace)); - }; - - let mut inferred_stacktrace = Cow::from(&stacktrace); - - // get the instruction before the submessage and add it to the stack trace - let call_step = match steps.get(last_submessage_data.step_index as usize - 1) { - Some(Either4::A(call_step)) => call_step, - _ => panic!("This should not happen: MessageTrace should be preceded by a EVM step"), - }; - - let call_inst = bytecode.get_instruction_napi(call_step.pc)?; - let call_stack_frame = instruction_to_callstack_stack_trace_entry(bytecode, call_inst)?; - - let (call_stack_frame_source_reference, call_stack_frame) = match call_stack_frame { - Either::A(frame) => (frame.source_reference.clone(), frame.into()), - Either::B(frame) => (frame.source_reference.clone(), frame.into()), - }; - - let last_message_failed = match last_submessage_data.message_trace { - Either3::A(precompile) => precompile.exit.is_error(), - Either3::B(call) => call.exit.is_error(), - Either3::C(create) => create.exit.is_error(), - }; - if last_message_failed { - // add the call/create that generated the message to the stack trace - let inferred_stacktrace = inferred_stacktrace.to_mut(); - inferred_stacktrace.push(call_stack_frame); - - if Self::is_subtrace_error_propagated(trace, last_submessage_data.step_index)? - || Self::is_proxy_error_propagated(trace, last_submessage_data.step_index)? - { - inferred_stacktrace.extend(last_submessage_data.stacktrace); - - if Self::is_contract_call_run_out_of_gas_error( - trace, - last_submessage_data.step_index, - )? { - let last_frame = match inferred_stacktrace.pop() { - Some(frame) => frame, - _ => panic!("Expected inferred stack trace to have at least one frame"), - }; - - inferred_stacktrace.push( - ContractCallRunOutOfGasError { - type_: StackTraceEntryTypeConst, - source_reference: last_frame.source_reference().cloned(), - } - .into(), - ); - } - - return Self::fix_initial_modifier(trace, inferred_stacktrace.to_owned()) - .map(Heuristic::Hit); - } - } else { - let is_return_data_size_error = - Self::fails_right_after_call(trace, last_submessage_data.step_index)?; - if is_return_data_size_error { - inferred_stacktrace.to_mut().push( - ReturndataSizeErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: call_stack_frame_source_reference, - } - .into(), - ); - - return Self::fix_initial_modifier(trace, inferred_stacktrace.into_owned()) - .map(Heuristic::Hit); - } - } - - Ok(Heuristic::Miss(stacktrace)) - } - - /// Check if the execution stopped with a revert or an invalid opcode. - fn check_revert_or_invalid_opcode( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - stacktrace: SolidityStackTrace, - last_instruction: &Instruction, - function_jumpdests: &[&Instruction], - jumped_into_function: bool, - ) -> napi::Result { - match last_instruction.opcode { - OpCode::REVERT | OpCode::INVALID => {} - _ => return Ok(Heuristic::Miss(stacktrace)), - } - - let (bytecode, return_data) = match &trace { - Either::A(call) => (&call.bytecode, &call.return_data), - Either::B(create) => (&create.bytecode, &create.return_data), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - let mut inferred_stacktrace = stacktrace.clone(); - - if let Some(location) = &last_instruction.location { - if jumped_into_function || matches!(trace, Either::B(CreateMessageTrace { .. })) { - // There should always be a function here, but that's not the case with - // optimizations. - // - // If this is a create trace, we already checked args and nonpayable failures - // before calling this function. - // - // If it's a call trace, we already jumped into a function. But optimizations - // can happen. - let failing_function = location.get_containing_function(); - - // If the failure is in a modifier we add an entry with the function/constructor - match failing_function { - Some(func) if func.r#type == ContractFunctionType::Modifier => { - let frame = - Self::get_entry_before_failure_in_modifier(trace, function_jumpdests)?; - - inferred_stacktrace.push(match frame { - Either::A(frame) => frame.into(), - Either::B(frame) => frame.into(), - }); - } - _ => {} - } - } - } - - let panic_stacktrace = Self::check_panic(trace, inferred_stacktrace, last_instruction)?; - let inferred_stacktrace = match panic_stacktrace { - hit @ Heuristic::Hit(..) => return Ok(hit), - Heuristic::Miss(stacktrace) => stacktrace, - }; - - let custom_error_stacktrace = - Self::check_custom_errors(trace, inferred_stacktrace, last_instruction)?; - let mut inferred_stacktrace = match custom_error_stacktrace { - hit @ Heuristic::Hit(..) => return Ok(hit), - Heuristic::Miss(stacktrace) => stacktrace, - }; - - if let Some(location) = &last_instruction.location { - if jumped_into_function || matches!(trace, Either::B(CreateMessageTrace { .. })) { - let failing_function = location.get_containing_function(); - - if failing_function.is_some() { - let frame = Self::instruction_within_function_to_revert_stack_trace_entry( - trace, - last_instruction, - )?; - - inferred_stacktrace.push(frame.into()); - } else { - let is_invalid_opcode_error = last_instruction.opcode == OpCode::INVALID; - - match &trace { - Either::A(CallMessageTrace { calldata, .. }) => { - let contract = bytecode.contract.borrow(); - - // This is here because of the optimizations - let function_from_selector = contract.get_function_from_selector( - calldata.get(..4).unwrap_or(&calldata[..]), - ); - - // in general this shouldn't happen, but it does when viaIR is enabled, - // "optimizerSteps": "u" is used, and the called function is fallback or - // receive - let Some(function) = function_from_selector else { - return Ok(Heuristic::Miss(inferred_stacktrace)); - }; - - let frame = RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_function_start_source_reference( - trace, function, - )?, - return_data: return_data.clone(), - is_invalid_opcode_error, - }; - - inferred_stacktrace.push(frame.into()); - } - Either::B(trace @ CreateMessageTrace { .. }) => { - // This is here because of the optimizations - let frame = RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_constructor_start_source_reference( - trace, - )?, - return_data: return_data.clone(), - is_invalid_opcode_error, - }; - - inferred_stacktrace.push(frame.into()); - } - } - } - - return Self::fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit); - } - } - - // If the revert instruction is not mapped but there is return data, - // we add the frame anyway, sith the best sourceReference we can get - if last_instruction.location.is_none() && !return_data.is_empty() { - let revert_frame = RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_contract_start_without_function_source_reference( - trace, - )?, - return_data: return_data.clone(), - is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID, - }; - - inferred_stacktrace.push(revert_frame.into()); - - return Self::fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit); - } - - Ok(Heuristic::Miss(stacktrace)) - } - - fn check_last_instruction( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - stacktrace: SolidityStackTrace, - function_jumpdests: &[&Instruction], - jumped_into_function: bool, - ) -> napi::Result { - let (bytecode, steps) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - if steps.is_empty() { - return Ok(Heuristic::Miss(stacktrace)); - } - - let last_step = match steps.last() { - Some(Either4::A(step)) => step, - _ => panic!("This should not happen: MessageTrace ends with a subtrace"), - }; - - let last_instruction = bytecode.get_instruction_napi(last_step.pc)?; - - let revert_or_invalid_stacktrace = Self::check_revert_or_invalid_opcode( - trace, - stacktrace, - last_instruction, - function_jumpdests, - jumped_into_function, - )?; - let stacktrace = match revert_or_invalid_stacktrace { - hit @ Heuristic::Hit(..) => return Ok(hit), - Heuristic::Miss(stacktrace) => stacktrace, - }; - - let (Either::A(trace @ CallMessageTrace { ref calldata, .. }), false) = - (&trace, jumped_into_function) - else { - return Ok(Heuristic::Miss(stacktrace)); - }; - - if Self::has_failed_inside_the_fallback_function(trace)? - || Self::has_failed_inside_the_receive_function(trace)? - { - let frame = Self::instruction_within_function_to_revert_stack_trace_entry( - Either::A(trace), - last_instruction, - )?; - - return Ok(Heuristic::Hit(vec![frame.into()])); - } - - // Sometimes we do fail inside of a function but there's no jump into - if let Some(location) = &last_instruction.location { - let failing_function = location.get_containing_function(); - - if let Some(failing_function) = failing_function { - let frame = RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_function_start_source_reference( - Either::A(trace), - &failing_function, - )?, - return_data: trace.return_data.clone(), - is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID, - }; - - return Ok(Heuristic::Hit(vec![frame.into()])); - } - } - - let contract = bytecode.contract.borrow(); - - let selector = calldata.get(..4).unwrap_or(&calldata[..]); - let calldata = &calldata.get(4..).unwrap_or(&[]); - - let called_function = contract.get_function_from_selector(selector); - - if let Some(called_function) = called_function { - let abi = alloy_json_abi::Function::try_from(&**called_function).map_err(|e| { - napi::Error::from_reason(format!("Error converting to alloy ABI: {e}")) - })?; - - let is_valid_calldata = match &called_function.param_types { - Some(_) => abi.abi_decode_input(calldata, true).is_ok(), - // if we don't know the param types, we just assume that the call is valid - None => true, - }; - - if !is_valid_calldata { - let frame = InvalidParamsErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_function_start_source_reference( - Either::A(trace), - called_function, - )?, - }; - - return Ok(Heuristic::Hit(vec![frame.into()])); - } - } - - if Self::solidity_0_6_3_maybe_unmapped_revert(Either::A(trace))? { - let revert_frame = - Self::solidity_0_6_3_get_frame_for_unmapped_revert_before_function(trace)?; - - if let Some(revert_frame) = revert_frame { - return Ok(Heuristic::Hit(vec![revert_frame.into()])); - } - } - - let frame = Self::get_other_error_before_called_function_stack_trace_entry(trace)?; - - Ok(Heuristic::Hit(vec![frame.into()])) - } - - // Helpers - - fn fix_initial_modifier( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - mut stacktrace: SolidityStackTrace, - ) -> napi::Result { - if let Some(Either24::A(CallstackEntryStackTraceEntry { - function_type: ContractFunctionTypeNapi::MODIFIER, - .. - })) = stacktrace.first() - { - let entry_before_initial_modifier = - Self::get_entry_before_initial_modifier_callstack_entry(trace)?; - - stacktrace.insert(0, entry_before_initial_modifier); - } - - Ok(stacktrace) - } - - fn get_entry_before_initial_modifier_callstack_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let trace = match trace { - Either::B(create) => { - return Ok(CallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_constructor_start_source_reference(create)?, - function_type: ContractFunctionType::Constructor.into(), - } - .into()) - } - Either::A(call) => call, - }; - - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - let called_function = contract - .get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); - - let source_reference = match called_function { - Some(called_function) => { - Self::get_function_start_source_reference(Either::A(trace), called_function)? - } - None => Self::get_fallback_start_source_reference(trace)?, - }; - - let function_type = match called_function { - Some(_) => ContractFunctionType::Function, - None => ContractFunctionType::Fallback, - }; - - Ok(CallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - function_type: function_type.into(), - } - .into()) - } - - fn call_instruction_to_call_failed_to_execute_stack_trace_entry( - bytecode: &Bytecode, - call_inst: &Instruction, - ) -> napi::Result { - let location = call_inst.location.as_deref(); - - let source_reference = source_location_to_source_reference(bytecode, location)?; - let source_reference = source_reference.expect("Expected source reference to be defined"); - - // Calls only happen within functions - Ok(CallFailedErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - }) - } - - fn get_entry_before_failure_in_modifier( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - function_jumpdests: &[&Instruction], - ) -> napi::Result> { - let bytecode = match &trace { - Either::A(call) => &call.bytecode, - Either::B(create) => &create.bytecode, - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - // If there's a jumpdest, this modifier belongs to the last function that it - // represents - if let Some(last_jumpdest) = function_jumpdests.last() { - let entry = instruction_to_callstack_stack_trace_entry(bytecode, last_jumpdest)?; - - return Ok(entry); - } - - // This function is only called after we jumped into the initial function in - // call traces, so there should always be at least a function jumpdest. - let trace = match trace { - Either::A(_call) => return Err( - napi::Error::new( - napi::Status::GenericFailure, - "This shouldn't happen: a call trace has no functionJumpdest but has already jumped into a function" - )), - Either::B(create) => create, - }; - - // If there's no jump dest, we point to the constructor. - Ok(Either::A(CallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_constructor_start_source_reference(trace)?, - function_type: ContractFunctionType::Constructor.into(), - })) - } - - fn fails_right_after_call( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - call_subtrace_step_index: u32, - ) -> napi::Result { - let (bytecode, steps) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - let Some(Either4::A(last_step)) = steps.last() else { - return Ok(false); - }; - - let last_inst = bytecode.get_instruction_napi(last_step.pc)?; - if last_inst.opcode != OpCode::REVERT { - return Ok(false); - } - - let call_opcode_step = steps.get(call_subtrace_step_index as usize - 1); - let call_opcode_step = match call_opcode_step { - Some(Either4::A(step)) => step, - _ => panic!("JS code asserts this is always an EvmStep"), - }; - let call_inst = bytecode.get_instruction_napi(call_opcode_step.pc)?; - - // Calls are always made from within functions - let call_inst_location = call_inst - .location - .as_ref() - .expect("Expected call instruction location to be defined"); - - Self::is_last_location(trace, call_subtrace_step_index + 1, call_inst_location) - } - - fn is_call_failed_error( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - inst_index: u32, - call_instruction: &Instruction, - ) -> napi::Result { - let call_location = match &call_instruction.location { - Some(location) => location, - None => panic!("Expected call location to be defined"), - }; - - Self::is_last_location(trace, inst_index, call_location) - } - - fn is_last_location( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - from_step: u32, - location: &SourceLocation, - ) -> napi::Result { - let (bytecode, steps) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - for step in steps.iter().skip(from_step as usize) { - let step = match step { - Either4::A(step) => step, - _ => return Ok(false), - }; - - let step_inst = bytecode.get_instruction_napi(step.pc)?; - - if let Some(step_inst_location) = &step_inst.location { - if **step_inst_location != *location { - return Ok(false); - } - } - } - - Ok(true) - } - - fn is_subtrace_error_propagated( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - call_subtrace_step_index: u32, - ) -> napi::Result { - let (return_data, exit, steps) = match &trace { - Either::A(call) => (&call.return_data, call.exit.kind(), &call.steps), - Either::B(create) => (&create.return_data, create.exit.kind(), &create.steps), - }; - - let (call_return_data, call_exit) = match steps.get(call_subtrace_step_index as usize) { - None | Some(Either4::A(_)) => panic!("Expected call to be a message trace"), - Some(Either4::B(precompile)) => (&precompile.return_data, precompile.exit.kind()), - Some(Either4::C(call)) => (&call.return_data, call.exit.kind()), - Some(Either4::D(create)) => (&create.return_data, create.exit.kind()), - }; - - if return_data.as_ref() != call_return_data.as_ref() { - return Ok(false); - } - - if exit == ExitCode::OUT_OF_GAS && call_exit == ExitCode::OUT_OF_GAS { - return Ok(true); - } - - // If the return data is not empty, and it's still the same, we assume it - // is being propagated - if return_data.len() > 0 { - return Ok(true); - } - - Self::fails_right_after_call(trace, call_subtrace_step_index) - } - - fn is_contract_call_run_out_of_gas_error( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - call_step_index: u32, - ) -> napi::Result { - let (steps, return_data, exit_code) = match &trace { - Either::A(call) => (&call.steps, &call.return_data, call.exit.kind()), - Either::B(create) => (&create.steps, &create.return_data, create.exit.kind()), - }; - - if return_data.len() > 0 { - return Ok(false); - } - - if exit_code != ExitCode::REVERT { - return Ok(false); - } - - let call_exit = match steps.get(call_step_index as usize) { - None | Some(Either4::A(_)) => panic!("Expected call to be a message trace"), - Some(Either4::B(precompile)) => precompile.exit.kind(), - Some(Either4::C(call)) => call.exit.kind(), - Some(Either4::D(create)) => create.exit.kind(), - }; - - if call_exit != ExitCode::OUT_OF_GAS { - return Ok(false); - } - - Self::fails_right_after_call(trace, call_step_index) - } - - fn is_proxy_error_propagated( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - call_subtrace_step_index: u32, - ) -> napi::Result { - let trace = match &trace { - Either::A(call) => call, - Either::B(_) => return Ok(false), - }; - - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - - let call_step = match trace.steps.get(call_subtrace_step_index as usize - 1) { - Some(Either4::A(step)) => step, - _ => return Ok(false), - }; - - let call_inst = bytecode.get_instruction_napi(call_step.pc)?; - - if call_inst.opcode != OpCode::DELEGATECALL { - return Ok(false); - } - - let subtrace = match trace.steps.get(call_subtrace_step_index as usize) { - None | Some(Either4::A(_) | Either4::B(_)) => return Ok(false), - Some(Either4::C(call)) => Either::A(call), - Some(Either4::D(create)) => Either::B(create), - }; - - let (subtrace_bytecode, subtrace_return_data) = match &subtrace { - Either::A(call) => (&call.bytecode, &call.return_data), - Either::B(create) => (&create.bytecode, &create.return_data), - }; - let subtrace_bytecode = match subtrace_bytecode { - Some(bytecode) => bytecode, - // If we can't recognize the implementation we'd better don't consider it as such - None => return Ok(false), - }; - - if subtrace_bytecode.contract.borrow().r#type == ContractKind::Library { - return Ok(false); - } - - if trace.return_data.as_ref() != subtrace_return_data.as_ref() { - return Ok(false); - } - - for step in trace - .steps - .iter() - .skip(call_subtrace_step_index as usize + 1) - { - let step = match step { - Either4::A(step) => step, - _ => return Ok(false), - }; - - let inst = bytecode.get_instruction_napi(step.pc)?; - - // All the remaining locations should be valid, as they are part of the inline - // asm - if inst.location.is_none() { - return Ok(false); - } - - if matches!( - inst.jump_type, - JumpType::IntoFunction | JumpType::OutofFunction - ) { - return Ok(false); - } - } - - let last_step = match trace.steps.last() { - Some(Either4::A(step)) => step, - _ => panic!("Expected last step to be an EvmStep"), - }; - let last_inst = bytecode.get_instruction_napi(last_step.pc)?; - - Ok(last_inst.opcode == OpCode::REVERT) - } - - fn other_execution_error_stacktrace( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - mut stacktrace: SolidityStackTrace, - ) -> napi::Result { - let other_execution_error_frame = OtherExecutionErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Self::get_last_source_reference(trace)?, - }; - - stacktrace.push(other_execution_error_frame.into()); - Ok(stacktrace) - } - - fn is_direct_library_call(trace: &CallMessageTrace) -> napi::Result { - let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract; - let contract = contract.borrow(); - - Ok(trace.depth == 0 && contract.r#type == ContractKind::Library) - } - - fn get_direct_library_call_error_stack_trace( - trace: &CallMessageTrace, - ) -> napi::Result { - let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract; - let contract = contract.borrow(); - - let func = contract - .get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); - - let source_reference = match func { - Some(func) => Self::get_function_start_source_reference(Either::A(trace), func)?, - None => Self::get_contract_start_without_function_source_reference(Either::A(trace))?, - }; - - Ok(vec![DirectLibraryCallErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - } - .into()]) - } - - fn get_function_start_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - func: &ContractFunction, - ) -> napi::Result { - let bytecode = match &trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - }; - - let contract = &bytecode.as_ref().expect("JS code asserts").contract; - let contract = contract.borrow(); - - let file = func.location.file(); - let file = file.borrow(); - - let location = &func.location; - - Ok(SourceReference { - source_name: file.source_name.clone(), - source_content: file.content.clone(), - contract: Some(contract.name.clone()), - - function: Some(func.name.clone()), - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - }) - } - - fn is_missing_function_and_fallback_error( - trace: &CallMessageTrace, - called_function: Option<&ContractFunction>, - ) -> napi::Result { - // This error doesn't return data - if trace.return_data.len() > 0 { - return Ok(false); - } - - // the called function exists in the contract - if called_function.is_some() { - return Ok(false); - } - - let bytecode = trace - .bytecode - .as_ref() - .expect("The TS code type-checks this to always have bytecode"); - let contract = bytecode.contract.borrow(); - - // there's a receive function and no calldata - if trace.calldata.len() == 0 && contract.receive.is_some() { - return Ok(false); - } - - Ok(contract.fallback.is_none()) - } - - fn empty_calldata_and_no_receive(trace: &CallMessageTrace) -> napi::Result { - let bytecode = trace - .bytecode - .as_ref() - .expect("The TS code type-checks this to always have bytecode"); - let contract = bytecode.contract.borrow(); - - let version = - Version::parse(&bytecode.compiler_version).expect("Failed to parse SemVer version"); - - // this only makes sense when receive functions are available - if version < FIRST_SOLC_VERSION_RECEIVE_FUNCTION { - return Ok(false); - } - - Ok(trace.calldata.is_empty() && contract.receive.is_none()) - } - - fn is_fallback_not_payable_error( - trace: &CallMessageTrace, - called_function: Option<&ContractFunction>, - ) -> napi::Result { - // This error doesn't return data - if !trace.return_data.is_empty() { - return Ok(false); - } - - let (neg, value, _) = trace.value.get_u64(); - if neg || value == 0 { - return Ok(false); - } - - // the called function exists in the contract - if called_function.is_some() { - return Ok(false); - } - - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - match &contract.fallback { - Some(fallback) => Ok(fallback.is_payable != Some(true)), - None => Ok(false), - } - } - - fn get_fallback_start_source_reference( - trace: &CallMessageTrace, - ) -> napi::Result { - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - let func = match &contract.fallback { - Some(func) => func, - None => panic!("This shouldn't happen: trying to get fallback source reference from a contract without fallback"), - }; - - let location = &func.location; - let file = location.file(); - let file = file.borrow(); - - Ok(SourceReference { - source_name: file.source_name.clone(), - source_content: file.content.clone(), - contract: Some(contract.name.clone()), - function: Some(FALLBACK_FUNCTION_NAME.to_string()), - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - }) - } - - fn is_constructor_not_payable_error(trace: &CreateMessageTrace) -> napi::Result { - // This error doesn't return data - if !trace.return_data.is_empty() { - return Ok(false); - } - - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - // This function is only matters with contracts that have constructors defined. - // The ones that don't are abstract contracts, or their constructor - // doesn't take any argument. - let constructor = match &contract.constructor { - Some(constructor) => constructor, - None => return Ok(false), - }; - - let (neg, value, _) = trace.value.get_u64(); - if neg || value == 0 { - return Ok(false); - } - - Ok(constructor.is_payable != Some(true)) - } - - /// Returns a source reference pointing to the constructor if it exists, or - /// to the contract otherwise. - fn get_constructor_start_source_reference( - trace: &CreateMessageTrace, - ) -> napi::Result { - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - let contract_location = &contract.location; - - let line = match &contract.constructor { - Some(constructor) => constructor.location.get_starting_line_number(), - None => contract_location.get_starting_line_number(), - }; - - let file = contract_location.file(); - let file = file.borrow(); - - Ok(SourceReference { - source_name: file.source_name.clone(), - source_content: file.content.clone(), - contract: Some(contract.name.clone()), - function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), - line, - range: [ - contract_location.offset, - contract_location.offset + contract_location.length, - ] - .to_vec(), - }) - } - - fn is_constructor_invalid_arguments_error(trace: &CreateMessageTrace) -> napi::Result { - if trace.return_data.len() > 0 { - return Ok(false); - } - - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - // This function is only matters with contracts that have constructors defined. - // The ones that don't are abstract contracts, or their constructor - // doesn't take any argument. - let Some(constructor) = &contract.constructor else { - return Ok(false); - }; - - let Ok(version) = Version::parse(&bytecode.compiler_version) else { - return Ok(false); - }; - if version < FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION { - return Ok(false); - } - - let last_step = trace.steps.last(); - let Some(Either4::A(last_step)) = last_step else { - return Ok(false); - }; - - let last_inst = bytecode.get_instruction_napi(last_step.pc)?; - - if last_inst.opcode != OpCode::REVERT || last_inst.location.is_some() { - return Ok(false); - } - - let mut has_read_deployment_code_size = false; - for step in &trace.steps { - let step = match step { - Either4::A(step) => step, - _ => return Ok(false), - }; - - let inst = bytecode.get_instruction_napi(step.pc)?; - - if let Some(inst_location) = &inst.location { - if contract.location != *inst_location && constructor.location != *inst_location { - return Ok(false); - } - } - - if inst.opcode == OpCode::CODESIZE { - has_read_deployment_code_size = true; - } - } - - Ok(has_read_deployment_code_size) - } - - fn get_contract_start_without_function_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let bytecode = match &trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - }; - - let contract = &bytecode.as_ref().expect("JS code asserts").contract; - - let contract = contract.borrow(); - - let location = &contract.location; - let file = location.file(); - let file = file.borrow(); - - Ok(SourceReference { - source_name: file.source_name.clone(), - source_content: file.content.clone(), - contract: Some(contract.name.clone()), - - function: None, - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - }) - } - - fn is_function_not_payable_error( - trace: &CallMessageTrace, - called_function: &ContractFunction, - ) -> napi::Result { - // This error doesn't return data - if !trace.return_data.is_empty() { - return Ok(false); - } - - let (neg, value, _) = trace.value.get_u64(); - if neg || value == 0 { - return Ok(false); - } - - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - // Libraries don't have a nonpayable check - if contract.r#type == ContractKind::Library { - return Ok(false); - } - - Ok(called_function.is_payable != Some(true)) - } - - fn get_last_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result> { - let (bytecode, steps) = match trace { - Either::A(create) => (&create.bytecode, &create.steps), - Either::B(call) => (&call.bytecode, &call.steps), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - for step in steps.iter().rev() { - let step = match step { - Either4::A(step) => step, - _ => continue, - }; - - let inst = bytecode.get_instruction_napi(step.pc)?; - - let Some(location) = &inst.location else { - continue; - }; - - let source_reference = source_location_to_source_reference(bytecode, Some(location))?; - - if let Some(source_reference) = source_reference { - return Ok(Some(source_reference)); - } - } - - Ok(None) - } - - fn has_failed_inside_the_fallback_function(trace: &CallMessageTrace) -> napi::Result { - let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract; - let contract = contract.borrow(); - - match &contract.fallback { - Some(fallback) => Self::has_failed_inside_function(trace, fallback), - None => Ok(false), - } - } - - fn has_failed_inside_the_receive_function(trace: &CallMessageTrace) -> napi::Result { - let contract = &trace.bytecode.as_ref().expect("JS code asserts").contract; - let contract = contract.borrow(); - - match &contract.receive { - Some(receive) => Self::has_failed_inside_function(trace, receive), - None => Ok(false), - } - } - - fn has_failed_inside_function( - trace: &CallMessageTrace, - func: &ContractFunction, - ) -> napi::Result { - let last_step = trace - .steps - .last() - .expect("There should at least be one step"); - - let last_step = match last_step { - Either4::A(step) => step, - _ => panic!("JS code asserted this is always an EvmStep"), - }; - - let last_instruction = trace - .bytecode - .as_ref() - .expect("The TS code type-checks this to always have bytecode") - .get_instruction_napi(last_step.pc)?; - - Ok(match &last_instruction.location { - Some(last_instruction_location) => { - last_instruction.opcode == OpCode::REVERT - && func.location.contains(last_instruction_location) - } - _ => false, - }) - } - - fn instruction_within_function_to_revert_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - inst: &Instruction, - ) -> napi::Result { - let bytecode = match &trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - } - .as_ref() - .expect("JS code asserts"); - - let source_reference = - source_location_to_source_reference(bytecode, inst.location.as_deref())? - .expect("Expected source reference to be defined"); - - let return_data = match &trace { - Either::A(create) => &create.return_data, - Either::B(call) => &call.return_data, - }; - - Ok(RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - is_invalid_opcode_error: inst.opcode == OpCode::INVALID, - return_data: return_data.clone(), - }) - } - - fn instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - inst: &Instruction, - ) -> napi::Result { - let bytecode = match &trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - } - .as_ref() - .expect("JS code asserts"); - - let source_reference = - source_location_to_source_reference(bytecode, inst.location.as_deref())?; - - Ok(UnmappedSolc063RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - }) - } - - fn instruction_within_function_to_panic_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - inst: &Instruction, - error_code: BigInt, - ) -> napi::Result { - let last_source_reference = Self::get_last_source_reference(trace)?; - - let bytecode = match &trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - } - .as_ref() - .expect("JS code asserts"); - - let source_reference = - source_location_to_source_reference(bytecode, inst.location.as_deref())?; - - let source_reference = source_reference.or(last_source_reference); - - Ok(PanicErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - error_code, - }) - } - - fn instruction_within_function_to_custom_error_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - inst: &Instruction, - message: String, - ) -> napi::Result { - let last_source_reference = Self::get_last_source_reference(trace)?; - let last_source_reference = - last_source_reference.expect("Expected source reference to be defined"); - - let bytecode = match &trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - } - .as_ref() - .expect("JS code asserts"); - - let source_reference = - source_location_to_source_reference(bytecode, inst.location.as_deref())?; - - let source_reference = source_reference.unwrap_or(last_source_reference); - - Ok(CustomErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - message, - }) - } - - fn solidity_0_6_3_maybe_unmapped_revert( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let (bytecode, steps) = match &trace { - Either::A(create) => (&create.bytecode, &create.steps), - Either::B(call) => (&call.bytecode, &call.steps), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - if steps.is_empty() { - return Ok(false); - } - - let last_step = steps.last(); - let last_step = match last_step { - Some(Either4::A(step)) => step, - _ => return Ok(false), - }; - - let last_instruction = bytecode.get_instruction_napi(last_step.pc)?; - - let Ok(version) = Version::parse(&bytecode.compiler_version) else { - return Ok(false); - }; - let req = VersionReq::parse(&format!("^{FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS}")) - .expect("valid semver"); - - Ok(req.matches(&version) && last_instruction.opcode == OpCode::REVERT) - } - - // Solidity 0.6.3 unmapped reverts special handling - // For more info: https://github.com/ethereum/solidity/issues/9006 - fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( - trace: &CallMessageTrace, - ) -> napi::Result> { - let bytecode = trace.bytecode.as_ref().expect("JS code asserts"); - let contract = bytecode.contract.borrow(); - - let revert_frame = - Self::solidity_0_6_3_get_frame_for_unmapped_revert_within_function(Either::A(trace))?; - - let revert_frame = match revert_frame { - None - | Some(UnmappedSolc063RevertErrorStackTraceEntry { - source_reference: None, - .. - }) => { - if contract.receive.is_none() || trace.calldata.len() > 0 { - // Failed within the fallback - if let Some(fallback) = &contract.fallback { - let location = &fallback.location; - let file = location.file(); - let file = file.borrow(); - - let revert_frame = UnmappedSolc063RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Some(SourceReference { - contract: Some(contract.name.clone()), - function: Some(FALLBACK_FUNCTION_NAME.to_string()), - source_name: file.source_name.clone(), - source_content: file.content.clone(), - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length] - .to_vec(), - }), - }; - - Some(Self::solidity_0_6_3_correct_line_number(revert_frame)) - } else { - None - } - } else { - let receive = contract - .receive - .as_ref() - .expect("None always hits branch above"); - - let location = &receive.location; - let file = location.file(); - let file = file.borrow(); - - let revert_frame = UnmappedSolc063RevertErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Some(SourceReference { - contract: Some(contract.name.clone()), - function: Some(RECEIVE_FUNCTION_NAME.to_string()), - source_name: file.source_name.clone(), - source_content: file.content.clone(), - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - }), - }; - - Some(Self::solidity_0_6_3_correct_line_number(revert_frame)) - } - } - Some(revert_frame) => Some(revert_frame), - }; - - Ok(revert_frame) - } - - fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result> { - let (bytecode, steps) = match &trace { - Either::A(create) => (&create.bytecode, &create.steps), - Either::B(call) => (&call.bytecode, &call.steps), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - let contract = bytecode.contract.borrow(); - - // If we are within a function there's a last valid location. It may - // be the entire contract. - let prev_inst = Self::get_last_instruction_with_valid_location(trace)?; - let last_step = match steps.last() { - Some(Either4::A(step)) => step, - _ => panic!("JS code asserts this is always an EvmStep"), - }; - let next_inst_pc = last_step.pc + 1; - let has_next_inst = bytecode.has_instruction(next_inst_pc); - - if has_next_inst { - let next_inst = bytecode.get_instruction_napi(next_inst_pc)?; - - let prev_loc = prev_inst.and_then(|i| i.location.as_deref()); - let next_loc = next_inst.location.as_deref(); - - let prev_func = prev_loc.and_then(SourceLocation::get_containing_function); - let next_func = next_loc.and_then(SourceLocation::get_containing_function); - - // This is probably a require. This means that we have the exact - // line, but the stack trace may be degraded (e.g. missing our - // synthetic call frames when failing in a modifier) so we still - // add this frame as UNMAPPED_SOLC_0_6_3_REVERT_ERROR - match (&prev_func, &next_loc, &prev_loc) { - (Some(_), Some(next_loc), Some(prev_loc)) if prev_loc == next_loc => { - return Ok(Some(Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry( - trace, - next_inst, - - )?)); - } - _ => {} - } - - let revert_frame = if prev_func.is_some() && prev_inst.is_some() { - Some(Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry( - trace, - prev_inst.as_ref().unwrap(), - - )?) - } else if next_func.is_some() { - Some(Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry( - trace, - next_inst, - - )?) - } else { - None - }; - - return Ok(revert_frame.map(Self::solidity_0_6_3_correct_line_number)); - } - - if matches!(trace, Either::B(CreateMessageTrace { .. })) && prev_inst.is_some() { - // Solidity is smart enough to stop emitting extra instructions after - // an unconditional revert happens in a constructor. If this is the case - // we just return a special error. - - let mut constructor_revert_frame = Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry( - trace, - prev_inst.as_ref().unwrap(), - - )?; - - // When the latest instruction is not within a function we need - // some default sourceReference to show to the user - if constructor_revert_frame.source_reference.is_none() { - let location = &contract.location; - let file = location.file(); - let file = file.borrow(); - - let mut default_source_reference = SourceReference { - function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), - contract: Some(contract.name.clone()), - source_name: file.source_name.clone(), - source_content: file.content.clone(), - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - }; - - if let Some(constructor) = &contract.constructor { - default_source_reference.line = constructor.location.get_starting_line_number(); - } - - constructor_revert_frame.source_reference = Some(default_source_reference); - } else { - constructor_revert_frame = - Self::solidity_0_6_3_correct_line_number(constructor_revert_frame); - } - - return Ok(Some(constructor_revert_frame)); - } - - if let Some(prev_inst) = prev_inst { - // We may as well just be in a function or modifier and just happen - // to be at the last instruction of the runtime bytecode. - // In this case we just return whatever the last mapped intruction - // points to. - let mut latest_instruction_revert_frame = Self::instruction_within_function_to_unmapped_solc_0_6_3_revert_error_stack_trace_entry( - trace, - prev_inst, - - )?; - - if latest_instruction_revert_frame.source_reference.is_some() { - latest_instruction_revert_frame = - Self::solidity_0_6_3_correct_line_number(latest_instruction_revert_frame); - } - return Ok(Some(latest_instruction_revert_frame)); - } - - Ok(None) - } - - fn solidity_0_6_3_correct_line_number( - mut revert_frame: UnmappedSolc063RevertErrorStackTraceEntry, - ) -> UnmappedSolc063RevertErrorStackTraceEntry { - let Some(source_reference) = &mut revert_frame.source_reference else { - return revert_frame; - }; - - let lines: Vec<_> = source_reference.source_content.split('\n').collect(); - - let current_line = lines[source_reference.line as usize - 1]; - if current_line.contains("require") || current_line.contains("revert") { - return revert_frame; - } - - let next_lines = &lines - .get(source_reference.line as usize..) - .unwrap_or_default(); - let first_non_empty_line = next_lines.iter().position(|l| !l.trim().is_empty()); - - let Some(first_non_empty_line) = first_non_empty_line else { - return revert_frame; - }; - - let next_line = next_lines[first_non_empty_line]; - if next_line.contains("require") || next_line.contains("revert") { - source_reference.line += 1 + first_non_empty_line as u32; - } - - revert_frame - } - - fn get_other_error_before_called_function_stack_trace_entry( - trace: &CallMessageTrace, - ) -> napi::Result { - let source_reference = - Self::get_contract_start_without_function_source_reference(Either::A(trace))?; - - Ok(OtherExecutionErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: Some(source_reference), - }) - } - - fn is_called_non_contract_account_error( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - // We could change this to checking that the last valid location maps to a call, - // but it's way more complex as we need to get the ast node from that - // location. - - let (bytecode, steps) = match &trace { - Either::A(create) => (&create.bytecode, &create.steps), - Either::B(call) => (&call.bytecode, &call.steps), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - let last_index = Self::get_last_instruction_with_valid_location_step_index(trace)?; - - let last_index = match last_index { - None | Some(0) => return Ok(false), - Some(last_index) => last_index as usize, - }; - - let last_step = match &steps[last_index] { - Either4::A(step) => step, - _ => panic!("We know this is an EVM step"), - }; - - let last_inst = bytecode.get_instruction_napi(last_step.pc)?; - - if last_inst.opcode != OpCode::ISZERO { - return Ok(false); - } - - let prev_step = match &steps[last_index - 1] { - Either4::A(step) => step, - _ => panic!("We know this is an EVM step"), - }; - - let prev_inst = bytecode.get_instruction_napi(prev_step.pc)?; - - Ok(prev_inst.opcode == OpCode::EXTCODESIZE) - } - - fn get_last_instruction_with_valid_location_step_index( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result> { - let (bytecode, steps) = match &trace { - Either::A(create) => (&create.bytecode, &create.steps), - Either::B(call) => (&call.bytecode, &call.steps), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - for (i, step) in steps.iter().enumerate().rev() { - let step = match step { - Either4::A(step) => step, - _ => return Ok(None), - }; - - let inst = bytecode.get_instruction_napi(step.pc)?; - - if inst.location.is_some() { - return Ok(Some(i as u32)); - } - } - - Ok(None) - } - - fn get_last_instruction_with_valid_location<'a>( - trace: Either<&'a CallMessageTrace, &'a CreateMessageTrace>, - ) -> napi::Result> { - let last_location_index = Self::get_last_instruction_with_valid_location_step_index(trace)?; - - let Some(last_location_index) = last_location_index else { - return Ok(None); - }; - - let (bytecode, steps) = match &trace { - Either::A(create) => (&create.bytecode, &create.steps), - Either::B(call) => (&call.bytecode, &call.steps), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - match &steps.get(last_location_index as usize) { - Some(Either4::A(step)) => { - let inst = bytecode.get_instruction_napi(step.pc)?; - - Ok(Some(inst)) - } - _ => Ok(None), - } - } -} - -fn source_location_to_source_reference( - bytecode: &Bytecode, - location: Option<&SourceLocation>, -) -> napi::Result> { - let Some(location) = location else { - return Ok(None); - }; - let Some(func) = location.get_containing_function() else { - return Ok(None); - }; - - let func_name = match func.r#type { - ContractFunctionType::Constructor => CONSTRUCTOR_FUNCTION_NAME.to_string(), - ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(), - ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(), - _ => func.name.clone(), - }; - - let func_location_file = func.location.file(); - let func_location_file = func_location_file.borrow(); - - Ok(Some(SourceReference { - function: Some(func_name.clone()), - contract: if func.r#type == ContractFunctionType::FreeFunction { - None - } else { - Some(bytecode.contract.borrow().name.clone()) - }, - source_name: func_location_file.source_name.clone(), - source_content: func_location_file.content.clone(), - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - })) -} - -pub fn instruction_to_callstack_stack_trace_entry( - bytecode: &Bytecode, - inst: &Instruction, -) -> napi::Result> { - let contract = bytecode.contract.borrow(); - - // This means that a jump is made from within an internal solc function. - // These are normally made from yul code, so they don't map to any Solidity - // function - let inst_location = match &inst.location { - None => { - let location = &contract.location; - let file = location.file(); - let file = file.borrow(); - - return Ok(Either::B(InternalFunctionCallStackEntry { - type_: StackTraceEntryTypeConst, - pc: inst.pc, - source_reference: SourceReference { - source_name: file.source_name.clone(), - source_content: file.content.clone(), - contract: Some(contract.name.clone()), - function: None, - line: location.get_starting_line_number(), - range: [location.offset, location.offset + location.length].to_vec(), - }, - })); - } - Some(inst_location) => inst_location, - }; - - if let Some(func) = inst_location.get_containing_function() { - let source_reference = source_location_to_source_reference(bytecode, Some(inst_location))? - .expect("Expected source reference to be defined"); - - return Ok(Either::A(CallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference, - function_type: func.r#type.into(), - })); - }; - - let file = inst_location.file(); - let file = file.borrow(); - - Ok(Either::A(CallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: SourceReference { - function: None, - contract: Some(contract.name.clone()), - source_name: file.source_name.clone(), - source_content: file.content.clone(), - line: inst_location.get_starting_line_number(), - range: [ - inst_location.offset, - inst_location.offset + inst_location.length, - ] - .to_vec(), - }, - function_type: ContractFunctionType::Function.into(), - })) -} - -// Rewrite of `AbiHelpers.formatValues` from Hardhat -fn format_dyn_sol_value(val: &DynSolValue) -> String { - match val { - // print nested values as [value1, value2, ...] - DynSolValue::Array(items) - | DynSolValue::Tuple(items) - | DynSolValue::FixedArray(items) - | DynSolValue::CustomStruct { tuple: items, .. } => { - let mut result = String::from("["); - for (i, val) in items.iter().enumerate() { - if i > 0 { - result.push_str(", "); - } - result.push_str(&format_dyn_sol_value(val)); - } - - result.push(']'); - result - } - // surround string values with quotes - DynSolValue::String(s) => format!("\"{s}\""), - - DynSolValue::Address(address) => format!("\"{address}\""), - DynSolValue::Bytes(bytes) => format!("\"{}\"", hex::encode_prefixed(bytes)), - DynSolValue::FixedBytes(word, size) => { - format!("\"{}\"", hex::encode_prefixed(&word.0.as_slice()[..*size])) - } - DynSolValue::Bool(b) => b.to_string(), - DynSolValue::Function(_) => "".to_string(), - DynSolValue::Int(int, _bits) => int.to_string(), - DynSolValue::Uint(uint, _bits) => uint.to_string(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_sol_value_to_string() { - assert_eq!( - format_dyn_sol_value(&DynSolValue::String("hello".to_string())), - "\"hello\"" - ); - // Uniform, 0-prefixed hex strings - assert_eq!( - format_dyn_sol_value(&DynSolValue::Address([0u8; 20].into())), - format!(r#""0x{}""#, "0".repeat(2 * 20)) - ); - assert_eq!( - format_dyn_sol_value(&DynSolValue::Bytes(vec![0u8; 32])), - format!(r#""0x{}""#, "0".repeat(2 * 32)) - ); - assert_eq!( - format_dyn_sol_value(&DynSolValue::FixedBytes([0u8; 32].into(), 10)), - format!(r#""0x{}""#, "0".repeat(2 * 10)) - ); - assert_eq!( - format_dyn_sol_value(&DynSolValue::FixedBytes([0u8; 32].into(), 32)), - format!(r#""0x{}""#, "0".repeat(2 * 32)) - ); - } -} diff --git a/crates/edr_napi/src/trace/library_utils.rs b/crates/edr_napi/src/trace/library_utils.rs deleted file mode 100644 index 7d0fbf957..000000000 --- a/crates/edr_napi/src/trace/library_utils.rs +++ /dev/null @@ -1,6 +0,0 @@ -use napi_derive::napi; - -#[napi] -pub fn link_hex_string_bytecode(code: String, address: String, position: u32) -> String { - edr_solidity::library_utils::link_hex_string_bytecode(code, &address, position) -} diff --git a/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs b/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs deleted file mode 100644 index eafeef902..000000000 --- a/crates/edr_napi/src/trace/mapped_inlined_internal_functions_heuristics.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! This file includes Solidity tracing heuristics for solc starting with -//! version 0.6.9. -//! -//! This solc version introduced a significant change to how sourcemaps are -//! handled for inline yul/internal functions. These were mapped to the -//! unmapped/-1 file before, which lead to many unmapped reverts. Now, they are -//! mapped to the part of the Solidity source that lead to their inlining. -//! -//! This change is a very positive change, as errors would point to the correct -//! line by default. The only problem is that we used to rely very heavily on -//! unmapped reverts to decide when our error detection heuristics were to be -//! run. In fact, these heuristics were first introduced because of unmapped -//! reverts. -//! -//! Instead of synthetically completing stack traces when unmapped reverts -//! occur, we now start from complete stack traces and adjust them if we can -//! provide more meaningful errors. - -use edr_evm::interpreter::OpCode; -use napi::{ - bindgen_prelude::{Either24, Either4}, - Either, -}; -use semver::Version; - -use super::{ - message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep}, - solidity_stack_trace::{ - InvalidParamsErrorStackTraceEntry, NonContractAccountCalledErrorStackTraceEntry, - RevertErrorStackTraceEntry, SolidityStackTrace, StackTraceEntryTypeConst, - }, -}; - -const FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS: Version = Version::new(0, 6, 9); - -pub fn stack_trace_may_require_adjustments( - stacktrace: &SolidityStackTrace, - decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>, -) -> bool { - let bytecode = match &decoded_trace { - Either::A(create) => &create.bytecode, - Either::B(call) => &call.bytecode, - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - let Some(last_frame) = stacktrace.last() else { - return false; - }; - - if let Either24::E(last_frame @ RevertErrorStackTraceEntry { .. }) = last_frame { - return !last_frame.is_invalid_opcode_error - && last_frame.return_data.is_empty() - && Version::parse(&bytecode.compiler_version) - .map(|version| version >= FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS) - .unwrap_or(false); - } - - false -} - -pub fn adjust_stack_trace( - mut stacktrace: SolidityStackTrace, - decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>, -) -> napi::Result { - let Some(Either24::E(revert @ RevertErrorStackTraceEntry { .. })) = stacktrace.last() else { - unreachable!("JS code asserts that; it's only used immediately after we check with `stack_trace_may_require_adjustments` that the last frame is a revert frame"); - }; - - // Replace the last revert frame with an adjusted frame if needed - if is_non_contract_account_called_error(decoded_trace)? { - let last_revert_frame_source_reference = revert.source_reference.clone(); - stacktrace.pop(); - stacktrace.push( - NonContractAccountCalledErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: last_revert_frame_source_reference, - } - .into(), - ); - return Ok(stacktrace); - } - - if is_constructor_invalid_params_error(decoded_trace)? { - let last_revert_frame_source_reference = revert.source_reference.clone(); - stacktrace.pop(); - stacktrace.push( - InvalidParamsErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: last_revert_frame_source_reference, - } - .into(), - ); - return Ok(stacktrace); - } - - if is_call_invalid_params_error(decoded_trace)? { - let last_revert_frame_source_reference = revert.source_reference.clone(); - stacktrace.pop(); - stacktrace.push( - InvalidParamsErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: last_revert_frame_source_reference, - } - .into(), - ); - - return Ok(stacktrace); - } - - Ok(stacktrace) -} - -fn is_non_contract_account_called_error( - decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>, -) -> napi::Result { - match_opcodes( - decoded_trace, - -9, - &[ - OpCode::EXTCODESIZE, - OpCode::ISZERO, - OpCode::DUP1, - OpCode::ISZERO, - ], - ) -} - -fn is_constructor_invalid_params_error( - decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>, -) -> napi::Result { - Ok(match_opcodes(decoded_trace, -20, &[OpCode::CODESIZE])? - && match_opcodes(decoded_trace, -15, &[OpCode::CODECOPY])? - && match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?) -} - -fn is_call_invalid_params_error( - decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>, -) -> napi::Result { - Ok(match_opcodes(decoded_trace, -11, &[OpCode::CALLDATASIZE])? - && match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?) -} - -fn match_opcodes( - decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>, - first_step_index: i32, - opcodes: &[OpCode], -) -> napi::Result { - let (bytecode, steps) = match &decoded_trace { - Either::A(call) => (&call.bytecode, &call.steps), - Either::B(create) => (&create.bytecode, &create.steps), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - // If the index is negative, we start from the end of the trace, - // just like in the original JS code - let mut index = match first_step_index { - 0.. => first_step_index as usize, - ..=-1 if first_step_index.abs() < steps.len() as i32 => { - (steps.len() as i32 + first_step_index) as usize - } - // Out of bounds - _ => return Ok(false), - }; - - for opcode in opcodes { - let Some(Either4::A(EvmStep { pc })) = steps.get(index) else { - return Ok(false); - }; - - let instruction = bytecode.get_instruction_napi(*pc)?; - - if instruction.opcode != *opcode { - return Ok(false); - } - - index += 1; - } - - Ok(true) -} diff --git a/crates/edr_napi/src/trace/message_trace.rs b/crates/edr_napi/src/trace/message_trace.rs deleted file mode 100644 index c44f1165f..000000000 --- a/crates/edr_napi/src/trace/message_trace.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! Bridging type for the existing `MessageTrace` interface in Hardhat. - -use napi::{ - bindgen_prelude::{BigInt, ClassInstance, Either3, Either4, Uint8Array, Undefined}, - Either, Env, -}; -use napi_derive::napi; - -use super::{exit::Exit, model::BytecodeWrapper}; - -#[napi(object)] -pub struct EvmStep { - pub pc: u32, -} - -#[napi(object)] -pub struct PrecompileMessageTrace { - // `BaseMessageTrace` - pub value: BigInt, - pub return_data: Uint8Array, - pub exit: ClassInstance, - pub gas_used: BigInt, - pub depth: u32, - // `PrecompileMessageTrace` - pub precompile: u32, - pub calldata: Uint8Array, -} - -// NOTE: Because of the hack below for `deployed_contract`, now the -// `CallMessageTrace` is a strict superset of `CreateMessageTrace`, so we need -// to take care to keep the order consistent from most-specific to -// least-specific in the `Either{3,4}` type when converting to or from N-API. -#[napi(object)] -pub struct CreateMessageTrace { - // `BaseMessageTrace` - pub value: BigInt, - pub return_data: Uint8Array, - pub exit: ClassInstance, - pub gas_used: BigInt, - pub depth: u32, - // `BaseEvmMessageTrace` - pub code: Uint8Array, - pub steps: Vec>, - /// Reference to the resolved `Bytecode` EDR data. - /// Only used on the JS side by the `VmTraceDecoder` class. - pub bytecode: Option>, - pub number_of_subtraces: u32, - // `CreateMessageTrace` - // HACK: It seems that `Either` means exactly what we - // want (a required property but can be explicitly `undefined`) but internally - // the napi-rs treats an encountered `Undefined` like a missing property - // and it throws a validation error. While not 100% backwards compatible, we - // work around using an optional type. - // See https://github.com/napi-rs/napi-rs/issues/1986 for context on the PR - // that introduced this behavior. - pub deployed_contract: Option>, -} - -#[napi(object)] -pub struct CallMessageTrace { - // `BaseMessageTrace` - pub value: BigInt, - pub return_data: Uint8Array, - pub exit: ClassInstance, - pub gas_used: BigInt, - pub depth: u32, - // `BaseEvmMessageTrace` - pub code: Uint8Array, - pub steps: Vec>, - /// Reference to the resolved `Bytecode` EDR data. - /// Only used on the JS side by the `VmTraceDecoder` class. - pub bytecode: Option>, - pub number_of_subtraces: u32, - // `CallMessageTrace` - pub calldata: Uint8Array, - pub address: Uint8Array, - pub code_address: Uint8Array, -} - -/// Converts [`edr_solidity::message_trace::VmTracerMessageTraceStep`] to the -/// N-API representation. -/// -/// # Panics -/// This function will panic if the value is mutably borrowed. -pub fn message_trace_step_to_napi( - value: edr_solidity::message_trace::VmTracerMessageTraceStep, - env: Env, -) -> napi::Result> { - Ok(match value { - edr_solidity::message_trace::VmTracerMessageTraceStep::Evm(step) => { - Either4::A(EvmStep { pc: step.pc as u32 }) - } - edr_solidity::message_trace::VmTracerMessageTraceStep::Message(msg) => { - // Immediately drop the borrow lock to err on the safe side as we - // may be recursing. - let owned = msg.borrow().clone(); - match message_trace_to_napi(owned, env)? { - Either3::A(precompile) => Either4::B(precompile), - Either3::B(call) => Either4::C(call), - Either3::C(create) => Either4::D(create), - } - } - }) -} - -/// Converts the Rust representation of a `MessageTrace` to the N-API -/// representation. -pub fn message_trace_to_napi( - value: edr_solidity::message_trace::MessageTrace, - env: Env, -) -> napi::Result> { - Ok(match value { - edr_solidity::message_trace::MessageTrace::Precompile(precompile) => { - Either3::A(PrecompileMessageTrace { - value: BigInt { - sign_bit: false, - words: precompile.base.value.as_limbs().to_vec(), - }, - return_data: Uint8Array::from(precompile.base.return_data.as_ref()), - exit: Exit(precompile.base.exit.into()).into_instance(env)?, - gas_used: BigInt::from(precompile.base.gas_used), - depth: precompile.base.depth as u32, - - precompile: precompile.precompile, - calldata: Uint8Array::from(precompile.calldata.as_ref()), - }) - } - edr_solidity::message_trace::MessageTrace::Call(call) => Either3::B(CallMessageTrace { - value: BigInt { - sign_bit: false, - words: call.base.base.value.as_limbs().to_vec(), - }, - return_data: Uint8Array::from(call.base.base.return_data.as_ref()), - exit: Exit(call.base.base.exit.into()).into_instance(env)?, - gas_used: BigInt::from(call.base.base.gas_used), - depth: call.base.base.depth as u32, - code: Uint8Array::from(call.base.code.as_ref()), - steps: call - .base - .steps - .into_iter() - .map(|step| message_trace_step_to_napi(step, env)) - .collect::>>()?, - // NOTE: We specifically use None as that will be later filled on the JS side - bytecode: None, - number_of_subtraces: call.base.number_of_subtraces, - - address: Uint8Array::from(call.address.as_slice()), - calldata: Uint8Array::from(call.calldata.as_ref()), - code_address: Uint8Array::from(call.code_address.as_slice()), - }), - edr_solidity::message_trace::MessageTrace::Create(create) => { - Either3::C(CreateMessageTrace { - value: BigInt { - sign_bit: false, - words: create.base.base.value.as_limbs().to_vec(), - }, - return_data: Uint8Array::from(create.base.base.return_data.as_ref()), - exit: Exit(create.base.base.exit.into()).into_instance(env)?, - gas_used: BigInt::from(create.base.base.gas_used), - depth: create.base.base.depth as u32, - code: Uint8Array::from(create.base.code.as_ref()), - steps: create - .base - .steps - .into_iter() - .map(|step| message_trace_step_to_napi(step, env)) - .collect::>>()?, - // NOTE: We specifically use None as that will be later filled on the JS side - bytecode: None, - - number_of_subtraces: create.base.number_of_subtraces, - deployed_contract: create - .deployed_contract - .map(|contract| Either::A(Uint8Array::from(contract.as_ref()))), - }) - } - }) -} diff --git a/crates/edr_napi/src/trace/model.rs b/crates/edr_napi/src/trace/model.rs index 8072d68a7..d08ded847 100644 --- a/crates/edr_napi/src/trace/model.rs +++ b/crates/edr_napi/src/trace/model.rs @@ -1,34 +1,6 @@ -use std::rc::Rc; - -use edr_solidity::build_model::Bytecode; use napi_derive::napi; use serde::Serialize; -/// Opaque handle to the `Bytecode` struct. -/// Only used on the JS side by the `VmTraceDecoder` class. -// NOTE: Needed, because we store the resolved `Bytecode` in the MessageTrace -// JS plain objects and those need a dedicated (class) type. -#[napi] -pub struct BytecodeWrapper(pub(crate) Rc); - -impl BytecodeWrapper { - pub fn new(bytecode: Rc) -> Self { - Self(bytecode) - } - - pub fn inner(&self) -> &Rc { - &self.0 - } -} - -impl std::ops::Deref for BytecodeWrapper { - type Target = Bytecode; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - #[derive(Debug, PartialEq, Eq, Serialize)] #[allow(non_camel_case_types)] // intentionally mimicks the original case in TS #[allow(clippy::upper_case_acronyms)] diff --git a/crates/edr_napi/src/trace/solidity_stack_trace.rs b/crates/edr_napi/src/trace/solidity_stack_trace.rs index 1a6bf8039..4e7542f36 100644 --- a/crates/edr_napi/src/trace/solidity_stack_trace.rs +++ b/crates/edr_napi/src/trace/solidity_stack_trace.rs @@ -128,16 +128,6 @@ impl ToNapiValue for StackTraceEntryTypeConst } } -impl StackTraceEntryTypeConst { - #[allow(clippy::unused_self)] // less verbose than ::as_value() - const fn as_value(&self) -> StackTraceEntryType { - match StackTraceEntryType::from_repr(ENTRY_TYPE) { - Some(val) => val, - None => panic!("Invalid StackTraceEntryType value"), - } - } -} - impl Serialize for StackTraceEntryTypeConst { fn serialize(&self, serializer: S) -> Result where @@ -823,72 +813,6 @@ impl TryCast for edr_solidity::solidity_stack_trace::St // Same as above, but for the `SolidityStackTrace` type. pub type SolidityStackTrace = Vec; -pub trait SolidityStackTraceEntryExt { - fn type_(&self) -> StackTraceEntryType; - fn source_reference(&self) -> Option<&SourceReference>; -} - -impl SolidityStackTraceEntryExt for SolidityStackTraceEntry { - fn type_(&self) -> StackTraceEntryType { - match self { - Either24::A(entry) => entry.type_.as_value(), - Either24::B(entry) => entry.type_.as_value(), - Either24::C(entry) => entry.type_.as_value(), - Either24::D(entry) => entry.type_.as_value(), - Either24::E(entry) => entry.type_.as_value(), - Either24::F(entry) => entry.type_.as_value(), - Either24::G(entry) => entry.type_.as_value(), - Either24::H(entry) => entry.type_.as_value(), - Either24::I(entry) => entry.type_.as_value(), - Either24::J(entry) => entry.type_.as_value(), - Either24::K(entry) => entry.type_.as_value(), - Either24::L(entry) => entry.type_.as_value(), - Either24::M(entry) => entry.type_.as_value(), - Either24::N(entry) => entry.type_.as_value(), - Either24::O(entry) => entry.type_.as_value(), - Either24::P(entry) => entry.type_.as_value(), - Either24::Q(entry) => entry.type_.as_value(), - Either24::R(entry) => entry.type_.as_value(), - Either24::S(entry) => entry.type_.as_value(), - Either24::T(entry) => entry.type_.as_value(), - Either24::U(entry) => entry.type_.as_value(), - Either24::V(entry) => entry.type_.as_value(), - Either24::W(entry) => entry.type_.as_value(), - Either24::X(entry) => entry.type_.as_value(), - } - } - - #[allow(clippy::unnecessary_lazy_evaluations)] // guards against potential variant reordering - fn source_reference(&self) -> Option<&SourceReference> { - match self { - Either24::A(entry) => Some(&entry.source_reference), - Either24::B(entry) => entry.source_reference.and_then(|_: ()| None), - Either24::C(entry) => entry.source_reference.and_then(|_: ()| None), - Either24::D(entry) => entry.source_reference.and_then(|_: ()| None), - Either24::E(entry) => Some(&entry.source_reference), - Either24::F(entry) => entry.source_reference.as_ref(), - Either24::G(entry) => Some(&entry.source_reference), - Either24::H(entry) => Some(&entry.source_reference), - Either24::I(entry) => Some(&entry.source_reference), - Either24::J(entry) => Some(&entry.source_reference), - Either24::K(entry) => Some(&entry.source_reference), - Either24::L(entry) => Some(&entry.source_reference), - Either24::M(entry) => Some(&entry.source_reference), - Either24::N(entry) => Some(&entry.source_reference), - Either24::O(entry) => Some(&entry.source_reference), - Either24::P(entry) => Some(&entry.source_reference), - Either24::Q(entry) => Some(&entry.source_reference), - Either24::R(entry) => entry.source_reference.and_then(|_: ()| None), - Either24::S(entry) => entry.source_reference.and_then(|_: ()| None), - Either24::T(entry) => entry.source_reference.as_ref(), - Either24::U(entry) => entry.source_reference.as_ref(), - Either24::V(entry) => entry.source_reference.as_ref(), - Either24::W(entry) => Some(&entry.source_reference), - Either24::X(entry) => entry.source_reference.as_ref(), - } - } -} - const _: () = { const fn assert_to_from_napi_value() {} assert_to_from_napi_value::(); diff --git a/crates/edr_napi/src/trace/solidity_tracer.rs b/crates/edr_napi/src/trace/solidity_tracer.rs deleted file mode 100644 index bf15a89c6..000000000 --- a/crates/edr_napi/src/trace/solidity_tracer.rs +++ /dev/null @@ -1,315 +0,0 @@ -use edr_evm::interpreter::OpCode; -use edr_solidity::build_model::{Instruction, JumpType}; -use napi::{ - bindgen_prelude::{Either3, Either4}, - Either, -}; -use napi_derive::napi; - -use super::{ - error_inferrer::{ - instruction_to_callstack_stack_trace_entry, ErrorInferrer, SubmessageDataRef, - }, - mapped_inlined_internal_functions_heuristics::{ - adjust_stack_trace, stack_trace_may_require_adjustments, - }, - message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep, PrecompileMessageTrace}, - solidity_stack_trace::{PrecompileErrorStackTraceEntry, SolidityStackTrace}, -}; -use crate::trace::{ - exit::ExitCode, - solidity_stack_trace::{ - ContractTooLargeErrorStackTraceEntry, SolidityStackTraceEntry, StackTraceEntryTypeConst, - UnrecognizedContractCallstackEntryStackTraceEntry, - UnrecognizedContractErrorStackTraceEntry, UnrecognizedCreateCallstackEntryStackTraceEntry, - UnrecognizedCreateErrorStackTraceEntry, - }, -}; - -#[napi(constructor)] -pub struct SolidityTracer; - -#[allow(clippy::unused_self)] // we allow this for convenience for now -#[napi] -impl SolidityTracer { - #[napi(catch_unwind)] - pub fn get_stack_trace( - &self, - trace: Either3, - ) -> napi::Result { - let trace = match &trace { - Either3::A(precompile) => Either3::A(precompile), - Either3::B(call) => Either3::B(call), - Either3::C(create) => Either3::C(create), - }; - - self.get_stack_trace_inner(trace) - } - - pub fn get_stack_trace_inner( - &self, - trace: Either3<&PrecompileMessageTrace, &CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let exit = match &trace { - Either3::A(precompile) => &precompile.exit, - Either3::B(call) => &call.exit, - Either3::C(create) => &create.exit, - }; - - if !exit.is_error() { - return Ok(vec![]); - } - - match trace { - Either3::A(precompile) => Ok(self.get_precompile_message_stack_trace(precompile)?), - Either3::B(call) if call.bytecode.is_some() => { - Ok(self.get_call_message_stack_trace(call)?) - } - Either3::C(create) if create.bytecode.is_some() => { - Ok(self.get_create_message_stack_trace(create)?) - } - // No bytecode is present - Either3::B(call) => Ok(self.get_unrecognized_message_stack_trace(Either::A(call))?), - Either3::C(create) => Ok(self.get_unrecognized_message_stack_trace(Either::B(create))?), - } - } - - fn get_last_subtrace<'a>( - &self, - trace: Either<&'a CallMessageTrace, &'a CreateMessageTrace>, - ) -> Option> - { - let (number_of_subtraces, steps) = match trace { - Either::A(create) => (create.number_of_subtraces, &create.steps), - Either::B(call) => (call.number_of_subtraces, &call.steps), - }; - - if number_of_subtraces == 0 { - return None; - } - - steps.iter().rev().find_map(|step| match step { - Either4::A(EvmStep { .. }) => None, - Either4::B(precompile) => Some(Either3::A(precompile)), - Either4::C(call) => Some(Either3::B(call)), - Either4::D(create) => Some(Either3::C(create)), - }) - } - - fn get_precompile_message_stack_trace( - &self, - trace: &PrecompileMessageTrace, - ) -> napi::Result { - Ok(vec![PrecompileErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - precompile: trace.precompile, - source_reference: None, - } - .into()]) - } - - fn get_create_message_stack_trace( - &self, - trace: &CreateMessageTrace, - ) -> napi::Result { - let inferred_error = ErrorInferrer::infer_before_tracing_create_message(trace)?; - - if let Some(inferred_error) = inferred_error { - return Ok(inferred_error); - } - - self.trace_evm_execution(Either::B(trace)) - } - - fn get_call_message_stack_trace( - &self, - trace: &CallMessageTrace, - ) -> napi::Result { - let inferred_error = ErrorInferrer::infer_before_tracing_call_message(trace)?; - - if let Some(inferred_error) = inferred_error { - return Ok(inferred_error); - } - - self.trace_evm_execution(Either::A(trace)) - } - - fn get_unrecognized_message_stack_trace( - &self, - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let (trace_exit_kind, trace_return_data) = match &trace { - Either::A(call) => (call.exit.kind(), &call.return_data), - Either::B(create) => (create.exit.kind(), &create.return_data), - }; - - let subtrace = self.get_last_subtrace(trace); - - if let Some(subtrace) = subtrace { - let (is_error, return_data) = match subtrace { - Either3::A(precompile) => { - (precompile.exit.is_error(), precompile.return_data.clone()) - } - Either3::B(call) => (call.exit.is_error(), call.return_data.clone()), - Either3::C(create) => (create.exit.is_error(), create.return_data.clone()), - }; - - // This is not a very exact heuristic, but most of the time it will be right, as - // solidity reverts if a call fails, and most contracts are in - // solidity - if is_error && trace_return_data.as_ref() == return_data.as_ref() { - let unrecognized_entry: SolidityStackTraceEntry = match trace { - Either::A(CallMessageTrace { address, .. }) => { - UnrecognizedContractCallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - address: address.clone(), - source_reference: None, - } - .into() - } - Either::B(CreateMessageTrace { .. }) => { - UnrecognizedCreateCallstackEntryStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: None, - } - .into() - } - }; - - let mut stacktrace = vec![unrecognized_entry]; - stacktrace.extend(self.get_stack_trace_inner(subtrace)?); - - return Ok(stacktrace); - } - } - - if trace_exit_kind == ExitCode::CODESIZE_EXCEEDS_MAXIMUM { - return Ok(vec![ContractTooLargeErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - source_reference: None, - } - .into()]); - } - - let is_invalid_opcode_error = trace_exit_kind == ExitCode::INVALID_OPCODE; - - match trace { - Either::A(trace @ CallMessageTrace { .. }) => { - Ok(vec![UnrecognizedContractErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - address: trace.address.clone(), - return_data: trace.return_data.clone(), - is_invalid_opcode_error, - source_reference: None, - } - .into()]) - } - Either::B(trace @ CreateMessageTrace { .. }) => { - Ok(vec![UnrecognizedCreateErrorStackTraceEntry { - type_: StackTraceEntryTypeConst, - return_data: trace.return_data.clone(), - is_invalid_opcode_error, - source_reference: None, - } - .into()]) - } - } - } - - fn trace_evm_execution( - &self, - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let stack_trace = self.raw_trace_evm_execution(trace)?; - - if stack_trace_may_require_adjustments(&stack_trace, trace) { - return adjust_stack_trace(stack_trace, trace); - } - - Ok(stack_trace) - } - - fn raw_trace_evm_execution( - &self, - trace: Either<&CallMessageTrace, &CreateMessageTrace>, - ) -> napi::Result { - let (bytecode, steps, number_of_subtraces) = match &trace { - Either::A(call) => (&call.bytecode, &call.steps, call.number_of_subtraces), - Either::B(create) => (&create.bytecode, &create.steps, create.number_of_subtraces), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - let mut stacktrace: SolidityStackTrace = vec![]; - - let mut subtraces_seen = 0; - - // There was a jump into a function according to the sourcemaps - let mut jumped_into_function = false; - - let mut function_jumpdests: Vec<&Instruction> = vec![]; - - let mut last_submessage_data: Option> = None; - - let mut iter = steps.iter().enumerate().peekable(); - while let Some((step_index, step)) = iter.next() { - if let Either4::A(EvmStep { pc }) = step { - let inst = bytecode.get_instruction_napi(*pc)?; - - if inst.jump_type == JumpType::IntoFunction && iter.peek().is_some() { - let (_, next_step) = iter.peek().unwrap(); - let Either4::A(next_evm_step) = next_step else { - unreachable!("JS code asserted that"); - }; - let next_inst = bytecode.get_instruction_napi(next_evm_step.pc)?; - - if next_inst.opcode == OpCode::JUMPDEST { - let frame = instruction_to_callstack_stack_trace_entry(bytecode, inst)?; - stacktrace.push(match frame { - Either::A(frame) => frame.into(), - Either::B(frame) => frame.into(), - }); - if next_inst.location.is_some() { - jumped_into_function = true; - } - function_jumpdests.push(next_inst); - } - } else if inst.jump_type == JumpType::OutofFunction { - stacktrace.pop(); - function_jumpdests.pop(); - } - } else { - let message_trace = match step { - Either4::A(_) => unreachable!("branch is taken above"), - Either4::B(precompile) => Either3::A(precompile), - Either4::C(call) => Either3::B(call), - Either4::D(create) => Either3::C(create), - }; - - subtraces_seen += 1; - - // If there are more subtraces, this one didn't terminate the execution - if subtraces_seen < number_of_subtraces { - continue; - } - - let submessage_trace = self.get_stack_trace_inner(message_trace)?; - - last_submessage_data = Some(SubmessageDataRef { - message_trace, - step_index: step_index as u32, - stacktrace: submessage_trace, - }); - } - } - - let stacktrace_with_inferred_error = ErrorInferrer::infer_after_tracing( - trace, - stacktrace, - &function_jumpdests, - jumped_into_function, - last_submessage_data, - )?; - - ErrorInferrer::filter_redundant_frames(stacktrace_with_inferred_error) - } -} diff --git a/crates/edr_napi/src/trace/vm_trace_decoder.rs b/crates/edr_napi/src/trace/vm_trace_decoder.rs deleted file mode 100644 index 750beee74..000000000 --- a/crates/edr_napi/src/trace/vm_trace_decoder.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::rc::Rc; - -use edr_solidity::{ - artifacts::BuildInfo, - build_model::{Bytecode, ContractFunctionType}, - compiler::create_models_and_decode_bytecodes, - contracts_identifier::ContractsIdentifier, -}; -use napi::{ - bindgen_prelude::{ClassInstance, Either3, Either4, Uint8Array, Undefined}, - Either, Env, -}; -use napi_derive::napi; -use serde::{Deserialize, Serialize}; - -use super::{ - message_trace::{CallMessageTrace, CreateMessageTrace, PrecompileMessageTrace}, - solidity_stack_trace::{ - FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, - UNRECOGNIZED_FUNCTION_NAME, - }, -}; -use crate::trace::model::BytecodeWrapper; - -#[derive(Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TracingConfig { - pub build_infos: Option>, - pub ignore_contracts: Option, -} - -#[derive(Default)] -#[napi] -pub struct VmTraceDecoder { - contracts_identifier: ContractsIdentifier, -} - -#[napi] -impl VmTraceDecoder { - #[napi(constructor)] - pub fn new() -> Self { - Self::default() - } - - #[napi(catch_unwind)] - pub fn add_bytecode(&mut self, bytecode: ClassInstance) { - self.add_bytecode_inner(bytecode.inner().clone()); - } - - pub fn add_bytecode_inner(&mut self, bytecode: Rc) { - self.contracts_identifier.add_bytecode(bytecode); - } - - #[napi(catch_unwind)] - pub fn try_to_decode_message_trace( - &mut self, - message_trace: Either3, - env: Env, - ) -> napi::Result> { - match message_trace { - precompile @ Either3::A(..) => Ok(precompile), - // NOTE: The branches below are the same with the difference of `is_create` - Either3::B(mut call) => { - let is_create = false; - - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(call.code.as_ref(), is_create); - - let steps: Vec<_> = call - .steps - .into_iter() - .map(|step| { - let trace = match step { - Either4::A(step) => return Ok(Either4::A(step)), - Either4::B(precompile) => Either3::A(precompile), - Either4::C(create) => Either3::B(create), - Either4::D(call) => Either3::C(call), - }; - - Ok(match self.try_to_decode_message_trace(trace, env)? { - Either3::A(precompile) => Either4::B(precompile), - Either3::B(create) => Either4::C(create), - Either3::C(call) => Either4::D(call), - }) - }) - .collect::>()?; - - let bytecode = bytecode - .map(|b| BytecodeWrapper::new(b).into_instance(env)) - .transpose()?; - - call.bytecode = bytecode; - call.steps = steps; - - Ok(Either3::B(call)) - } - Either3::C(mut create @ CreateMessageTrace { .. }) => { - let is_create = true; - - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(create.code.as_ref(), is_create); - - let steps: Vec<_> = create - .steps - .into_iter() - .map(|step| { - let trace = match step { - Either4::A(step) => return Ok(Either4::A(step)), - Either4::B(precompile) => Either3::A(precompile), - Either4::C(create) => Either3::B(create), - Either4::D(call) => Either3::C(call), - }; - - Ok(match self.try_to_decode_message_trace(trace, env)? { - Either3::A(precompile) => Either4::B(precompile), - Either3::B(create) => Either4::C(create), - Either3::C(call) => Either4::D(call), - }) - }) - .collect::>()?; - - let bytecode = bytecode - .map(|b| BytecodeWrapper::new(b).into_instance(env)) - .transpose()?; - create.bytecode = bytecode; - create.steps = steps; - - Ok(Either3::C(create)) - } - } - } - - #[napi] - pub fn get_contract_and_function_names_for_call( - &mut self, - code: Uint8Array, - calldata: Either, - ) -> napi::Result { - let is_create = matches!(calldata, Either::B(())); - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(code.as_ref(), is_create); - - let contract = bytecode.map(|bytecode| bytecode.contract.clone()); - let contract = contract.as_ref().map(|c| c.borrow()); - - let contract_name = contract.as_ref().map_or_else( - || UNRECOGNIZED_CONTRACT_NAME.to_string(), - |c| c.name.clone(), - ); - - if is_create { - Ok(ContractAndFunctionName { - contract_name, - function_name: Either::B(()), - }) - } else { - match contract { - None => Ok(ContractAndFunctionName { - contract_name, - function_name: Either::A("".to_string()), - }), - Some(contract) => { - let calldata = match calldata { - Either::A(calldata) => calldata, - Either::B(_) => { - unreachable!("calldata should be Some if is_create is false") - } - }; - - let selector = &calldata.get(..4).unwrap_or(&calldata[..]); - - let func = contract.get_function_from_selector(selector); - - let function_name = match func { - Some(func) => match func.r#type { - ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(), - ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(), - _ => func.name.clone(), - }, - None => UNRECOGNIZED_FUNCTION_NAME.to_string(), - }; - - Ok(ContractAndFunctionName { - contract_name, - function_name: Either::A(function_name), - }) - } - } - } - } -} - -#[napi(object)] -pub struct ContractAndFunctionName { - pub contract_name: String, - pub function_name: Either, -} - -#[napi(catch_unwind)] -pub fn initialize_vm_trace_decoder( - mut vm_trace_decoder: ClassInstance, - tracing_config: serde_json::Value, -) -> napi::Result<()> { - let config = serde_json::from_value::(tracing_config).map_err(|e| { - napi::Error::from_reason(format!("Failed to deserialize tracing config: {e:?}")) - })?; - - let Some(build_infos) = config.build_infos else { - return Ok(()); - }; - - for build_info in &build_infos { - let bytecodes = create_models_and_decode_bytecodes( - build_info.solc_version.clone(), - &build_info.input, - &build_info.output, - )?; - - for bytecode in bytecodes { - if config.ignore_contracts == Some(true) - && bytecode.contract.borrow().name.starts_with("Ignored") - { - continue; - } - - vm_trace_decoder.add_bytecode_inner(Rc::new(bytecode)); - } - } - - Ok(()) -} diff --git a/crates/edr_napi/src/trace/vm_tracer.rs b/crates/edr_napi/src/trace/vm_tracer.rs deleted file mode 100644 index 6d418b768..000000000 --- a/crates/edr_napi/src/trace/vm_tracer.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! N-API bindings for the Rust port of `VMTracer` from Hardhat. - -use napi::{ - bindgen_prelude::{Either3, Either4, Undefined}, - Either, Env, JsError, -}; -use napi_derive::napi; - -use crate::trace::{ - message_trace::{ - message_trace_to_napi, CallMessageTrace, CreateMessageTrace, PrecompileMessageTrace, - }, - RawTrace, -}; - -/// N-API bindings for the Rust port of `VMTracer` from Hardhat. -#[napi] -pub struct VMTracer(edr_solidity::vm_tracer::VmTracer); - -#[napi] -impl VMTracer { - #[napi(constructor)] - pub fn new() -> napi::Result { - Ok(Self(edr_solidity::vm_tracer::VmTracer::new())) - } - - /// Observes a trace, collecting information about the execution of the EVM. - #[napi] - pub fn observe(&mut self, trace: &RawTrace) { - self.0.observe(&trace.inner); - } - - // Explicitly return undefined as `Option` by default returns `null` in JS - // and the null/undefined checks we use in JS are strict - #[napi] - pub fn get_last_top_level_message_trace( - &self, - env: Env, - ) -> napi::Result< - Either4, - > { - Ok( - match self - .0 - .get_last_top_level_message_trace_ref() - .map(|x| { - x.try_borrow() - .expect("cannot be executed concurrently with `VMTracer::observe`") - .clone() - }) - .map(|msg| message_trace_to_napi(msg, env)) - .transpose()? - { - Some(Either3::A(precompile)) => Either4::A(precompile), - Some(Either3::B(call)) => Either4::B(call), - Some(Either3::C(create)) => Either4::C(create), - None => Either4::D(()), - }, - ) - } - - // Explicitly return undefined as `Option` by default returns `null` in JS - // and the null/undefined checks we use in JS are strict - #[napi] - pub fn get_last_error(&self) -> Either { - match self.0.get_last_error() { - Some(err) => Either::A(napi::Error::from_reason(err).into()), - None => Either::B(()), - } - } -} diff --git "a/hardhat-tests/test/internal/hardhat-network/\0010\023@8\031\255@8" "b/hardhat-tests/test/internal/hardhat-network/\0010\023@8\031\255@8" deleted file mode 100644 index e69de29bb..000000000 diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index 930e83c11..2cb98afd8 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -9,23 +9,13 @@ import { ConsoleLogs, ConsoleLogger, } from "hardhat/internal/hardhat-network/stack-traces/consoleLogger"; -import { - printMessageTrace, - printStackTrace, -} from "hardhat/internal/hardhat-network/stack-traces/debug"; +import { printStackTrace } from "hardhat/internal/hardhat-network/stack-traces/debug"; import { linkHexStringBytecode } from "hardhat/internal/hardhat-network/stack-traces/library-utils"; -import { - CallMessageTrace, - CreateMessageTrace, - MessageTrace, -} from "hardhat/internal/hardhat-network/stack-traces/message-trace"; import { SolidityStackTrace, SolidityStackTraceEntry, StackTraceEntryType, } from "hardhat/internal/hardhat-network/stack-traces/solidity-stack-trace"; -import { SolidityTracer } from "hardhat/internal/hardhat-network/stack-traces/solidityTracer"; -import { VmTraceDecoderT } from "hardhat/internal/hardhat-network/stack-traces/vm-trace-decoder"; import { SUPPORTED_SOLIDITY_VERSION_RANGE } from "hardhat/internal/hardhat-network/stack-traces/constants"; import { BuildInfo, @@ -480,7 +470,6 @@ async function runTest( }; const logger = new FakeModulesLogger(); - const solidityTracer = new SolidityTracer(); const provider = await instantiateProvider( { enabled: false, From 748b678afe76ddacd27b57bfff3d96017a4c6fcb Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Fri, 6 Dec 2024 10:01:26 +0000 Subject: [PATCH 07/30] remove getContractAndFunctionNameCallback --- crates/edr_napi/index.d.ts | 2 - crates/edr_napi/src/logger.rs | 97 ++++++++++----------------------- crates/edr_napi/src/provider.rs | 16 ++++-- 3 files changed, 41 insertions(+), 74 deletions(-) diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 6e4df5f0e..ba96a2b2c 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -149,8 +149,6 @@ export interface LoggerConfig { /** Whether to enable the logger. */ enable: boolean decodeConsoleLogInputsCallback: (inputs: Buffer[]) => string[] - /** Used to resolve the contract and function name when logging. */ - getContractAndFunctionNameCallback: (code: Buffer, calldata?: Buffer) => ContractAndFunctionName printLineCallback: (message: string, replace: boolean) => void } /** Configuration for a chain */ diff --git a/crates/edr_napi/src/logger.rs b/crates/edr_napi/src/logger.rs index 757fdb4bd..8c02ff310 100644 --- a/crates/edr_napi/src/logger.rs +++ b/crates/edr_napi/src/logger.rs @@ -1,4 +1,7 @@ -use std::{fmt::Display, sync::mpsc::channel}; +use std::{ + fmt::Display, + sync::{mpsc::channel, Arc}, +}; use ansi_term::{Color, Style}; use edr_eth::{ @@ -14,6 +17,7 @@ use edr_evm::{ ExecutionResult, SyncBlock, }; use edr_provider::{ProviderError, TransactionFailure}; +use edr_solidity::vm_trace_decoder::{initialize_vm_trace_decoder, TracingConfig, VmTraceDecoder}; use itertools::izip; use napi::{ threadsafe_function::{ @@ -41,21 +45,12 @@ impl TryCast<(String, Option)> for ContractAndFunctionName { } } -struct ContractAndFunctionNameCall { - code: Bytes, - /// Only present for calls. - calldata: Option, -} - #[napi(object)] pub struct LoggerConfig { /// Whether to enable the logger. pub enable: bool, #[napi(ts_type = "(inputs: Buffer[]) => string[]")] pub decode_console_log_inputs_callback: JsFunction, - #[napi(ts_type = "(code: Buffer, calldata?: Buffer) => ContractAndFunctionName")] - /// Used to resolve the contract and function name when logging. - pub get_contract_and_function_name_callback: JsFunction, #[napi(ts_type = "(message: string, replace: boolean) => void")] pub print_line_callback: JsFunction, } @@ -118,9 +113,13 @@ pub struct Logger { } impl Logger { - pub fn new(env: &Env, config: LoggerConfig) -> napi::Result { + pub fn new( + env: &Env, + config: LoggerConfig, + tracing_config: Arc, + ) -> napi::Result { Ok(Self { - collector: LogCollector::new(env, config)?, + collector: LogCollector::new(env, config, tracing_config)?, }) } } @@ -245,9 +244,8 @@ pub struct CollapsedMethod { #[derive(Clone)] struct LogCollector { + tracing_config: Arc, decode_console_log_inputs_fn: ThreadsafeFunction, ErrorStrategy::Fatal>, - get_contract_and_function_name_fn: - ThreadsafeFunction, indentation: usize, is_enabled: bool, logs: Vec, @@ -257,7 +255,11 @@ struct LogCollector { } impl LogCollector { - pub fn new(env: &Env, config: LoggerConfig) -> napi::Result { + pub fn new( + env: &Env, + config: LoggerConfig, + tracing_config: Arc, + ) -> napi::Result { let mut decode_console_log_inputs_fn = config .decode_console_log_inputs_callback .create_threadsafe_function(0, |ctx: ThreadSafeCallContext>| { @@ -281,34 +283,6 @@ impl LogCollector { // exiting. decode_console_log_inputs_fn.unref(env)?; - let mut get_contract_and_function_name_fn = config - .get_contract_and_function_name_callback - .create_threadsafe_function( - 0, - |ctx: ThreadSafeCallContext| { - // Buffer - let code = ctx - .env - .create_buffer_with_data(ctx.value.code.to_vec())? - .into_unknown(); - - // Option - let calldata = if let Some(calldata) = ctx.value.calldata { - ctx.env - .create_buffer_with_data(calldata.to_vec())? - .into_unknown() - } else { - ctx.env.get_undefined()?.into_unknown() - }; - - Ok(vec![code, calldata]) - }, - )?; - - // Maintain a weak reference to the function to avoid the event loop from - // exiting. - get_contract_and_function_name_fn.unref(env)?; - let mut print_line_fn = config.print_line_callback.create_threadsafe_function( 0, |ctx: ThreadSafeCallContext<(String, bool)>| { @@ -327,8 +301,8 @@ impl LogCollector { print_line_fn.unref(env)?; Ok(Self { + tracing_config, decode_console_log_inputs_fn, - get_contract_and_function_name_fn, indentation: 0, is_enabled: config.enable, logs: Vec::new(), @@ -560,29 +534,18 @@ impl LogCollector { code: Bytes, calldata: Option, ) -> (String, Option) { - let (sender, receiver) = channel(); - - let status = self - .get_contract_and_function_name_fn - .call_with_return_value( - ContractAndFunctionNameCall { code, calldata }, - ThreadsafeFunctionCallMode::Blocking, - move |result: ContractAndFunctionName| { - let contract_and_function_name = result.try_cast(); - sender.send(contract_and_function_name).map_err(|_error| { - napi::Error::new( - Status::GenericFailure, - "Failed to send result from get_contract_and_function_name", - ) - }) - }, - ); - assert_eq!(status, Status::Ok); - - receiver - .recv() - .unwrap() - .expect("Failed call to get_contract_and_function_name") + // TODO this is hyper inefficient. Doing it like this for now because Bytecode + // is not Send. Will refactor. + let mut vm_trace_decoder = VmTraceDecoder::default(); + // TODO remove expect + initialize_vm_trace_decoder(&mut vm_trace_decoder, self.tracing_config.as_ref()) + .expect("can initialize vm trace decoder"); + + let edr_solidity::vm_trace_decoder::ContractAndFunctionName { + contract_name, + function_name, + } = vm_trace_decoder.get_contract_and_function_names_for_call(&code, calldata.as_ref()); + (contract_name, function_name) } fn format(&self, message: impl ToString) -> String { diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 56dd0fac8..80527b032 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -41,16 +41,22 @@ impl Provider { tracing_config: serde_json::Value, #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction, ) -> napi::Result { - let config = edr_provider::ProviderConfig::try_from(config)?; let runtime = runtime::Handle::current(); - let logger = Box::new(Logger::new(&env, logger_config)?); - let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?; - let subscriber_callback = Box::new(move |event| subscriber_callback.call(event)); + let config = edr_provider::ProviderConfig::try_from(config)?; // TODO get actual type as argument let tracing_config: edr_solidity::vm_trace_decoder::TracingConfig = serde_json::from_value(tracing_config)?; + let tracing_config = Arc::new(tracing_config); + + let logger = Box::new(Logger::new( + &env, + logger_config, + Arc::clone(&tracing_config), + )?); + let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?; + let subscriber_callback = Box::new(move |event| subscriber_callback.call(event)); let (deferred, promise) = env.create_deferred()?; runtime.clone().spawn_blocking(move || { @@ -74,7 +80,7 @@ impl Provider { Ok(Provider { provider: Arc::new(provider), runtime, - tracing_config: Arc::new(tracing_config), + tracing_config, #[cfg(feature = "scenarios")] scenario_file, }) From 5277a66564dd8315a926de70d1a90d6e3c8fba97 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 6 Dec 2024 11:03:24 +0100 Subject: [PATCH 08/30] Add link_hex_string_bytecode back to edr_napi --- crates/edr_napi/index.d.ts | 1 + crates/edr_napi/index.js | 3 ++- crates/edr_napi/src/trace.rs | 2 ++ crates/edr_napi/src/trace/library_utils.rs | 6 ++++++ 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 crates/edr_napi/src/trace/library_utils.rs diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index ba96a2b2c..a1123fe30 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -344,6 +344,7 @@ export interface SubscriptionEvent { filterId: bigint result: any } +export declare function linkHexStringBytecode(code: string, address: string, position: number): string export declare function printStackTrace(trace: SolidityStackTrace): void /** Represents the exit code of the EVM. */ export const enum ExitCode { diff --git a/crates/edr_napi/index.js b/crates/edr_napi/index.js index 480c1c1da..03cf23ff7 100644 --- a/crates/edr_napi/index.js +++ b/crates/edr_napi/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, printStackTrace, Exit, ExitCode, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace } = nativeBinding +const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, linkHexStringBytecode, printStackTrace, Exit, ExitCode, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace } = nativeBinding module.exports.SpecId = SpecId module.exports.EdrContext = EdrContext @@ -319,6 +319,7 @@ module.exports.Provider = Provider module.exports.Response = Response module.exports.SuccessReason = SuccessReason module.exports.ExceptionalHalt = ExceptionalHalt +module.exports.linkHexStringBytecode = linkHexStringBytecode module.exports.printStackTrace = printStackTrace module.exports.Exit = Exit module.exports.ExitCode = ExitCode diff --git a/crates/edr_napi/src/trace.rs b/crates/edr_napi/src/trace.rs index 0113da9f7..e97a63ff2 100644 --- a/crates/edr_napi/src/trace.rs +++ b/crates/edr_napi/src/trace.rs @@ -16,6 +16,8 @@ use napi_derive::napi; use crate::result::ExecutionResult; +mod library_utils; + mod debug; mod exit; mod model; diff --git a/crates/edr_napi/src/trace/library_utils.rs b/crates/edr_napi/src/trace/library_utils.rs new file mode 100644 index 000000000..3d08db191 --- /dev/null +++ b/crates/edr_napi/src/trace/library_utils.rs @@ -0,0 +1,6 @@ +use napi_derive::napi; + +#[napi] +pub fn link_hex_string_bytecode(code: String, address: String, position: u32) -> String { + edr_solidity::library_utils::link_hex_string_bytecode(code, &address, position) +} \ No newline at end of file From 8e5955033c991b8ab8dc7aaa919313c189da639c Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 6 Dec 2024 11:04:18 +0100 Subject: [PATCH 09/30] Adapt hardhat patch to latest changes --- patches/hardhat@2.22.15.patch | 221 +++++++++++++++++++++++++++++++++- pnpm-lock.yaml | 24 ++-- 2 files changed, 231 insertions(+), 14 deletions(-) diff --git a/patches/hardhat@2.22.15.patch b/patches/hardhat@2.22.15.patch index ecb3587ad..ce644a840 100644 --- a/patches/hardhat@2.22.15.patch +++ b/patches/hardhat@2.22.15.patch @@ -1,8 +1,100 @@ diff --git a/internal/hardhat-network/provider/provider.js b/internal/hardhat-network/provider/provider.js -index a4b921c8a37b7d5967955d0449df3d05dbe725a8..7ff9eac18cb9a587aa6bd13ff69904cb4b610611 100644 +index a4b921c8a37b7d5967955d0449df3d05dbe725a8..2a5ed80f3defa71f18c0df97b719820622460fba 100644 --- a/internal/hardhat-network/provider/provider.js +++ b/internal/hardhat-network/provider/provider.js -@@ -196,7 +196,7 @@ class EdrProviderWrapper extends events_1.EventEmitter { +@@ -1,52 +1,22 @@ + "use strict"; +-var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { +- if (k2 === undefined) k2 = k; +- var desc = Object.getOwnPropertyDescriptor(m, k); +- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { +- desc = { enumerable: true, get: function() { return m[k]; } }; +- } +- Object.defineProperty(o, k2, desc); +-}) : (function(o, m, k, k2) { +- if (k2 === undefined) k2 = k; +- o[k2] = m[k]; +-})); +-var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { +- Object.defineProperty(o, "default", { enumerable: true, value: v }); +-}) : function(o, v) { +- o["default"] = v; +-}); +-var __importStar = (this && this.__importStar) || function (mod) { +- if (mod && mod.__esModule) return mod; +- var result = {}; +- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); +- __setModuleDefault(result, mod); +- return result; +-}; + var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; + }; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.createHardhatNetworkProvider = exports.EdrProviderWrapper = exports.getNodeConfig = exports.getGlobalEdrContext = exports.DEFAULT_COINBASE = void 0; +-const chalk_1 = __importDefault(require("chalk")); ++const picocolors_1 = __importDefault(require("picocolors")); + const debug_1 = __importDefault(require("debug")); + const events_1 = require("events"); + const fs_extra_1 = __importDefault(require("fs-extra")); +-const t = __importStar(require("io-ts")); + const semver_1 = __importDefault(require("semver")); + const napi_rs_1 = require("../../../common/napi-rs"); + const constants_1 = require("../../constants"); +-const solc_1 = require("../../core/jsonrpc/types/input/solc"); +-const validation_1 = require("../../core/jsonrpc/types/input/validation"); + const errors_1 = require("../../core/providers/errors"); + const http_1 = require("../../core/providers/http"); + const hardforks_1 = require("../../util/hardforks"); +-const compiler_to_model_1 = require("../stack-traces/compiler-to-model"); + const consoleLogger_1 = require("../stack-traces/consoleLogger"); +-const vm_trace_decoder_1 = require("../stack-traces/vm-trace-decoder"); + const constants_2 = require("../stack-traces/constants"); + const solidity_errors_1 = require("../stack-traces/solidity-errors"); +-const solidityTracer_1 = require("../stack-traces/solidityTracer"); +-const vm_tracer_1 = require("../stack-traces/vm-tracer"); + const packageInfo_1 = require("../../util/packageInfo"); + const convertToEdr_1 = require("./utils/convertToEdr"); + const makeCommon_1 = require("./utils/makeCommon"); +@@ -94,18 +64,13 @@ class EdrProviderEventAdapter extends events_1.EventEmitter { + class EdrProviderWrapper extends events_1.EventEmitter { + constructor(_provider, + // we add this for backwards-compatibility with plugins like solidity-coverage +- _node, _vmTraceDecoder, ++ _node, + // The common configuration for EthereumJS VM is not used by EDR, but tests expect it as part of the provider. +- _common, tracingConfig) { ++ _common) { + super(); + this._provider = _provider; + this._node = _node; +- this._vmTraceDecoder = _vmTraceDecoder; + this._common = _common; +- this._failedStackTraces = 0; +- if (tracingConfig !== undefined) { +- (0, vm_trace_decoder_1.initializeVmTraceDecoder)(this._vmTraceDecoder, tracingConfig); +- } + } + static async create(config, loggerConfig, tracingConfig) { + const { Provider } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); +@@ -138,7 +103,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + const eventAdapter = new EdrProviderEventAdapter(); + const printLineFn = loggerConfig.printLineFn ?? logger_1.printLine; + const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? logger_1.replaceLastLine; +- const vmTraceDecoder = new vm_trace_decoder_1.VmTraceDecoder(); + const hardforkName = (0, hardforks_1.getHardforkName)(config.hardfork); + const provider = await Provider.withConfig(getGlobalEdrContext(), { + allowBlocksWithSameTimestamp: config.allowBlocksWithSameTimestamp ?? false, +@@ -186,7 +150,7 @@ class EdrProviderWrapper extends events_1.EventEmitter { + enable: loggerConfig.enabled, + decodeConsoleLogInputsCallback: consoleLogger_1.ConsoleLogger.getDecodedLogs, + getContractAndFunctionNameCallback: (code, calldata) => { +- return vmTraceDecoder.getContractAndFunctionNamesForCall(code, calldata); ++ return { contractName: "", functionName: "" }; + }, + printLineCallback: (message, replace) => { + if (replace) { +@@ -196,14 +160,14 @@ class EdrProviderWrapper extends events_1.EventEmitter { printLineFn(message); } }, @@ -11,3 +103,128 @@ index a4b921c8a37b7d5967955d0449df3d05dbe725a8..7ff9eac18cb9a587aa6bd13ff69904cb eventAdapter.emit("ethEvent", event); }); const minimalEthereumJsNode = { + _vm: (0, minimal_vm_1.getMinimalEthereumJsVm)(provider), + }; + const common = (0, makeCommon_1.makeCommon)(getNodeConfig(config)); +- const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode, vmTraceDecoder, common, tracingConfig); ++ const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode, common); + // Pass through all events from the provider + eventAdapter.addListener("ethEvent", wrapper._ethEventListener.bind(wrapper)); + return wrapper; +@@ -213,12 +177,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + throw new errors_1.InvalidInputError("Hardhat Network doesn't support JSON-RPC params sent as an object"); + } + const params = args.params ?? []; +- if (args.method === "hardhat_addCompilationResult") { +- return this._addCompilationResultAction(...this._addCompilationResultParams(params)); +- } +- else if (args.method === "hardhat_getStackTraceFailuresCount") { +- return this._getStackTraceFailuresCountAction(...this._getStackTraceFailuresCountParams(params)); +- } + const stringifiedArgs = JSON.stringify({ + method: args.method, + params, +@@ -232,12 +190,10 @@ class EdrProviderWrapper extends events_1.EventEmitter { + response = responseObject.data; + } + const needsTraces = this._node._vm.evm.events.eventNames().length > 0 || +- this._node._vm.events.eventNames().length > 0 || +- this._vmTracer !== undefined; ++ this._node._vm.events.eventNames().length > 0; + if (needsTraces) { + const rawTraces = responseObject.traces; + for (const rawTrace of rawTraces) { +- this._vmTracer?.observe(rawTrace); + // For other consumers in JS we need to marshall the entire trace over FFI + const trace = rawTrace.trace(); + // beforeTx event +@@ -272,12 +228,8 @@ class EdrProviderWrapper extends events_1.EventEmitter { + } + if ((0, http_1.isErrorResponse)(response)) { + let error; +- const solidityTrace = responseObject.solidityTrace; +- let stackTrace; +- if (solidityTrace !== null) { +- stackTrace = await this._rawTraceToSolidityStackTrace(solidityTrace); +- } +- if (stackTrace !== undefined) { ++ const stackTrace = responseObject.stackTrace(); ++ if (stackTrace !== null) { + error = (0, solidity_errors_1.encodeSolidityStackTrace)(response.error.message, stackTrace); + // Pass data and transaction hash from the original error + error.data = response.error.data?.data ?? undefined; +@@ -315,14 +267,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + return response.result; + } + } +- /** +- * Sets a `VMTracer` that observes EVM throughout requests. +- * +- * Used for internal stack traces integration tests. +- */ +- setVmTracer(vmTracer) { +- this._vmTracer = vmTracer; +- } + // temporarily added to make smock work with HH+EDR + _setCallOverrideCallback(callback) { + this._callOverrideCallback = callback; +@@ -357,50 +301,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + }; + this.emit("message", message); + } +- _addCompilationResultParams(params) { +- return (0, validation_1.validateParams)(params, t.string, solc_1.rpcCompilerInput, solc_1.rpcCompilerOutput); +- } +- async _addCompilationResultAction(solcVersion, compilerInput, compilerOutput) { +- let bytecodes; +- try { +- bytecodes = (0, compiler_to_model_1.createModelsAndDecodeBytecodes)(solcVersion, compilerInput, compilerOutput); +- } +- catch (error) { +- console.warn(chalk_1.default.yellow("The Hardhat Network tracing engine could not be updated. Run Hardhat with --verbose to learn more.")); +- log("VmTraceDecoder failed to be updated. Please report this to help us improve Hardhat.\n", error); +- return false; +- } +- for (const bytecode of bytecodes) { +- this._vmTraceDecoder.addBytecode(bytecode); +- } +- return true; +- } +- _getStackTraceFailuresCountParams(params) { +- return (0, validation_1.validateParams)(params); +- } +- _getStackTraceFailuresCountAction() { +- return this._failedStackTraces; +- } +- async _rawTraceToSolidityStackTrace(rawTrace) { +- const vmTracer = new vm_tracer_1.VMTracer(); +- vmTracer.observe(rawTrace); +- let vmTrace = vmTracer.getLastTopLevelMessageTrace(); +- const vmTracerError = vmTracer.getLastError(); +- if (vmTrace !== undefined) { +- vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); +- } +- try { +- if (vmTrace === undefined || vmTracerError !== undefined) { +- throw vmTracerError; +- } +- const solidityTracer = new solidityTracer_1.SolidityTracer(); +- return solidityTracer.getStackTrace(vmTrace); +- } +- catch (err) { +- this._failedStackTraces += 1; +- log("Could not generate stack trace. Please report this to help us improve Hardhat.\n", err); +- } +- } + } + exports.EdrProviderWrapper = EdrProviderWrapper; + async function clientVersion(edrClientVersion) { +@@ -433,7 +333,7 @@ async function makeTracingConfig(artifacts) { + }; + } + catch (error) { +- console.warn(chalk_1.default.yellow("Stack traces engine could not be initialized. Run Hardhat with --verbose to learn more.")); ++ console.warn(picocolors_1.default.yellow("Stack traces engine could not be initialized. Run Hardhat with --verbose to learn more.")); + log("Solidity stack traces disabled: Failed to read solc's input and output files. Please report this to help us improve Hardhat.\n", error); + } + } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d61c94f6f..e1524710a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ overrides: patchedDependencies: hardhat@2.22.15: - hash: bv5kqmujiion6gbtrpeh3dexx4 + hash: eklheooma4hcnaltirg5pwganq path: patches/hardhat@2.22.15.patch importers: @@ -129,7 +129,7 @@ importers: version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) hardhat: specifier: 2.22.15 - version: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash: specifier: ^4.17.11 version: 4.17.21 @@ -255,7 +255,7 @@ importers: version: 7.0.1 hardhat: specifier: 2.22.15 - version: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -285,10 +285,10 @@ importers: devDependencies: '@defi-wonderland/smock': specifier: ^2.4.0 - version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@types/node': specifier: ^20.0.0 version: 20.16.1 @@ -300,7 +300,7 @@ importers: version: 5.7.2 hardhat: specifier: 2.22.15 - version: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -3356,16 +3356,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) diff: 5.0.0 ethers: 5.7.2 - hardhat: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash.isequal: 4.5.0 lodash.isequalwith: 4.4.0 rxjs: 7.8.1 @@ -3881,10 +3881,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: ethers: 5.7.2 - hardhat: 2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) '@pkgr/core@0.1.1': {} @@ -5198,7 +5198,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.15(patch_hash=bv5kqmujiion6gbtrpeh3dexx4)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): + hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 From 433ad4d6019ae95c1ad1415ab14347378e1f30bc Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 6 Dec 2024 11:05:15 +0100 Subject: [PATCH 10/30] Remove unused stuff in hardhat-tests --- .../hardhat-network/stack-traces/execution.ts | 6 --- .../hardhat-network/stack-traces/test.ts | 38 ++++++++----------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 06ca2fad1..1fa963087 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -5,14 +5,12 @@ import { toBytes, } from "@nomicfoundation/ethereumjs-util"; -import { MessageTrace } from "hardhat/internal/hardhat-network/stack-traces/message-trace"; import { defaultHardhatNetworkParams } from "hardhat/internal/core/config/default-config"; import { MempoolOrder, TracingConfig, } from "hardhat/internal/hardhat-network/provider/node-types"; import { EdrProviderWrapper } from "hardhat/internal/hardhat-network/provider/provider"; -import { VMTracer } from "hardhat/internal/hardhat-network/stack-traces/vm-tracer"; import { LoggerConfig } from "hardhat/internal/hardhat-network/provider/modules/logger"; import { SolidityStackTrace } from "hardhat/internal/hardhat-network/stack-traces/solidity-stack-trace"; import { Response } from "@nomicfoundation/edr"; @@ -128,10 +126,6 @@ export async function traceTransaction( method: "eth_getCode", params: [bytesToHex(txData.to), "latest"], }); - - // uncomment to see code and calldata - // console.log(code) - // console.log(bytesToHex(txData.data)) } const responseObject: Response = diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index 2cb98afd8..b4fb1688a 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -1,4 +1,4 @@ -import { stackTraceEntryTypeToString } from "@nomicfoundation/edr"; +import { linkHexStringBytecode, stackTraceEntryTypeToString } from "@nomicfoundation/edr"; import { toBytes } from "@nomicfoundation/ethereumjs-util"; import { assert } from "chai"; import { BUILD_INFO_FORMAT_VERSION } from "hardhat/internal/constants"; @@ -9,8 +9,6 @@ import { ConsoleLogs, ConsoleLogger, } from "hardhat/internal/hardhat-network/stack-traces/consoleLogger"; -import { printStackTrace } from "hardhat/internal/hardhat-network/stack-traces/debug"; -import { linkHexStringBytecode } from "hardhat/internal/hardhat-network/stack-traces/library-utils"; import { SolidityStackTrace, SolidityStackTraceEntry, @@ -320,7 +318,7 @@ function compareStackTraces( "message" in actual ? actual.message : "returnData" in actual && - new ReturnData(actual.returnData).isErrorReturnData() + new ReturnData(actual.returnData).isErrorReturnData() ? new ReturnData(actual.returnData).decodeError() : ""; @@ -519,21 +517,21 @@ async function runTest( try { if (tx.stackTrace === undefined) { - assert.isTrue( - stackTraceOrContractAddress === undefined || - typeof stackTraceOrContractAddress === "string", - // FVTODO - `Transaction ${txIndex} shouldn't have failed` - ); + if (!(stackTraceOrContractAddress === undefined || + typeof stackTraceOrContractAddress === "string")) { + assert.fail( + `Transaction ${txIndex} shouldn't have failed` + ); + } } else { assert.isFalse( stackTraceOrContractAddress === undefined || - typeof stackTraceOrContractAddress === "string", + typeof stackTraceOrContractAddress === "string", `Transaction ${txIndex} should have failed` ); } } catch (error) { - // printMessageTrace(decodedTrace); FVTODo + // printMessageTrace(decodedTrace); FVTODO throw error; } @@ -551,12 +549,8 @@ async function runTest( ); if (testDefinition.print !== undefined && testDefinition.print) { console.log(`Transaction ${txIndex} stack trace`); - printStackTrace(stackTraceOrContractAddress); } } catch (err) { - // printMessageTrace(decodedTrace); TODO - printStackTrace(stackTraceOrContractAddress); - throw err; } } @@ -700,13 +694,13 @@ const onlyLatestSolcVersions = const filterSolcVersionBy = (versionRange: string) => - ({ solidityVersion, latestSolcVersion }: SolidityCompiler) => { - if (onlyLatestSolcVersions && latestSolcVersion !== true) { - return false; - } + ({ solidityVersion, latestSolcVersion }: SolidityCompiler) => { + if (onlyLatestSolcVersions && latestSolcVersion !== true) { + return false; + } - return semver.satisfies(solidityVersion, versionRange); - }; + return semver.satisfies(solidityVersion, versionRange); + }; const solidity05Compilers = solidityCompilers.filter( filterSolcVersionBy("^0.5.0") From 4cef55ad3e89d323b851dfbec6fcaffa5d322b17 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Tue, 10 Dec 2024 13:36:36 +0000 Subject: [PATCH 11/30] Refactor for type safety --- crates/edr_napi/index.d.ts | 5 + crates/edr_napi/index.js | 3 +- crates/edr_napi/src/logger.rs | 9 +- crates/edr_napi/src/provider.rs | 41 +- crates/edr_napi/src/trace/exit.rs | 6 +- crates/edr_napi/src/trace/library_utils.rs | 2 +- crates/edr_napi/src/trace/model.rs | 28 + .../src/trace/solidity_stack_trace.rs | 16 +- crates/edr_solidity/src/build_model.rs | 29 +- crates/edr_solidity/src/compiler.rs | 15 +- .../edr_solidity/src/contracts_identifier.rs | 40 +- crates/edr_solidity/src/error_inferrer.rs | 902 +++++++++--------- crates/edr_solidity/src/exit_code.rs | 46 + crates/edr_solidity/src/lib.rs | 19 +- ...ed_inline_internal_functions_heuristics.rs | 44 +- crates/edr_solidity/src/message_trace.rs | 337 ------- crates/edr_solidity/src/nested_trace.rs | 216 +++++ .../edr_solidity/src/nested_trace_decoder.rs | 234 +++++ crates/edr_solidity/src/nested_tracer.rs | 484 ++++++++++ crates/edr_solidity/src/return_data.rs | 10 - .../edr_solidity/src/solidity_stack_trace.rs | 28 +- crates/edr_solidity/src/solidity_tracer.rs | 186 ++-- crates/edr_solidity/src/vm_trace_decoder.rs | 211 ---- crates/edr_solidity/src/vm_tracer.rs | 274 ------ 24 files changed, 1686 insertions(+), 1499 deletions(-) create mode 100644 crates/edr_solidity/src/exit_code.rs delete mode 100644 crates/edr_solidity/src/message_trace.rs create mode 100644 crates/edr_solidity/src/nested_trace.rs create mode 100644 crates/edr_solidity/src/nested_trace_decoder.rs create mode 100644 crates/edr_solidity/src/nested_tracer.rs delete mode 100644 crates/edr_solidity/src/vm_trace_decoder.rs delete mode 100644 crates/edr_solidity/src/vm_tracer.rs diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index a1123fe30..9dce1b6f4 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -614,6 +614,11 @@ export declare class Exit { isError(): boolean getReason(): string } +/** + * Opaque handle to the `Bytecode` struct. + * Only used on the JS side by the `VmTraceDecoder` class. + */ +export declare class BytecodeWrapper { } export declare class ReturnData { readonly value: Uint8Array constructor(value: Uint8Array) diff --git a/crates/edr_napi/index.js b/crates/edr_napi/index.js index 03cf23ff7..9b62e1be7 100644 --- a/crates/edr_napi/index.js +++ b/crates/edr_napi/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, linkHexStringBytecode, printStackTrace, Exit, ExitCode, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace } = nativeBinding +const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, linkHexStringBytecode, printStackTrace, Exit, ExitCode, BytecodeWrapper, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace } = nativeBinding module.exports.SpecId = SpecId module.exports.EdrContext = EdrContext @@ -323,6 +323,7 @@ module.exports.linkHexStringBytecode = linkHexStringBytecode module.exports.printStackTrace = printStackTrace module.exports.Exit = Exit module.exports.ExitCode = ExitCode +module.exports.BytecodeWrapper = BytecodeWrapper module.exports.ContractFunctionType = ContractFunctionType module.exports.ReturnData = ReturnData module.exports.StackTraceEntryType = StackTraceEntryType diff --git a/crates/edr_napi/src/logger.rs b/crates/edr_napi/src/logger.rs index 8c02ff310..e8b5584d2 100644 --- a/crates/edr_napi/src/logger.rs +++ b/crates/edr_napi/src/logger.rs @@ -17,7 +17,7 @@ use edr_evm::{ ExecutionResult, SyncBlock, }; use edr_provider::{ProviderError, TransactionFailure}; -use edr_solidity::vm_trace_decoder::{initialize_vm_trace_decoder, TracingConfig, VmTraceDecoder}; +use edr_solidity::nested_trace_decoder::{NestedTraceDecoder, TracingConfig}; use itertools::izip; use napi::{ threadsafe_function::{ @@ -536,12 +536,11 @@ impl LogCollector { ) -> (String, Option) { // TODO this is hyper inefficient. Doing it like this for now because Bytecode // is not Send. Will refactor. - let mut vm_trace_decoder = VmTraceDecoder::default(); // TODO remove expect - initialize_vm_trace_decoder(&mut vm_trace_decoder, self.tracing_config.as_ref()) - .expect("can initialize vm trace decoder"); + let mut vm_trace_decoder = + NestedTraceDecoder::new(&self.tracing_config).expect("can initialize vm trace decoder"); - let edr_solidity::vm_trace_decoder::ContractAndFunctionName { + let edr_solidity::nested_trace_decoder::ContractAndFunctionName { contract_name, function_name, } = vm_trace_decoder.get_contract_and_function_names_for_call(&code, calldata.as_ref()); diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 80527b032..b79132e45 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -10,7 +10,6 @@ use napi_derive::napi; use self::config::ProviderConfig; use crate::{ call_override::CallOverrideCallback, - cast::TryCast, context::EdrContext, logger::{Logger, LoggerConfig, LoggerError}, subscribe::SubscriberCallback, @@ -22,7 +21,7 @@ use crate::{ pub struct Provider { provider: Arc>, runtime: runtime::Handle, - tracing_config: Arc, + tracing_config: Arc, #[cfg(feature = "scenarios")] scenario_file: Option>, } @@ -46,7 +45,7 @@ impl Provider { let config = edr_provider::ProviderConfig::try_from(config)?; // TODO get actual type as argument - let tracing_config: edr_solidity::vm_trace_decoder::TracingConfig = + let tracing_config: edr_solidity::nested_trace_decoder::TracingConfig = serde_json::from_value(tracing_config)?; let tracing_config = Arc::new(tracing_config); @@ -246,7 +245,7 @@ impl Provider { #[derive(Debug)] struct SolidityTraceData { trace: Arc, - config: Arc, + config: Arc, } #[napi] @@ -285,31 +284,35 @@ impl Response { let Some(SolidityTraceData { trace, config }) = &self.solidity_trace else { return Ok(None); }; - let mut vm_tracer = edr_solidity::vm_tracer::VmTracer::new(); - vm_tracer.observe(trace); - - let mut vm_trace = vm_tracer.get_last_top_level_message_trace(); - let vm_tracer_error = vm_tracer.get_last_error(); - - let mut vm_trace_decoder = edr_solidity::vm_trace_decoder::VmTraceDecoder::new(); - edr_solidity::vm_trace_decoder::initialize_vm_trace_decoder(&mut vm_trace_decoder, config)?; + let hierarchical_trace = + edr_solidity::nested_tracer::convert_trace_messages_to_hierarchical_trace( + trace.as_ref().clone(), + ); - vm_trace = vm_trace.map(|trace| vm_trace_decoder.try_to_decode_message_trace(trace)); + if let Some(vm_trace) = hierarchical_trace.result { + let mut nested_trace_decoder = + edr_solidity::nested_trace_decoder::NestedTraceDecoder::new(config).map_err( + |err| { + napi::Error::from_reason(format!( + "Error initializing trace decoder: '{err}'" + )) + }, + )?; - if let Some(vm_trace) = vm_trace { - let stack_trace = - edr_solidity::solidity_tracer::get_stack_trace(vm_trace).map_err(|err| { + let decoded_trace = nested_trace_decoder.try_to_decode_message_trace(vm_trace); + let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace) + .map_err(|err| { napi::Error::from_reason(format!( - "Error converting to solidity stack trace: {err}" + "Error converting to solidity stack trace: '{err}'" )) })?; let stack_trace = stack_trace .into_iter() - .map(|stack_trace| stack_trace.try_cast()) + .map(super::cast::TryCast::try_cast) .collect::, _>>()?; Ok(Some(Either::A(stack_trace))) - } else if let Some(vm_tracer_error) = vm_tracer_error { + } else if let Some(vm_tracer_error) = hierarchical_trace.error { Ok(Some(Either::B(vm_tracer_error.to_string()))) } else { Ok(None) diff --git a/crates/edr_napi/src/trace/exit.rs b/crates/edr_napi/src/trace/exit.rs index c09424dcd..2d6b65222 100644 --- a/crates/edr_napi/src/trace/exit.rs +++ b/crates/edr_napi/src/trace/exit.rs @@ -51,9 +51,9 @@ impl fmt::Display for ExitCode { } #[allow(clippy::fallible_impl_from)] // naively ported for now -impl From for ExitCode { - fn from(code: edr_solidity::message_trace::ExitCode) -> Self { - use edr_solidity::message_trace::ExitCode; +impl From for ExitCode { + fn from(code: edr_solidity::exit_code::ExitCode) -> Self { + use edr_solidity::exit_code::ExitCode; match code { ExitCode::Success => Self::SUCCESS, diff --git a/crates/edr_napi/src/trace/library_utils.rs b/crates/edr_napi/src/trace/library_utils.rs index 3d08db191..7d0fbf957 100644 --- a/crates/edr_napi/src/trace/library_utils.rs +++ b/crates/edr_napi/src/trace/library_utils.rs @@ -3,4 +3,4 @@ use napi_derive::napi; #[napi] pub fn link_hex_string_bytecode(code: String, address: String, position: u32) -> String { edr_solidity::library_utils::link_hex_string_bytecode(code, &address, position) -} \ No newline at end of file +} diff --git a/crates/edr_napi/src/trace/model.rs b/crates/edr_napi/src/trace/model.rs index d08ded847..b1495a48c 100644 --- a/crates/edr_napi/src/trace/model.rs +++ b/crates/edr_napi/src/trace/model.rs @@ -1,6 +1,34 @@ +use std::rc::Rc; + +use edr_solidity::build_model::ContractMetadata; use napi_derive::napi; use serde::Serialize; +/// Opaque handle to the `Bytecode` struct. +/// Only used on the JS side by the `VmTraceDecoder` class. +// NOTE: Needed, because we store the resolved `Bytecode` in the MessageTrace +// JS plain objects and those need a dedicated (class) type. +#[napi] +pub struct BytecodeWrapper(pub(crate) Rc); + +impl BytecodeWrapper { + pub fn new(bytecode: Rc) -> Self { + Self(bytecode) + } + + pub fn inner(&self) -> &Rc { + &self.0 + } +} + +impl std::ops::Deref for BytecodeWrapper { + type Target = ContractMetadata; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Debug, PartialEq, Eq, Serialize)] #[allow(non_camel_case_types)] // intentionally mimicks the original case in TS #[allow(clippy::upper_case_acronyms)] diff --git a/crates/edr_napi/src/trace/solidity_stack_trace.rs b/crates/edr_napi/src/trace/solidity_stack_trace.rs index 4e7542f36..c2d4aa428 100644 --- a/crates/edr_napi/src/trace/solidity_stack_trace.rs +++ b/crates/edr_napi/src/trace/solidity_stack_trace.rs @@ -636,7 +636,7 @@ impl TryCast for edr_solidity::solidity_stack_trace::St } StackTraceEntry::PrecompileError { precompile } => PrecompileErrorStackTraceEntry { type_: StackTraceEntryTypeConst, - precompile: precompile.into(), + precompile, source_reference: None, } .into(), @@ -657,7 +657,7 @@ impl TryCast for edr_solidity::solidity_stack_trace::St } => PanicErrorStackTraceEntry { type_: StackTraceEntryTypeConst, error_code: u256_to_bigint(&error_code), - source_reference: source_reference.map(|x| x.into()), + source_reference: source_reference.map(std::convert::Into::into), } .into(), StackTraceEntry::CustomError { @@ -665,7 +665,7 @@ impl TryCast for edr_solidity::solidity_stack_trace::St source_reference, } => CustomErrorStackTraceEntry { type_: StackTraceEntryTypeConst, - message: message.into(), + message, source_reference: source_reference.into(), } .into(), @@ -770,21 +770,21 @@ impl TryCast for edr_solidity::solidity_stack_trace::St StackTraceEntry::OtherExecutionError { source_reference } => { OtherExecutionErrorStackTraceEntry { type_: StackTraceEntryTypeConst, - source_reference: source_reference.map(|x| x.into()), + source_reference: source_reference.map(std::convert::Into::into), } .into() } StackTraceEntry::UnmappedSolc0_6_3RevertError { source_reference } => { UnmappedSolc063RevertErrorStackTraceEntry { type_: StackTraceEntryTypeConst, - source_reference: source_reference.map(|x| x.into()), + source_reference: source_reference.map(std::convert::Into::into), } .into() } StackTraceEntry::ContractTooLargeError { source_reference } => { ContractTooLargeErrorStackTraceEntry { type_: StackTraceEntryTypeConst, - source_reference: source_reference.map(|x| x.into()), + source_reference: source_reference.map(std::convert::Into::into), } .into() } @@ -793,14 +793,14 @@ impl TryCast for edr_solidity::solidity_stack_trace::St source_reference, } => InternalFunctionCallStackEntry { type_: StackTraceEntryTypeConst, - pc: pc.into(), + pc, source_reference: source_reference.into(), } .into(), StackTraceEntry::ContractCallRunOutOfGasError { source_reference } => { ContractCallRunOutOfGasError { type_: StackTraceEntryTypeConst, - source_reference: source_reference.map(|x| x.into()), + source_reference: source_reference.map(std::convert::Into::into), } .into() } diff --git a/crates/edr_solidity/src/build_model.rs b/crates/edr_solidity/src/build_model.rs index c745dfe53..1df4bd73a 100644 --- a/crates/edr_solidity/src/build_model.rs +++ b/crates/edr_solidity/src/build_model.rs @@ -7,7 +7,7 @@ //! - related contract and free [`ContractFunction`]s and their name, location //! and parameters //! - related [`CustomError`]s and their name, location and parameters -//! - the resolved [`Bytecode`] of the contract +//! - the resolved [`ContractMetadata`] of the contract //! - related resolved [`Instruction`]s and their location use std::{ @@ -133,7 +133,7 @@ impl SourceLocation { /// # Panics /// This function panics if the source location is dangling, i.e. source /// files mapping has been dropped (currently only owned by the - /// [`Bytecode`]). + /// [`ContractMetadata`]). pub fn file(&self) -> Rc> { match self.sources.upgrade() { Some(ref sources) => sources.get(&self.file_id).unwrap().clone(), @@ -294,7 +294,7 @@ impl CustomError { } /// A decoded EVM instruction. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Instruction { /// The program counter (PC) of the instruction in a bytecode. pub pc: u32, @@ -321,15 +321,20 @@ pub enum JumpType { InternalJump, } +/// A [`ContractMetadata`] error. #[derive(Clone, Debug, thiserror::Error)] -pub enum BytecodeError { +pub enum ContractMetadataError { + /// The instruction was not found at the provided program counter (PC). #[error("Instruction not found at PC {pc}")] - InstructionNotFound { pc: u32 }, + InstructionNotFound { + /// The program counter (PC) of the instruction. + pc: u32, + }, } /// A resolved bytecode. #[derive(Debug)] -pub struct Bytecode { +pub struct ContractMetadata { pc_to_instruction: HashMap, // This owns the source files transitively used by the source locations @@ -350,8 +355,8 @@ pub struct Bytecode { pub compiler_version: String, } -impl Bytecode { - /// Creates a new [`Bytecode`] with the provided arguments. +impl ContractMetadata { + /// Creates a new [`ContractMetadata`] with the provided arguments. #[allow(clippy::too_many_arguments)] pub fn new( sources: Rc, @@ -362,13 +367,13 @@ impl Bytecode { library_address_positions: Vec, immutable_references: Vec, compiler_version: String, - ) -> Bytecode { + ) -> ContractMetadata { let mut pc_to_instruction = HashMap::new(); for inst in instructions { pc_to_instruction.insert(inst.pc, inst); } - Bytecode { + ContractMetadata { pc_to_instruction, _sources: sources, contract, @@ -381,10 +386,10 @@ impl Bytecode { } /// Returns the [`Instruction`] at the provided program counter (PC). - pub fn get_instruction(&self, pc: u32) -> Result<&Instruction, BytecodeError> { + pub fn get_instruction(&self, pc: u32) -> Result<&Instruction, ContractMetadataError> { self.pc_to_instruction .get(&pc) - .ok_or_else(|| BytecodeError::InstructionNotFound { pc }) + .ok_or(ContractMetadataError::InstructionNotFound { pc }) } /// Returns the [`Instruction`] at the provided program counter (PC). The diff --git a/crates/edr_solidity/src/compiler.rs b/crates/edr_solidity/src/compiler.rs index c78b86b0b..9127b7306 100644 --- a/crates/edr_solidity/src/compiler.rs +++ b/crates/edr_solidity/src/compiler.rs @@ -10,8 +10,9 @@ use indexmap::IndexMap; use crate::{ artifacts::{CompilerInput, CompilerOutput, CompilerOutputBytecode, ContractAbiEntry}, build_model::{ - BuildModel, BuildModelSources, Bytecode, Contract, ContractFunction, ContractFunctionType, - ContractFunctionVisibility, ContractKind, CustomError, SourceFile, SourceLocation, + BuildModel, BuildModelSources, Contract, ContractFunction, ContractFunctionType, + ContractFunctionVisibility, ContractKind, ContractMetadata, CustomError, SourceFile, + SourceLocation, }, library_utils::{get_library_address_positions, normalize_compiler_output_bytecode}, source_map::decode_instructions, @@ -28,7 +29,7 @@ pub fn create_models_and_decode_bytecodes( solc_version: String, compiler_input: &CompilerInput, compiler_output: &CompilerOutput, -) -> anyhow::Result> { +) -> anyhow::Result> { let build_model = create_sources_model_from_ast(compiler_output, compiler_input)?; let build_model = Rc::new(build_model); @@ -594,7 +595,7 @@ fn ast_src_to_source_location( } fn correct_selectors( - bytecodes: &[Bytecode], + bytecodes: &[ContractMetadata], compiler_output: &CompilerOutput, ) -> anyhow::Result<()> { for bytecode in bytecodes.iter().filter(|b| !b.is_deployment) { @@ -660,7 +661,7 @@ fn decode_evm_bytecode( is_deployment: bool, compiler_bytecode: &CompilerOutputBytecode, build_model: &Rc, -) -> anyhow::Result { +) -> anyhow::Result { let library_address_positions = get_library_address_positions(compiler_bytecode); let immutable_references = compiler_bytecode @@ -688,7 +689,7 @@ fn decode_evm_bytecode( is_deployment, ); - Ok(Bytecode::new( + Ok(ContractMetadata::new( Rc::clone(&build_model.file_id_to_source_file), contract, is_deployment, @@ -704,7 +705,7 @@ fn decode_bytecodes( solc_version: String, compiler_output: &CompilerOutput, build_model: &Rc, -) -> anyhow::Result> { +) -> anyhow::Result> { let mut bytecodes = Vec::new(); for contract in build_model.contract_id_to_contract.values() { diff --git a/crates/edr_solidity/src/contracts_identifier.rs b/crates/edr_solidity/src/contracts_identifier.rs index 57facbbaf..7e94aed3b 100644 --- a/crates/edr_solidity/src/contracts_identifier.rs +++ b/crates/edr_solidity/src/contracts_identifier.rs @@ -10,12 +10,12 @@ use std::{borrow::Cow, collections::HashMap, rc::Rc}; use edr_eth::Address; use edr_evm::interpreter::OpCode; -use crate::build_model::Bytecode; +use crate::build_model::ContractMetadata; /// The result of searching for a bytecode in a [`BytecodeTrie`]. enum TrieSearch<'a> { /// An exact match was found. - ExactHit(Rc), + ExactHit(Rc), /// No exact match found; a node with the longest prefix is returned. LongestPrefixNode(&'a BytecodeTrie), } @@ -27,8 +27,8 @@ enum TrieSearch<'a> { #[derive(Clone)] struct BytecodeTrie { child_nodes: HashMap>, - descendants: Vec>, - match_: Option>, + descendants: Vec>, + match_: Option>, depth: Option, } @@ -42,7 +42,7 @@ impl BytecodeTrie { } } - fn add(&mut self, bytecode: Rc) { + fn add(&mut self, bytecode: Rc) { let mut cursor = self; let bytecode_normalized_code = &bytecode.normalized_code; @@ -118,7 +118,7 @@ fn is_matching_metadata(code: &[u8], last_byte: u32) -> bool { /// A data structure that allows searching for well-known bytecodes. pub struct ContractsIdentifier { trie: BytecodeTrie, - cache: HashMap, Rc>, + cache: HashMap, Rc>, enable_cache: bool, } @@ -141,12 +141,16 @@ impl ContractsIdentifier { } /// Adds a bytecode to the tree. - pub fn add_bytecode(&mut self, bytecode: Rc) { + pub fn add_bytecode(&mut self, bytecode: Rc) { self.trie.add(bytecode); self.cache.clear(); } - fn search_bytecode_from_root(&mut self, is_create: bool, code: &[u8]) -> Option> { + fn search_bytecode_from_root( + &mut self, + is_create: bool, + code: &[u8], + ) -> Option> { let normalize_libraries = true; let first_byte_to_search = 0; @@ -165,7 +169,7 @@ impl ContractsIdentifier { normalize_libraries: bool, trie: &BytecodeTrie, first_byte_to_search: u32, - ) -> Option> { + ) -> Option> { let search_result = match trie.search(code, first_byte_to_search) { None => return None, Some(TrieSearch::ExactHit(bytecode)) => return Some(bytecode.clone()), @@ -257,7 +261,11 @@ impl ContractsIdentifier { } /// Searches for a bytecode that matches the given (call/create) code. - pub fn get_bytecode_for_call(&mut self, code: &[u8], is_create: bool) -> Option> { + pub fn get_bytecode_for_call( + &mut self, + code: &[u8], + is_create: bool, + ) -> Option> { let normalized_code = normalize_library_runtime_bytecode_if_necessary(code); if self.enable_cache { @@ -332,7 +340,7 @@ mod tests { ))) } - fn create_test_bytecode(normalized_code: Vec) -> Rc { + fn create_test_bytecode(normalized_code: Vec) -> Rc { let sources = create_sources(); let contract = create_test_contract(); let is_deployment = false; @@ -341,7 +349,7 @@ mod tests { let library_offsets = vec![]; let immutable_references = vec![]; - Rc::new(Bytecode::new( + Rc::new(ContractMetadata::new( sources, contract, is_deployment, @@ -353,7 +361,7 @@ mod tests { )) } - fn create_test_deployment_bytecode(normalized_code: Vec) -> Rc { + fn create_test_deployment_bytecode(normalized_code: Vec) -> Rc { let sources = create_sources(); let contract = create_test_contract(); let is_deployment = true; @@ -362,7 +370,7 @@ mod tests { let library_offsets = vec![]; let immutable_references = vec![]; - Rc::new(Bytecode::new( + Rc::new(ContractMetadata::new( sources, contract, is_deployment, @@ -378,14 +386,14 @@ mod tests { normalized_code: Vec, library_offsets: Vec, immutable_references: Vec, - ) -> Rc { + ) -> Rc { let sources = create_sources(); let contract = create_test_contract(); let is_deployment = false; let instructions = vec![]; - Rc::new(Bytecode::new( + Rc::new(ContractMetadata::new( sources, contract, is_deployment, diff --git a/crates/edr_solidity/src/error_inferrer.rs b/crates/edr_solidity/src/error_inferrer.rs index 7d29023dd..a6fb5a21c 100644 --- a/crates/edr_solidity/src/error_inferrer.rs +++ b/crates/edr_solidity/src/error_inferrer.rs @@ -3,16 +3,16 @@ use std::{borrow::Cow, collections::HashSet, mem}; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use edr_eth::U256; use edr_evm::{hex, interpreter::OpCode, HaltReason}; -use either::Either; use semver::{Version, VersionReq}; use crate::{ build_model::{ - Bytecode, BytecodeError, ContractFunction, ContractFunctionType, ContractKind, Instruction, - JumpType, SourceLocation, + ContractFunction, ContractFunctionType, ContractKind, ContractMetadata, + ContractMetadataError, Instruction, JumpType, SourceLocation, }, - message_trace::{ - CallMessageTrace, CreateMessageTrace, ExitCode, MessageTrace, MessageTraceStep, + exit_code::ExitCode, + nested_trace::{ + CallMessage, CreateMessage, CreateOrCallMessageRef, NestedTrace, NestedTraceStep, }, return_data::ReturnData, solidity_stack_trace::{ @@ -37,29 +37,40 @@ pub enum Heuristic { Miss(Vec), } +/// Data that is used to infer the stack trace of a submessage. #[derive(Clone, Debug)] -pub struct SubmessageData { - pub message_trace: MessageTrace, +pub(crate) struct SubmessageData { + pub message_trace: NestedTrace, pub stacktrace: Vec, pub step_index: u32, } +/// Errors that can occur during the inference of the stack trace. #[derive(Debug, thiserror::Error)] pub enum InferrerError { + /// Errors that can occur when decoding the ABI. #[error("{0}")] Abi(String), + /// Errors that can occur when decoding the contract metadata. #[error(transparent)] - BytecodeError(#[from] BytecodeError), + ContractMetadata(#[from] ContractMetadataError), + /// Invalid input or logic error: Expected an EVM step. #[error("Expected EVM step")] ExpectedEvmStep, + /// Invalid input or logic error: Missing contract metadata. #[error("Missing contract")] MissingContract, + /// Invalid input or logic error: The call trace has no functionJumpdest but + /// has already jumped into a function. #[error("call trace has no functionJumpdest but has already jumped into a function")] - MissingFunctionJumpDest(CallMessageTrace), + MissingFunctionJumpDest(CallMessage), + /// Invalid input or logic error: Missing source reference. #[error("Missing source reference")] MissingSourceReference, + /// Serde JSON error. #[error(transparent)] SerdeJson(#[from] serde_json::Error), + /// Solidity types error. #[error(transparent)] SolidityTypes(#[from] alloy_sol_types::Error), } @@ -72,7 +83,7 @@ impl From for InferrerError { } } -pub fn filter_redundant_frames( +pub(crate) fn filter_redundant_frames( stacktrace: Vec, ) -> Result, InferrerError> { // To work around the borrow checker, we'll collect the indices of the frames we @@ -150,8 +161,8 @@ pub fn filter_redundant_frames( .collect()) } -pub fn infer_after_tracing( - trace: &Either, +pub(crate) fn infer_after_tracing( + trace: CreateOrCallMessageRef<'_>, stacktrace: Vec, function_jumpdests: &[&Instruction], jumped_into_function: bool, @@ -167,12 +178,6 @@ pub fn infer_after_tracing( }; } - // TODO clean this up - let trace = match trace { - Either::Left(call) => Either::Left(call), - Either::Right(create) => Either::Right(create), - }; - let result = check_last_submessage(trace, stacktrace, last_submessage_data)?; let stacktrace = return_if_hit!(result); @@ -197,15 +202,18 @@ pub fn infer_after_tracing( Ok(stacktrace) } -pub fn infer_before_tracing_call_message( - trace: &CallMessageTrace, +pub(crate) fn infer_before_tracing_call_message( + trace: &CallMessage, ) -> Result>, InferrerError> { if is_direct_library_call(trace)? { return Ok(Some(get_direct_library_call_error_stack_trace(trace)?)); } - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let called_function = contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); @@ -214,20 +222,18 @@ pub fn infer_before_tracing_call_message( if is_function_not_payable_error(trace, called_function)? { return Ok(Some(vec![StackTraceEntry::FunctionNotPayableError { source_reference: get_function_start_source_reference( - Either::Left(trace), + trace.into(), called_function, )?, - value: trace.value().clone(), - } - .into()])); + value: trace.value, + }])); } } let called_function = called_function.map(AsRef::as_ref); if is_missing_function_and_fallback_error(trace, called_function)? { - let source_reference = - get_contract_start_without_function_source_reference(Either::Left(trace))?; + let source_reference = get_contract_start_without_function_source_reference(trace.into())?; if empty_calldata_and_no_receive(trace)? { return Ok(Some(vec![StackTraceEntry::MissingFallbackOrReceiveError { @@ -247,27 +253,27 @@ pub fn infer_before_tracing_call_message( return Ok(Some(vec![ StackTraceEntry::FallbackNotPayableAndNoReceiveError { source_reference, - value: trace.value().clone(), + value: trace.value, }, ])); } return Ok(Some(vec![StackTraceEntry::FallbackNotPayableError { source_reference, - value: trace.value().clone(), + value: trace.value, }])); } Ok(None) } -pub fn infer_before_tracing_create_message( - trace: &CreateMessageTrace, +pub(crate) fn infer_before_tracing_create_message( + trace: &CreateMessage, ) -> Result>, InferrerError> { if is_constructor_not_payable_error(trace)? { return Ok(Some(vec![StackTraceEntry::FunctionNotPayableError { source_reference: get_constructor_start_source_reference(trace)?, - value: trace.value().clone(), + value: trace.value, }])); } @@ -280,11 +286,11 @@ pub fn infer_before_tracing_create_message( Ok(None) } -pub fn instruction_to_callstack_stack_trace_entry( - bytecode: &Bytecode, +pub(crate) fn instruction_to_callstack_stack_trace_entry( + contract_meta: &ContractMetadata, inst: &Instruction, ) -> Result { - let contract = bytecode.contract.borrow(); + let contract = contract_meta.contract.borrow(); // This means that a jump is made from within an internal solc function. // These are normally made from yul code, so they don't map to any Solidity @@ -311,12 +317,13 @@ pub fn instruction_to_callstack_stack_trace_entry( }; if let Some(func) = inst_location.get_containing_function() { - let source_reference = source_location_to_source_reference(bytecode, Some(inst_location)) - .ok_or(InferrerError::MissingSourceReference)?; + let source_reference = + source_location_to_source_reference(contract_meta, Some(inst_location)) + .ok_or(InferrerError::MissingSourceReference)?; return Ok(StackTraceEntry::CallstackEntry { source_reference, - function_type: func.r#type.into(), + function_type: func.r#type, }); }; @@ -335,17 +342,17 @@ pub fn instruction_to_callstack_stack_trace_entry( inst_location.offset + inst_location.length, ), }, - function_type: ContractFunctionType::Function.into(), + function_type: ContractFunctionType::Function, }) } fn call_instruction_to_call_failed_to_execute_stack_trace_entry( - bytecode: &Bytecode, + contract_meta: &ContractMetadata, call_inst: &Instruction, ) -> Result { let location = call_inst.location.as_deref(); - let source_reference = source_location_to_source_reference(bytecode, location) + let source_reference = source_location_to_source_reference(contract_meta, location) .ok_or(InferrerError::MissingSourceReference)?; // Calls only happen within functions @@ -353,11 +360,11 @@ fn call_instruction_to_call_failed_to_execute_stack_trace_entry( } fn check_contract_too_large( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result>, InferrerError> { - if let Either::Right(create) = trace { + if let CreateOrCallMessageRef::Create(create) = trace { if matches!( - create.exit(), + create.exit, ExitCode::Halt(HaltReason::CreateContractSizeLimit) ) { return Ok(Some(vec![StackTraceEntry::ContractTooLargeError { @@ -369,17 +376,15 @@ fn check_contract_too_large( } fn check_custom_errors( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, stacktrace: Vec, last_instruction: &Instruction, ) -> Result { - let (bytecode, return_data) = match &trace { - Either::Left(call) => (call.bytecode(), call.return_data()), - Either::Right(create) => (create.bytecode(), create.return_data()), - }; - - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); + let return_data = trace.return_data(); let return_data = ReturnData::new(return_data.clone()); @@ -421,8 +426,7 @@ fn check_custom_errors( trace, last_instruction, error_message, - )? - .into(), + )?, ); fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit) @@ -430,15 +434,13 @@ fn check_custom_errors( /// Check if the last call/create that was done failed. fn check_failed_last_call( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, stacktrace: Vec, ) -> Result { - let (bytecode, steps) = match &trace { - Either::Left(call) => (call.bytecode(), call.steps()), - Either::Right(create) => (create.bytecode(), create.steps()), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); if steps.is_empty() { return Ok(Heuristic::Miss(stacktrace)); @@ -446,18 +448,20 @@ fn check_failed_last_call( for step_index in (0..steps.len() - 1).rev() { let (step, next_step) = match &steps[step_index..][..2] { - &[MessageTraceStep::Evm(ref step), ref next_step] => (step, next_step), + &[NestedTraceStep::Evm(ref step), ref next_step] => (step, next_step), _ => return Ok(Heuristic::Miss(stacktrace)), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = contract_meta.get_instruction(step.pc)?; - if let (OpCode::CALL | OpCode::CREATE, MessageTraceStep::Evm(_)) = (inst.opcode, next_step) - { + if let (OpCode::CALL | OpCode::CREATE, NestedTraceStep::Evm(_)) = (inst.opcode, next_step) { if is_call_failed_error(trace, step_index as u32, inst)? { let mut inferred_stacktrace = stacktrace.clone(); inferred_stacktrace.push( - call_instruction_to_call_failed_to_execute_stack_trace_entry(bytecode, inst)?, + call_instruction_to_call_failed_to_execute_stack_trace_entry( + &contract_meta, + inst, + )?, ); return Ok(Heuristic::Hit(fix_initial_modifier( @@ -472,27 +476,26 @@ fn check_failed_last_call( } fn check_last_instruction( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, stacktrace: Vec, function_jumpdests: &[&Instruction], jumped_into_function: bool, ) -> Result { - let (bytecode, steps) = match &trace { - Either::Left(call) => (call.bytecode(), call.steps()), - Either::Right(create) => (create.bytecode(), create.steps()), - }; - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); if steps.is_empty() { return Ok(Heuristic::Miss(stacktrace)); } let last_step = match steps.last() { - Some(MessageTraceStep::Evm(step)) => step, + Some(NestedTraceStep::Evm(step)) => step, _ => panic!("This should not happen: MessageTrace ends with a subtrace"), }; - let last_instruction = bytecode.get_instruction(last_step.pc)?; + let last_instruction = contract_meta.get_instruction(last_step.pc)?; let revert_or_invalid_stacktrace = check_revert_or_invalid_opcode( trace, @@ -506,8 +509,8 @@ fn check_last_instruction( Heuristic::Miss(stacktrace) => stacktrace, }; - let (Either::Left(trace @ CallMessageTrace { ref calldata, .. }), false) = - (&trace, jumped_into_function) + let (CreateOrCallMessageRef::Call(trace @ CallMessage { calldata, .. }), false) = + (trace, jumped_into_function) else { return Ok(Heuristic::Miss(stacktrace)); }; @@ -516,11 +519,11 @@ fn check_last_instruction( || has_failed_inside_the_receive_function(trace)? { let frame = instruction_within_function_to_revert_stack_trace_entry( - Either::Left(trace), + CreateOrCallMessageRef::Call(trace), last_instruction, )?; - return Ok(Heuristic::Hit(vec![frame.into()])); + return Ok(Heuristic::Hit(vec![frame])); } // Sometimes we do fail inside of a function but there's no jump into @@ -530,18 +533,18 @@ fn check_last_instruction( if let Some(failing_function) = failing_function { let frame = StackTraceEntry::RevertError { source_reference: get_function_start_source_reference( - Either::Left(trace), + CreateOrCallMessageRef::Call(trace), &failing_function, )?, - return_data: trace.return_data().clone(), + return_data: trace.return_data.clone(), is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID, }; - return Ok(Heuristic::Hit(vec![frame.into()])); + return Ok(Heuristic::Hit(vec![frame])); } } - let contract = bytecode.contract.borrow(); + let contract = contract_meta.contract.borrow(); let selector = calldata.get(..4).unwrap_or(&calldata[..]); let calldata = &calldata.get(4..).unwrap_or(&[]); @@ -560,40 +563,38 @@ fn check_last_instruction( if !is_valid_calldata { let frame = StackTraceEntry::InvalidParamsError { source_reference: get_function_start_source_reference( - Either::Left(trace), + CreateOrCallMessageRef::Call(trace), called_function, )?, }; - return Ok(Heuristic::Hit(vec![frame.into()])); + return Ok(Heuristic::Hit(vec![frame])); } } - if solidity_0_6_3_maybe_unmapped_revert(Either::Left(trace))? { + if solidity_0_6_3_maybe_unmapped_revert(CreateOrCallMessageRef::Call(trace))? { let revert_frame = solidity_0_6_3_get_frame_for_unmapped_revert_before_function(trace)?; if let Some(revert_frame) = revert_frame { - return Ok(Heuristic::Hit(vec![revert_frame.into()])); + return Ok(Heuristic::Hit(vec![revert_frame])); } } let frame = get_other_error_before_called_function_stack_trace_entry(trace)?; - Ok(Heuristic::Hit(vec![frame.into()])) + Ok(Heuristic::Hit(vec![frame])) } /// Check if the last submessage can be used to generate the stack trace. fn check_last_submessage( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, stacktrace: Vec, last_submessage_data: Option, ) -> Result { - let (bytecode, steps) = match &trace { - Either::Left(call) => (call.bytecode(), call.steps()), - Either::Right(create) => (create.bytecode(), create.steps()), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); let Some(last_submessage_data) = last_submessage_data else { return Ok(Heuristic::Miss(stacktrace)); @@ -603,12 +604,12 @@ fn check_last_submessage( // get the instruction before the submessage and add it to the stack trace let call_step = match steps.get(last_submessage_data.step_index as usize - 1) { - Some(MessageTraceStep::Evm(call_step)) => call_step, + Some(NestedTraceStep::Evm(call_step)) => call_step, _ => panic!("This should not happen: MessageTrace should be preceded by a EVM step"), }; - let call_inst = bytecode.get_instruction(call_step.pc)?; - let call_stack_frame = instruction_to_callstack_stack_trace_entry(bytecode, call_inst)?; + let call_inst = contract_meta.get_instruction(call_step.pc)?; + let call_stack_frame = instruction_to_callstack_stack_trace_entry(&contract_meta, call_inst)?; // TODO: remove this expect let call_stack_frame_source_reference = call_stack_frame .source_reference() @@ -616,9 +617,9 @@ fn check_last_submessage( .expect("Callstack entry must have source reference"); let last_message_failed = match &last_submessage_data.message_trace { - MessageTrace::Create(create) => create.exit().is_error(), - MessageTrace::Call(call) => call.exit().is_error(), - MessageTrace::Precompile(precompile) => precompile.exit().is_error(), + NestedTrace::Create(create) => create.exit.is_error(), + NestedTrace::Call(call) => call.exit.is_error(), + NestedTrace::Precompile(precompile) => precompile.exit.is_error(), }; if last_message_failed { // add the call/create that generated the message to the stack trace @@ -663,17 +664,11 @@ fn check_last_submessage( /// Check if the trace reverted with a panic error. fn check_panic( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, mut stacktrace: Vec, last_instruction: &Instruction, ) -> Result { - let return_data = ReturnData::new( - match &trace { - Either::Left(call) => call.return_data(), - Either::Right(create) => create.return_data(), - } - .clone(), - ); + let return_data = ReturnData::new(trace.return_data().clone()); if !return_data.is_panic_return_data() { return Ok(Heuristic::Miss(stacktrace)); @@ -694,20 +689,17 @@ fn check_panic( stacktrace.pop(); } - stacktrace.push( - instruction_within_function_to_panic_stack_trace_entry( - trace, - last_instruction, - error_code, - )? - .into(), - ); + stacktrace.push(instruction_within_function_to_panic_stack_trace_entry( + trace, + last_instruction, + error_code, + )?); fix_initial_modifier(trace, stacktrace).map(Heuristic::Hit) } fn check_non_contract_called( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, mut stacktrace: Vec, ) -> Result { if is_called_non_contract_account_error(trace)? { @@ -730,7 +722,7 @@ fn check_non_contract_called( /// Check if the execution stopped with a revert or an invalid opcode. fn check_revert_or_invalid_opcode( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, stacktrace: Vec, last_instruction: &Instruction, function_jumpdests: &[&Instruction], @@ -741,16 +733,15 @@ fn check_revert_or_invalid_opcode( _ => return Ok(Heuristic::Miss(stacktrace)), } - let (bytecode, return_data) = match &trace { - Either::Left(call) => (call.bytecode(), call.return_data()), - Either::Right(create) => (create.bytecode(), create.return_data()), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let return_data = trace.return_data(); let mut inferred_stacktrace = stacktrace.clone(); if let Some(location) = &last_instruction.location { - if jumped_into_function || matches!(trace, Either::Right(CreateMessageTrace { .. })) { + if jumped_into_function || matches!(trace, CreateOrCallMessageRef::Create(_)) { // There should always be a function here, but that's not the case with // optimizations. // @@ -787,7 +778,7 @@ fn check_revert_or_invalid_opcode( }; if let Some(location) = &last_instruction.location { - if jumped_into_function || matches!(trace, Either::Right(CreateMessageTrace { .. })) { + if jumped_into_function || matches!(trace, CreateOrCallMessageRef::Create(_)) { let failing_function = location.get_containing_function(); if failing_function.is_some() { @@ -796,13 +787,13 @@ fn check_revert_or_invalid_opcode( last_instruction, )?; - inferred_stacktrace.push(frame.into()); + inferred_stacktrace.push(frame); } else { let is_invalid_opcode_error = last_instruction.opcode == OpCode::INVALID; match &trace { - Either::Left(CallMessageTrace { calldata, .. }) => { - let contract = bytecode.contract.borrow(); + CreateOrCallMessageRef::Call(CallMessage { calldata, .. }) => { + let contract = contract_meta.contract.borrow(); // This is here because of the optimizations let function_from_selector = contract @@ -821,17 +812,17 @@ fn check_revert_or_invalid_opcode( is_invalid_opcode_error, }; - inferred_stacktrace.push(frame.into()); + inferred_stacktrace.push(frame); } - Either::Right(trace @ CreateMessageTrace { .. }) => { + CreateOrCallMessageRef::Create(create) => { // This is here because of the optimizations let frame = StackTraceEntry::RevertError { - source_reference: get_constructor_start_source_reference(trace)?, + source_reference: get_constructor_start_source_reference(create)?, return_data: return_data.clone(), is_invalid_opcode_error, }; - inferred_stacktrace.push(frame.into()); + inferred_stacktrace.push(frame); } } } @@ -849,7 +840,7 @@ fn check_revert_or_invalid_opcode( is_invalid_opcode_error: last_instruction.opcode == OpCode::INVALID, }; - inferred_stacktrace.push(revert_frame.into()); + inferred_stacktrace.push(revert_frame); return fix_initial_modifier(trace, inferred_stacktrace).map(Heuristic::Hit); } @@ -858,14 +849,14 @@ fn check_revert_or_invalid_opcode( } fn check_solidity_0_6_3_unmapped_revert( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, mut stacktrace: Vec, ) -> Result { if solidity_0_6_3_maybe_unmapped_revert(trace)? { let revert_frame = solidity_0_6_3_get_frame_for_unmapped_revert_within_function(trace)?; if let Some(revert_frame) = revert_frame { - stacktrace.push(revert_frame.into()); + stacktrace.push(revert_frame); return Ok(Heuristic::Hit(stacktrace)); } @@ -876,12 +867,15 @@ fn check_solidity_0_6_3_unmapped_revert( Ok(Heuristic::Miss(stacktrace)) } -fn empty_calldata_and_no_receive(trace: &CallMessageTrace) -> Result { - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); +fn empty_calldata_and_no_receive(trace: &CallMessage) -> Result { + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let version = - Version::parse(&bytecode.compiler_version).expect("Failed to parse SemVer version"); + Version::parse(&contract_meta.compiler_version).expect("Failed to parse SemVer version"); // this only makes sense when receive functions are available if version < FIRST_SOLC_VERSION_RECEIVE_FUNCTION { @@ -892,31 +886,29 @@ fn empty_calldata_and_no_receive(trace: &CallMessageTrace) -> Result, + trace: CreateOrCallMessageRef<'_>, call_subtrace_step_index: u32, ) -> Result { - let (bytecode, steps) = match &trace { - Either::Left(call) => (call.bytecode(), call.steps()), - Either::Right(create) => (create.bytecode(), create.steps()), - }; - - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); - let Some(MessageTraceStep::Evm(last_step)) = steps.last() else { + let Some(NestedTraceStep::Evm(last_step)) = steps.last() else { return Ok(false); }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = contract_meta.get_instruction(last_step.pc)?; if last_inst.opcode != OpCode::REVERT { return Ok(false); } let call_opcode_step = steps.get(call_subtrace_step_index as usize - 1); let call_opcode_step = match call_opcode_step { - Some(MessageTraceStep::Evm(step)) => step, + Some(NestedTraceStep::Evm(step)) => step, _ => return Err(InferrerError::ExpectedEvmStep), }; - let call_inst = bytecode.get_instruction(call_opcode_step.pc)?; + let call_inst = contract_meta.get_instruction(call_opcode_step.pc)?; // Calls are always made from within functions let call_inst_location = call_inst @@ -928,7 +920,7 @@ fn fails_right_after_call( } fn fix_initial_modifier( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, mut stacktrace: Vec, ) -> Result, InferrerError> { if let Some(StackTraceEntry::CallstackEntry { @@ -980,10 +972,13 @@ fn format_dyn_sol_value(val: &DynSolValue) -> String { } fn get_constructor_start_source_reference( - trace: &CreateMessageTrace, + trace: &CreateMessage, ) -> Result { - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let contract_location = &contract.location; let line = match &contract.constructor { @@ -1008,15 +1003,13 @@ fn get_constructor_start_source_reference( } fn get_contract_start_without_function_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result { - let bytecode = match &trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - }; - - let contract = &bytecode.ok_or(InferrerError::MissingContract)?.contract; - let contract = contract.borrow(); + let contract_meta = trace + .contract_meta() + .clone() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let location = &contract.location; let file = location.file(); @@ -1034,20 +1027,24 @@ fn get_contract_start_without_function_source_reference( } fn get_direct_library_call_error_stack_trace( - trace: &CallMessageTrace, + trace: &CallMessage, ) -> Result, InferrerError> { - let contract = &trace - .bytecode() - .ok_or(InferrerError::MissingContract)? - .contract; - let contract = contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let func = contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); let source_reference = match func { - Some(func) => get_function_start_source_reference(Either::Left(trace), func)?, - None => get_contract_start_without_function_source_reference(Either::Left(trace))?, + Some(func) => { + get_function_start_source_reference(CreateOrCallMessageRef::Call(trace), func)? + } + None => get_contract_start_without_function_source_reference( + CreateOrCallMessageRef::Call(trace), + )?, }; Ok(vec![StackTraceEntry::DirectLibraryCallError { @@ -1056,16 +1053,13 @@ fn get_direct_library_call_error_stack_trace( } fn get_function_start_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, func: &ContractFunction, ) -> Result { - let bytecode = match &trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - }; - - let contract = &bytecode.ok_or(InferrerError::MissingContract)?.contract; - let contract = contract.borrow(); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let file = func.location.file(); let file = file.borrow(); @@ -1084,28 +1078,32 @@ fn get_function_start_source_reference( } fn get_entry_before_initial_modifier_callstack_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result { let trace = match trace { - Either::Right(create) => { + CreateOrCallMessageRef::Create(create) => { return Ok(StackTraceEntry::CallstackEntry { source_reference: get_constructor_start_source_reference(create)?, - function_type: ContractFunctionType::Constructor.into(), + function_type: ContractFunctionType::Constructor, }) } - Either::Left(call) => call, + CreateOrCallMessageRef::Call(call) => call, }; - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let called_function = contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); let source_reference = match called_function { - Some(called_function) => { - get_function_start_source_reference(Either::Left(trace), called_function)? - } + Some(called_function) => get_function_start_source_reference( + CreateOrCallMessageRef::Call(trace), + called_function, + )?, None => get_fallback_start_source_reference(trace)?, }; @@ -1116,24 +1114,22 @@ fn get_entry_before_initial_modifier_callstack_entry( Ok(StackTraceEntry::CallstackEntry { source_reference, - function_type: function_type.into(), + function_type, }) } fn get_entry_before_failure_in_modifier( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, function_jumpdests: &[&Instruction], ) -> Result { - let bytecode = match &trace { - Either::Left(call) => call.bytecode(), - Either::Right(create) => create.bytecode(), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; // If there's a jumpdest, this modifier belongs to the last function that it // represents if let Some(last_jumpdest) = function_jumpdests.last() { - let entry = instruction_to_callstack_stack_trace_entry(bytecode, last_jumpdest)?; + let entry = instruction_to_callstack_stack_trace_entry(&contract_meta, last_jumpdest)?; return Ok(entry); } @@ -1141,22 +1137,27 @@ fn get_entry_before_failure_in_modifier( // This function is only called after we jumped into the initial function in // call traces, so there should always be at least a function jumpdest. let trace = match trace { - Either::Left(call) => return Err(InferrerError::MissingFunctionJumpDest(call.clone())), - Either::Right(create) => create, + CreateOrCallMessageRef::Call(call) => { + return Err(InferrerError::MissingFunctionJumpDest(call.clone())) + } + CreateOrCallMessageRef::Create(create) => create, }; // If there's no jump dest, we point to the constructor. Ok(StackTraceEntry::CallstackEntry { source_reference: get_constructor_start_source_reference(trace)?, - function_type: ContractFunctionType::Constructor.into(), + function_type: ContractFunctionType::Constructor, }) } fn get_fallback_start_source_reference( - trace: &CallMessageTrace, + trace: &CallMessage, ) -> Result { - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); let func = match &contract.fallback { Some(func) => func, @@ -1178,22 +1179,20 @@ fn get_fallback_start_source_reference( } fn get_last_instruction_with_valid_location_step_index( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result, InferrerError> { - let (bytecode, steps) = match &trace { - Either::Left(create) => (create.bytecode(), create.steps()), - Either::Right(call) => (call.bytecode(), call.steps()), - }; - - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); for (i, step) in steps.iter().enumerate().rev() { let step = match step { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => return Ok(None), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = contract_meta.get_instruction(step.pc)?; if inst.location.is_some() { return Ok(Some(i as u32)); @@ -1203,54 +1202,50 @@ fn get_last_instruction_with_valid_location_step_index( Ok(None) } -fn get_last_instruction_with_valid_location<'a>( - trace: Either<&'a CallMessageTrace, &'a CreateMessageTrace>, -) -> Result, InferrerError> { +fn get_last_instruction_with_valid_location( + trace: CreateOrCallMessageRef<'_>, +) -> Result, InferrerError> { let last_location_index = get_last_instruction_with_valid_location_step_index(trace)?; let Some(last_location_index) = last_location_index else { return Ok(None); }; - let (bytecode, steps) = match &trace { - Either::Left(create) => (create.bytecode(), create.steps()), - Either::Right(call) => (call.bytecode(), call.steps()), - }; - - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); match &steps.get(last_location_index as usize) { - Some(MessageTraceStep::Evm(step)) => { - let inst = bytecode.get_instruction(step.pc)?; + Some(NestedTraceStep::Evm(step)) => { + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let inst = contract_meta.get_instruction(step.pc)?; - Ok(Some(inst)) + Ok(Some(inst.clone())) } _ => Ok(None), } } fn get_last_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result, InferrerError> { - let (bytecode, steps) = match trace { - Either::Left(create) => (create.bytecode(), create.steps()), - Either::Right(call) => (call.bytecode(), call.steps()), - }; - - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); for step in steps.iter().rev() { let step = match step { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => continue, }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = contract_meta.get_instruction(step.pc)?; let Some(location) = &inst.location else { continue; }; - let source_reference = source_location_to_source_reference(bytecode, Some(location)); + let source_reference = source_location_to_source_reference(&contract_meta, Some(location)); if let Some(source_reference) = source_reference { return Ok(Some(source_reference)); @@ -1261,21 +1256,20 @@ fn get_last_source_reference( } fn get_other_error_before_called_function_stack_trace_entry( - trace: &CallMessageTrace, + trace: &CallMessage, ) -> Result { let source_reference = - get_contract_start_without_function_source_reference(Either::Left(trace))?; + get_contract_start_without_function_source_reference(CreateOrCallMessageRef::Call(trace))?; Ok(StackTraceEntry::OtherExecutionError { source_reference: Some(source_reference), }) } -fn has_failed_inside_the_fallback_function( - trace: &CallMessageTrace, -) -> Result { +fn has_failed_inside_the_fallback_function(trace: &CallMessage) -> Result { let contract = &trace - .bytecode() + .contract_meta + .as_ref() .ok_or(InferrerError::MissingContract)? .contract; let contract = contract.borrow(); @@ -1286,9 +1280,10 @@ fn has_failed_inside_the_fallback_function( } } -fn has_failed_inside_the_receive_function(trace: &CallMessageTrace) -> Result { +fn has_failed_inside_the_receive_function(trace: &CallMessage) -> Result { let contract = &trace - .bytecode() + .contract_meta + .as_ref() .ok_or(InferrerError::MissingContract)? .contract; let contract = contract.borrow(); @@ -1300,24 +1295,26 @@ fn has_failed_inside_the_receive_function(trace: &CallMessageTrace) -> Result Result { let last_step = trace - .steps() - .into_iter() + .steps + .iter() .last() .expect("There should at least be one step"); let last_step = match last_step { - MessageTraceStep::Evm(step) => step, - _ => panic!("JS code asserted this is always an EvmStep"), + NestedTraceStep::Evm(step) => step, + _ => return Err(InferrerError::ExpectedEvmStep), }; - let last_instruction = trace - .bytecode() - .ok_or(InferrerError::MissingContract)? - .get_instruction(last_step.pc)?; + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + + let last_instruction = contract_meta.get_instruction(last_step.pc)?; Ok(match &last_instruction.location { Some(last_instruction_location) => { @@ -1329,7 +1326,7 @@ fn has_failed_inside_function( } fn instruction_within_function_to_custom_error_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, inst: &Instruction, message: String, ) -> Result { @@ -1337,13 +1334,12 @@ fn instruction_within_function_to_custom_error_stack_trace_entry( let last_source_reference = last_source_reference.expect("Expected source reference to be defined"); - let bytecode = match &trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - } - .ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; - let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()); + let source_reference = + source_location_to_source_reference(&contract_meta, inst.location.as_deref()); let source_reference = source_reference.unwrap_or(last_source_reference); @@ -1354,19 +1350,18 @@ fn instruction_within_function_to_custom_error_stack_trace_entry( } fn instruction_within_function_to_panic_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, inst: &Instruction, error_code: U256, ) -> Result { let last_source_reference = get_last_source_reference(trace)?; - let bytecode = match &trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - } - .ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; - let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()); + let source_reference = + source_location_to_source_reference(&contract_meta, inst.location.as_deref()); let source_reference = source_reference.or(last_source_reference); @@ -1377,58 +1372,49 @@ fn instruction_within_function_to_panic_stack_trace_entry( } fn instruction_within_function_to_revert_stack_trace_entry( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, inst: &Instruction, ) -> Result { - let bytecode = match &trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - } - .ok_or(InferrerError::MissingContract)?; - - let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()) - .ok_or(InferrerError::MissingSourceReference)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; - let return_data = match &trace { - Either::Left(create) => create.return_data(), - Either::Right(call) => call.return_data(), - }; + let source_reference = + source_location_to_source_reference(&contract_meta, inst.location.as_deref()) + .ok_or(InferrerError::MissingSourceReference)?; Ok(StackTraceEntry::RevertError { source_reference, is_invalid_opcode_error: inst.opcode == OpCode::INVALID, - return_data: return_data.clone(), + return_data: trace.return_data().clone(), }) } fn instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, inst: &Instruction, ) -> Result, InferrerError> { - let bytecode = match &trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - } - .ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; - let source_reference = source_location_to_source_reference(bytecode, inst.location.as_deref()); + let source_reference = + source_location_to_source_reference(&contract_meta, inst.location.as_deref()); Ok(source_reference) } fn is_called_non_contract_account_error( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result { // We could change this to checking that the last valid location maps to a call, // but it's way more complex as we need to get the ast node from that // location. - let (bytecode, steps) = match &trace { - Either::Left(create) => (create.bytecode(), create.steps()), - Either::Right(call) => (call.bytecode(), call.steps()), - }; - - let bytecode = bytecode.ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); let last_index = get_last_instruction_with_valid_location_step_index(trace)?; @@ -1438,28 +1424,28 @@ fn is_called_non_contract_account_error( }; let last_step = match &steps[last_index] { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => panic!("We know this is an EVM step"), }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = contract_meta.get_instruction(last_step.pc)?; if last_inst.opcode != OpCode::ISZERO { return Ok(false); } let prev_step = match &steps[last_index - 1] { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => panic!("We know this is an EVM step"), }; - let prev_inst = bytecode.get_instruction(prev_step.pc)?; + let prev_inst = contract_meta.get_instruction(prev_step.pc)?; Ok(prev_inst.opcode == OpCode::EXTCODESIZE) } fn is_call_failed_error( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, inst_index: u32, call_instruction: &Instruction, ) -> Result { @@ -1473,15 +1459,16 @@ fn is_call_failed_error( /// Returns a source reference pointing to the constructor if it exists, or /// to the contract otherwise. -fn is_constructor_invalid_arguments_error( - trace: &CreateMessageTrace, -) -> Result { - if trace.return_data().len() > 0 { +fn is_constructor_invalid_arguments_error(trace: &CreateMessage) -> Result { + if trace.return_data.len() > 0 { return Ok(false); } - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); // This function is only matters with contracts that have constructors defined. // The ones that don't are abstract contracts, or their constructor @@ -1490,32 +1477,32 @@ fn is_constructor_invalid_arguments_error( return Ok(false); }; - let Ok(version) = Version::parse(&bytecode.compiler_version) else { + let Ok(version) = Version::parse(&contract_meta.compiler_version) else { return Ok(false); }; if version < FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION { return Ok(false); } - let last_step = trace.steps().into_iter().last(); - let Some(MessageTraceStep::Evm(last_step)) = last_step else { + let last_step = trace.steps.last(); + let Some(NestedTraceStep::Evm(last_step)) = last_step else { return Ok(false); }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = contract_meta.get_instruction(last_step.pc)?; if last_inst.opcode != OpCode::REVERT || last_inst.location.is_some() { return Ok(false); } let mut has_read_deployment_code_size = false; - for step in trace.steps() { + for step in trace.steps.iter() { let step = match step { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => return Ok(false), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = contract_meta.get_instruction(step.pc)?; if let Some(inst_location) = &inst.location { if contract.location != *inst_location && constructor.location != *inst_location { @@ -1531,14 +1518,17 @@ fn is_constructor_invalid_arguments_error( Ok(has_read_deployment_code_size) } -fn is_constructor_not_payable_error(trace: &CreateMessageTrace) -> Result { +fn is_constructor_not_payable_error(trace: &CreateMessage) -> Result { // This error doesn't return data - if !trace.return_data().is_empty() { + if !trace.return_data.is_empty() { return Ok(false); } - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); // This function is only matters with contracts that have constructors defined. // The ones that don't are abstract contracts, or their constructor @@ -1548,32 +1538,31 @@ fn is_constructor_not_payable_error(trace: &CreateMessageTrace) -> Result return Ok(false), }; - let value = trace.value(); - if value.is_zero() { + if trace.value.is_zero() { return Ok(false); } Ok(constructor.is_payable != Some(true)) } -fn is_direct_library_call(trace: &CallMessageTrace) -> Result { +fn is_direct_library_call(trace: &CallMessage) -> Result { let contract = &trace - .bytecode() + .contract_meta + .as_ref() .ok_or(InferrerError::MissingContract)? .contract; let contract = contract.borrow(); - Ok(trace.depth() == 0 && contract.r#type == ContractKind::Library) + Ok(trace.depth == 0 && contract.r#type == ContractKind::Library) } fn is_contract_call_run_out_of_gas_error( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, call_step_index: u32, ) -> Result { - let (steps, return_data, exit_code) = match &trace { - Either::Left(call) => (call.steps(), call.return_data(), call.exit()), - Either::Right(create) => (create.steps(), create.return_data(), create.exit()), - }; + let steps = trace.steps(); + let return_data = trace.return_data(); + let exit_code = trace.exit_code(); if return_data.len() > 0 { return Ok(false); @@ -1584,10 +1573,10 @@ fn is_contract_call_run_out_of_gas_error( } let call_exit = match steps.get(call_step_index as usize) { - None | Some(MessageTraceStep::Evm(_)) => panic!("Expected call to be a message trace"), - Some(MessageTraceStep::Precompile(precompile)) => precompile.exit(), - Some(MessageTraceStep::Call(call)) => call.exit(), - Some(MessageTraceStep::Create(create)) => create.exit(), + None | Some(NestedTraceStep::Evm(_)) => panic!("Expected call to be a message trace"), + Some(NestedTraceStep::Precompile(precompile)) => precompile.exit.clone(), + Some(NestedTraceStep::Call(call)) => call.exit.clone(), + Some(NestedTraceStep::Create(create)) => create.exit.clone(), }; if !call_exit.is_out_of_gas_error() { @@ -1598,15 +1587,15 @@ fn is_contract_call_run_out_of_gas_error( } fn is_fallback_not_payable_error( - trace: &CallMessageTrace, + trace: &CallMessage, called_function: Option<&ContractFunction>, ) -> Result { // This error doesn't return data - if !trace.return_data().is_empty() { + if !trace.return_data.is_empty() { return Ok(false); } - if trace.value().is_zero() { + if trace.value.is_zero() { return Ok(false); } @@ -1615,8 +1604,11 @@ fn is_fallback_not_payable_error( return Ok(false); } - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); match &contract.fallback { Some(fallback) => Ok(fallback.is_payable != Some(true)), @@ -1625,20 +1617,23 @@ fn is_fallback_not_payable_error( } fn is_function_not_payable_error( - trace: &CallMessageTrace, + trace: &CallMessage, called_function: &ContractFunction, ) -> Result { // This error doesn't return data - if !trace.return_data().is_empty() { + if !trace.return_data.is_empty() { return Ok(false); } - if trace.value().is_zero() { + if trace.value.is_zero() { return Ok(false); } - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); // Libraries don't have a nonpayable check if contract.r#type == ContractKind::Library { @@ -1649,24 +1644,22 @@ fn is_function_not_payable_error( } fn is_last_location( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, from_step: u32, location: &SourceLocation, ) -> Result { - let (bytecode, steps) = match &trace { - Either::Left(call) => (call.bytecode(), call.steps()), - Either::Right(create) => (create.bytecode(), create.steps()), - }; - - let bytecode = bytecode.as_ref().expect("JS code asserts"); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); for step in steps.iter().skip(from_step as usize) { let step = match step { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => return Ok(false), }; - let step_inst = bytecode.get_instruction(step.pc)?; + let step_inst = contract_meta.get_instruction(step.pc)?; if let Some(step_inst_location) = &step_inst.location { if **step_inst_location != *location { @@ -1679,11 +1672,11 @@ fn is_last_location( } fn is_missing_function_and_fallback_error( - trace: &CallMessageTrace, + trace: &CallMessage, called_function: Option<&ContractFunction>, ) -> Result { // This error doesn't return data - if trace.return_data().len() > 0 { + if trace.return_data.len() > 0 { return Ok(false); } @@ -1692,8 +1685,11 @@ fn is_missing_function_and_fallback_error( return Ok(false); } - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); // there's a receive function and no calldata if trace.calldata.len() == 0 && contract.receive.is_some() { @@ -1704,66 +1700,61 @@ fn is_missing_function_and_fallback_error( } fn is_proxy_error_propagated( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, call_subtrace_step_index: u32, ) -> Result { let trace = match &trace { - Either::Left(call) => call, - Either::Right(_) => return Ok(false), + CreateOrCallMessageRef::Call(call) => call, + CreateOrCallMessageRef::Create(_) => return Ok(false), }; - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; - let steps = trace.steps(); - let call_step = match steps.get(call_subtrace_step_index as usize - 1) { - Some(MessageTraceStep::Evm(step)) => step, + let call_step = match trace.steps.get(call_subtrace_step_index as usize - 1) { + Some(NestedTraceStep::Evm(step)) => step, _ => return Ok(false), }; - let call_inst = bytecode.get_instruction(call_step.pc)?; + let call_inst = contract_meta.get_instruction(call_step.pc)?; if call_inst.opcode != OpCode::DELEGATECALL { return Ok(false); } - let steps = trace.steps(); - let subtrace = match steps.get(call_subtrace_step_index as usize) { - None | Some(MessageTraceStep::Evm(_) | MessageTraceStep::Precompile(_)) => { - return Ok(false) - } - Some(MessageTraceStep::Call(call)) => Either::Left(call), - Some(MessageTraceStep::Create(create)) => Either::Right(create), + let subtrace = match trace.steps.get(call_subtrace_step_index as usize) { + None | Some(NestedTraceStep::Evm(_) | NestedTraceStep::Precompile(_)) => return Ok(false), + Some(NestedTraceStep::Call(call)) => CreateOrCallMessageRef::Call(call), + Some(NestedTraceStep::Create(create)) => CreateOrCallMessageRef::Create(create), }; - let (subtrace_bytecode, subtrace_return_data) = match &subtrace { - Either::Left(call) => (call.bytecode(), call.return_data()), - Either::Right(create) => (create.bytecode(), create.return_data()), - }; - let subtrace_bytecode = match subtrace_bytecode { - Some(bytecode) => bytecode, - // If we can't recognize the implementation we'd better don't consider it as such - None => return Ok(false), + let Some(subtrace_contract_meta) = subtrace.contract_meta() else { + // If we can't recognize the implementation we'd better don't consider it as + // such + return Ok(false); }; - if subtrace_bytecode.contract.borrow().r#type == ContractKind::Library { + if subtrace_contract_meta.contract.borrow().r#type == ContractKind::Library { return Ok(false); } - if trace.return_data().as_ref() != subtrace_return_data.as_ref() { + if trace.return_data.as_ref() != subtrace.return_data().as_ref() { return Ok(false); } for step in trace - .steps() + .steps .iter() .skip(call_subtrace_step_index as usize + 1) { let step = match step { - MessageTraceStep::Evm(step) => step, + NestedTraceStep::Evm(step) => step, _ => return Ok(false), }; - let inst = bytecode.get_instruction(step.pc)?; + let inst = contract_meta.get_instruction(step.pc)?; // All the remaining locations should be valid, as they are part of the inline // asm @@ -1779,32 +1770,32 @@ fn is_proxy_error_propagated( } } - let steps = trace.steps(); - let last_step = match steps.last() { - Some(MessageTraceStep::Evm(step)) => step, - _ => panic!("Expected last step to be an EvmStep"), + let last_step = match trace.steps.last() { + Some(NestedTraceStep::Evm(step)) => step, + _ => return Err(InferrerError::ExpectedEvmStep), }; - let last_inst = bytecode.get_instruction(last_step.pc)?; + let last_inst = contract_meta.get_instruction(last_step.pc)?; Ok(last_inst.opcode == OpCode::REVERT) } fn is_subtrace_error_propagated( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, call_subtrace_step_index: u32, ) -> Result { - let (return_data, exit, steps) = match &trace { - Either::Left(call) => (call.return_data(), call.exit(), call.steps()), - Either::Right(create) => (create.return_data(), create.exit(), create.steps()), - }; + let return_data = trace.return_data(); + let steps = trace.steps(); + let exit = trace.exit_code(); let (call_return_data, call_exit) = match steps.get(call_subtrace_step_index as usize) { - None | Some(MessageTraceStep::Evm(_)) => panic!("Expected call to be a message trace"), - Some(MessageTraceStep::Precompile(precompile)) => { - (precompile.return_data(), precompile.exit()) + None | Some(NestedTraceStep::Evm(_)) => panic!("Expected call to be a message trace"), + Some(NestedTraceStep::Precompile(ref precompile)) => { + (precompile.return_data.clone(), precompile.exit.clone()) + } + Some(NestedTraceStep::Call(ref call)) => (call.return_data.clone(), call.exit.clone()), + Some(NestedTraceStep::Create(ref create)) => { + (create.return_data.clone(), create.exit.clone()) } - Some(MessageTraceStep::Call(call)) => (call.return_data(), call.exit()), - Some(MessageTraceStep::Create(create)) => (create.return_data(), create.exit()), }; if return_data.as_ref() != call_return_data.as_ref() { @@ -1825,28 +1816,24 @@ fn is_subtrace_error_propagated( } fn other_execution_error_stacktrace( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, mut stacktrace: Vec, ) -> Result, InferrerError> { let other_execution_error_frame = StackTraceEntry::OtherExecutionError { source_reference: get_last_source_reference(trace)?, }; - stacktrace.push(other_execution_error_frame.into()); + stacktrace.push(other_execution_error_frame); Ok(stacktrace) } fn solidity_0_6_3_maybe_unmapped_revert( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result { - let (bytecode, steps) = match &trace { - Either::Left(create) => (create.bytecode(), create.steps()), - Either::Right(call) => (call.bytecode(), call.steps()), - }; - - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); if steps.is_empty() { return Ok(false); @@ -1854,13 +1841,13 @@ fn solidity_0_6_3_maybe_unmapped_revert( let last_step = steps.last(); let last_step = match last_step { - Some(MessageTraceStep::Evm(step)) => step, + Some(NestedTraceStep::Evm(step)) => step, _ => return Ok(false), }; - let last_instruction = bytecode.get_instruction(last_step.pc)?; + let last_instruction = contract_meta.get_instruction(last_step.pc)?; - let Ok(version) = Version::parse(&bytecode.compiler_version) else { + let Ok(version) = Version::parse(&contract_meta.compiler_version) else { return Ok(false); }; let req = VersionReq::parse(&format!("^{FIRST_SOLC_VERSION_WITH_UNMAPPED_REVERTS}")) @@ -1872,13 +1859,17 @@ fn solidity_0_6_3_maybe_unmapped_revert( // Solidity 0.6.3 unmapped reverts special handling // For more info: https://github.com/ethereum/solidity/issues/9006 fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( - trace: &CallMessageTrace, + trace: &CallMessage, ) -> Result, InferrerError> { - let bytecode = trace.bytecode().ok_or(InferrerError::MissingContract)?; - let contract = bytecode.contract.borrow(); + let contract_meta = trace + .contract_meta + .as_ref() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); - let revert_frame = - solidity_0_6_3_get_frame_for_unmapped_revert_within_function(Either::Left(trace))?; + let revert_frame = solidity_0_6_3_get_frame_for_unmapped_revert_within_function( + CreateOrCallMessageRef::Call(trace), + )?; let revert_frame = match revert_frame { None @@ -1943,33 +1934,29 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( } fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( - trace: Either<&CallMessageTrace, &CreateMessageTrace>, + trace: CreateOrCallMessageRef<'_>, ) -> Result, InferrerError> { - let (bytecode, steps) = match &trace { - Either::Left(create) => (create.bytecode(), create.steps()), - Either::Right(call) => (call.bytecode(), call.steps()), - }; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let contract = contract_meta.contract.borrow(); - let bytecode = bytecode - .as_ref() - .expect("JS code only accepted variants that had bytecode defined"); - - let contract = bytecode.contract.borrow(); + let steps = trace.steps(); // If we are within a function there's a last valid location. It may // be the entire contract. let prev_inst = get_last_instruction_with_valid_location(trace)?; let last_step = match steps.last() { - Some(MessageTraceStep::Evm(step)) => step, - _ => panic!("JS code asserts this is always an EvmStep"), + Some(NestedTraceStep::Evm(step)) => step, + _ => return Err(InferrerError::ExpectedEvmStep), }; let next_inst_pc = last_step.pc + 1; - let has_next_inst = bytecode.has_instruction(next_inst_pc); + let has_next_inst = contract_meta.has_instruction(next_inst_pc); if has_next_inst { - let next_inst = bytecode.get_instruction(next_inst_pc)?; + let next_inst = contract_meta.get_instruction(next_inst_pc)?; - let prev_loc = prev_inst.and_then(|i| i.location.as_deref()); + let prev_loc = prev_inst.as_ref().and_then(|i| i.location.as_deref()); let next_loc = next_inst.location.as_deref(); let prev_func = prev_loc.and_then(SourceLocation::get_containing_function); @@ -2010,7 +1997,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( })); } - if matches!(trace, Either::Right(CreateMessageTrace { .. })) && prev_inst.is_some() { + if matches!(trace, CreateOrCallMessageRef::Create(_)) && prev_inst.is_some() { // Solidity is smart enough to stop emitting extra instructions after // an unconditional revert happens in a constructor. If this is the case // we just return a special error. @@ -2020,29 +2007,32 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( trace, prev_inst.as_ref().unwrap(), )? - .map(solidity_0_6_3_correct_line_number) - .unwrap_or_else(|| { - // When the latest instruction is not within a function we need - // some default sourceReference to show to the user - let location = &contract.location; - let file = location.file(); - let file = file.borrow(); + .map_or_else( + || { + // When the latest instruction is not within a function we need + // some default sourceReference to show to the user + let location = &contract.location; + let file = location.file(); + let file = file.borrow(); - let mut default_source_reference = SourceReference { - function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), - contract: Some(contract.name.clone()), - source_name: file.source_name.clone(), - source_content: file.content.clone(), - line: location.get_starting_line_number(), - range: (location.offset, location.offset + location.length), - }; + let mut default_source_reference = SourceReference { + function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), + contract: Some(contract.name.clone()), + source_name: file.source_name.clone(), + source_content: file.content.clone(), + line: location.get_starting_line_number(), + range: (location.offset, location.offset + location.length), + }; - if let Some(constructor) = &contract.constructor { - default_source_reference.line = constructor.location.get_starting_line_number(); - } + if let Some(constructor) = &contract.constructor { + default_source_reference.line = + constructor.location.get_starting_line_number(); + } - default_source_reference - }); + default_source_reference + }, + solidity_0_6_3_correct_line_number, + ); return Ok(Some(StackTraceEntry::UnmappedSolc0_6_3RevertError { source_reference: Some(source_reference), @@ -2056,7 +2046,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( // points to. let source_reference = instruction_within_function_to_unmapped_solc_0_6_3_revert_error_source_reference( - trace, prev_inst, + trace, &prev_inst, )? .map(solidity_0_6_3_correct_line_number); @@ -2094,15 +2084,11 @@ fn solidity_0_6_3_correct_line_number(mut source_reference: SourceReference) -> } fn source_location_to_source_reference( - bytecode: &Bytecode, + contract_meta: &ContractMetadata, location: Option<&SourceLocation>, ) -> Option { - let Some(location) = location else { - return None; - }; - let Some(func) = location.get_containing_function() else { - return None; - }; + let location = location?; + let func = location.get_containing_function()?; let func_name = match func.r#type { ContractFunctionType::Constructor => CONSTRUCTOR_FUNCTION_NAME.to_string(), @@ -2119,7 +2105,7 @@ fn source_location_to_source_reference( contract: if func.r#type == ContractFunctionType::FreeFunction { None } else { - Some(bytecode.contract.borrow().name.clone()) + Some(contract_meta.contract.borrow().name.clone()) }, source_name: func_location_file.source_name.clone(), source_content: func_location_file.content.clone(), diff --git a/crates/edr_solidity/src/exit_code.rs b/crates/edr_solidity/src/exit_code.rs new file mode 100644 index 000000000..92add5d9c --- /dev/null +++ b/crates/edr_solidity/src/exit_code.rs @@ -0,0 +1,46 @@ +//! Exit code of the EVM. + +use edr_evm::HaltReason; + +/// Represents the exit code of the EVM. +#[derive(Clone, Debug)] +pub enum ExitCode { + /// Execution was successful. + Success, + /// Execution was reverted. + Revert, + /// Indicates that the EVM has experienced an exceptional halt. + Halt(HaltReason), +} + +impl ExitCode { + /// Returns whether the exit code is an error. + pub fn is_error(&self) -> bool { + !matches!(self, Self::Success) + } + + /// Returns whether the exit code is a contract too large error. + pub fn is_contract_too_large_error(&self) -> bool { + matches!(self, Self::Halt(HaltReason::CreateContractSizeLimit)) + } + + /// Returns whether the exit code is an invalid opcode error. + pub fn is_invalid_opcode_error(&self) -> bool { + matches!( + self, + Self::Halt( + HaltReason::InvalidFEOpcode | HaltReason::OpcodeNotFound | HaltReason::NotActivated + ) + ) + } + + /// Returns whether the exit code is an out of gas error. + pub fn is_out_of_gas_error(&self) -> bool { + matches!(self, Self::Halt(HaltReason::OutOfGas(_))) + } + + /// Returns whether the exit code is a revert. + pub fn is_revert(&self) -> bool { + matches!(self, Self::Revert) + } +} diff --git a/crates/edr_solidity/src/lib.rs b/crates/edr_solidity/src/lib.rs index 9988cb426..292399359 100644 --- a/crates/edr_solidity/src/lib.rs +++ b/crates/edr_solidity/src/lib.rs @@ -9,15 +9,16 @@ pub mod contracts_identifier; pub mod utils; pub mod artifacts; +pub mod exit_code; pub mod library_utils; -pub mod message_trace; -pub mod vm_tracer; - -pub mod compiler; -pub mod error_inferrer; -mod mapped_inline_internal_functions_heuristics; -pub mod return_data; +pub mod nested_trace; +pub mod nested_trace_decoder; +pub mod nested_tracer; pub mod solidity_stack_trace; pub mod solidity_tracer; -pub mod source_map; -pub mod vm_trace_decoder; + +mod compiler; +mod error_inferrer; +mod mapped_inline_internal_functions_heuristics; +mod return_data; +mod source_map; diff --git a/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs b/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs index 8576f511a..3b6fd3ea5 100644 --- a/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs +++ b/crates/edr_solidity/src/mapped_inline_internal_functions_heuristics.rs @@ -17,12 +17,11 @@ //! provide more meaningful errors. use edr_evm::interpreter::OpCode; -use either::Either; use semver::Version; use crate::{ - build_model::BytecodeError, - message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep, MessageTraceStep}, + build_model::ContractMetadataError, + nested_trace::{CreateOrCallMessageRef, EvmStep, NestedTraceStep}, solidity_stack_trace::StackTraceEntry, }; @@ -31,20 +30,18 @@ const FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS: Version = Version #[derive(Debug, thiserror::Error)] pub enum HeuristicsError { #[error(transparent)] - BytecodeError(#[from] BytecodeError), + BytecodeError(#[from] ContractMetadataError), #[error("Missing contract")] MissingContract, } pub fn stack_trace_may_require_adjustments( - stacktrace: &Vec, - decoded_trace: &Either, + stacktrace: &[StackTraceEntry], + decoded_trace: CreateOrCallMessageRef<'_>, ) -> Result { - let bytecode = match &decoded_trace { - Either::Left(create) => create.bytecode(), - Either::Right(call) => call.bytecode(), - }; - let bytecode = bytecode.ok_or(HeuristicsError::MissingContract)?; + let contract_meta = decoded_trace + .contract_meta() + .ok_or(HeuristicsError::MissingContract)?; let Some(last_frame) = stacktrace.last() else { return Ok(false); @@ -58,7 +55,7 @@ pub fn stack_trace_may_require_adjustments( { let result = !is_invalid_opcode_error && return_data.is_empty() - && Version::parse(&bytecode.compiler_version) + && Version::parse(&contract_meta.compiler_version) .map(|version| version >= FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS) .unwrap_or(false); return Ok(result); @@ -69,7 +66,7 @@ pub fn stack_trace_may_require_adjustments( pub fn adjust_stack_trace( mut stacktrace: Vec, - decoded_trace: &Either, + decoded_trace: CreateOrCallMessageRef<'_>, ) -> Result, HeuristicsError> { let Some(StackTraceEntry::RevertError { source_reference, .. @@ -111,7 +108,7 @@ pub fn adjust_stack_trace( } fn is_non_contract_account_called_error( - decoded_trace: &Either, + decoded_trace: CreateOrCallMessageRef<'_>, ) -> Result { match_opcodes( decoded_trace, @@ -126,7 +123,7 @@ fn is_non_contract_account_called_error( } fn is_constructor_invalid_params_error( - decoded_trace: &Either, + decoded_trace: CreateOrCallMessageRef<'_>, ) -> Result { Ok(match_opcodes(decoded_trace, -20, &[OpCode::CODESIZE])? && match_opcodes(decoded_trace, -15, &[OpCode::CODECOPY])? @@ -134,22 +131,21 @@ fn is_constructor_invalid_params_error( } fn is_call_invalid_params_error( - decoded_trace: &Either, + decoded_trace: CreateOrCallMessageRef<'_>, ) -> Result { Ok(match_opcodes(decoded_trace, -11, &[OpCode::CALLDATASIZE])? && match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?) } fn match_opcodes( - decoded_trace: &Either, + decoded_trace: CreateOrCallMessageRef<'_>, first_step_index: i32, opcodes: &[OpCode], ) -> Result { - let (bytecode, steps) = match &decoded_trace { - Either::Left(call) => (call.bytecode(), call.steps()), - Either::Right(create) => (create.bytecode(), create.steps()), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); + let contract_meta = decoded_trace + .contract_meta() + .ok_or(HeuristicsError::MissingContract)?; + let steps = decoded_trace.steps(); // If the index is negative, we start from the end of the trace, // just like in the original JS code @@ -163,11 +159,11 @@ fn match_opcodes( }; for opcode in opcodes { - let Some(MessageTraceStep::Evm(EvmStep { pc })) = steps.get(index) else { + let Some(NestedTraceStep::Evm(EvmStep { pc })) = steps.get(index) else { return Ok(false); }; - let instruction = bytecode.get_instruction(*pc)?; + let instruction = contract_meta.get_instruction(*pc)?; if instruction.opcode != *opcode { return Ok(false); diff --git a/crates/edr_solidity/src/message_trace.rs b/crates/edr_solidity/src/message_trace.rs deleted file mode 100644 index f3804ef24..000000000 --- a/crates/edr_solidity/src/message_trace.rs +++ /dev/null @@ -1,337 +0,0 @@ -//! Naive Rust port of the `MessageTrace` et al. from Hardhat. - -use std::{cell::RefCell, rc::Rc}; - -use edr_eth::{Address, Bytes, U256}; -use edr_evm::HaltReason; - -use crate::build_model::Bytecode; - -/// Represents the exit code of the EVM. -#[derive(Clone, Debug)] -pub enum ExitCode { - /// Execution was successful. - Success, - /// Execution was reverted. - Revert, - /// Indicates that the EVM has experienced an exceptional halt. - Halt(HaltReason), -} - -impl ExitCode { - pub fn is_error(&self) -> bool { - !matches!(self, Self::Success) - } - - pub fn is_contract_too_large_error(&self) -> bool { - matches!(self, Self::Halt(HaltReason::CreateContractSizeLimit)) - } - - pub fn is_invalid_opcode_error(&self) -> bool { - matches!( - self, - Self::Halt( - HaltReason::InvalidFEOpcode | HaltReason::OpcodeNotFound | HaltReason::NotActivated - ) - ) - } - - pub fn is_out_of_gas_error(&self) -> bool { - matches!(self, Self::Halt(HaltReason::OutOfGas(_))) - } - - pub fn is_revert(&self) -> bool { - matches!(self, Self::Revert) - } -} - -/// Represents a message trace. Naive Rust port of the `MessageTrace` from -/// Hardhat. -#[derive(Clone, Debug)] -pub enum MessageTrace { - /// Represents a create message trace. - Create(CreateMessageTrace), - /// Represents a call message trace. - Call(CallMessageTrace), - /// Represents a precompile message trace. - Precompile(PrecompileMessageTrace), -} - -impl MessageTrace { - /// Returns a reference to the the common fields of the message trace. - pub fn base(&mut self) -> &mut BaseMessageTrace { - match self { - MessageTrace::Create(create) => &mut create.base.base, - MessageTrace::Call(call) => &mut call.base.base, - MessageTrace::Precompile(precompile) => &mut precompile.base, - } - } - - pub fn exit(&self) -> &ExitCode { - match self { - MessageTrace::Create(create) => &create.base.base.exit, - MessageTrace::Call(call) => &call.base.base.exit, - MessageTrace::Precompile(precompile) => &precompile.base.exit, - } - } -} - -/// Represents a message trace. Naive Rust port of the `MessageTrace` from -/// Hardhat. -#[derive(Clone, Debug)] -pub enum MessageTraceRef<'a> { - /// Represents a create message trace. - Create(&'a CreateMessageTrace), - /// Represents a call message trace. - Call(&'a CallMessageTrace), - /// Represents a precompile message trace. - Precompile(PrecompileMessageTrace), -} - -/// Represents the common fields of a message trace. -#[derive(Clone, Debug)] -pub struct BaseMessageTrace { - /// Value of the message. - pub value: U256, - /// Return data buffer. - pub return_data: Bytes, - /// EVM exit code. - pub exit: ExitCode, - /// How much gas was used. - pub gas_used: u64, - /// Depth of the message. - pub depth: usize, -} - -/// Represents a precompile message trace. -#[derive(Clone, Debug)] -pub struct PrecompileMessageTrace { - /// Common fields of the message trace. - pub base: BaseMessageTrace, - /// Precompile number. - pub precompile: u32, - /// Calldata buffer - pub calldata: Bytes, -} - -impl PrecompileMessageTrace { - pub fn exit(&self) -> &ExitCode { - &self.base.exit - } - - pub fn return_data(&self) -> &Bytes { - &self.base.return_data - } -} - -/// Represents a base EVM message trace. -#[derive(Clone, Debug)] -pub struct BaseEvmMessageTrace { - /// Common fields of the message trace. - pub base: BaseMessageTrace, - /// Code of the contract that is being executed. - pub code: Bytes, - /// Children message traces. - pub steps: Vec, - /// Resolved metadata of the contract that is being executed. - /// Filled in the JS side by `ContractsIdentifier`. - pub bytecode: Option>, - // The following is just an optimization: When processing this traces it's useful to know ahead - // of time how many subtraces there are. - /// Number of subtraces. Used to speed up the processing of the traces in - /// JS. - pub number_of_subtraces: u32, -} - -/// Represents a create message trace. -#[derive(Clone, Debug)] -pub struct CreateMessageTrace { - /// Common fields - pub base: BaseEvmMessageTrace, - /// Address of the deployed contract. - pub deployed_contract: Option, -} - -impl CreateMessageTrace { - /// Returns a reference to the metadata of the contract that is being - /// executed. - pub fn bytecode(&self) -> Option<&Rc> { - self.base.bytecode.as_ref() - } - - pub fn set_bytecode(&mut self, bytecode: Option>) { - self.base.bytecode = bytecode - } - - pub fn code(&self) -> &Bytes { - &self.base.code - } - - pub fn depth(&self) -> usize { - self.base.base.depth - } - - pub fn exit(&self) -> &ExitCode { - &self.base.base.exit - } - - pub fn number_of_subtraces(&self) -> u32 { - self.base.number_of_subtraces - } - - pub fn return_data(&self) -> &Bytes { - &self.base.base.return_data - } - - // TODO avoid clone - pub fn steps(&self) -> Vec { - self.base - .steps - .iter() - .cloned() - .map(MessageTraceStep::from) - .collect() - } - - // TODO avoid conversion - pub fn set_steps(&mut self, steps: impl IntoIterator) { - self.base.steps = steps - .into_iter() - .map(VmTracerMessageTraceStep::from) - .collect(); - } - - pub fn value(&self) -> &U256 { - &self.base.base.value - } -} - -/// Represents a call message trace. -#[derive(Clone, Debug)] -pub struct CallMessageTrace { - /// Common fields - pub base: BaseEvmMessageTrace, - /// Calldata buffer - pub calldata: Bytes, - /// Address of the contract that is being executed. - pub address: Address, - /// Address of the code that is being executed. - pub code_address: Address, -} - -impl CallMessageTrace { - /// Returns a reference to the metadata of the contract that is being - /// executed. - pub fn bytecode(&self) -> Option<&Rc> { - self.base.bytecode.as_ref() - } - - pub fn set_bytecode(&mut self, bytecode: Option>) { - self.base.bytecode = bytecode - } - - pub fn code(&self) -> &Bytes { - &self.base.code - } - - pub fn depth(&self) -> usize { - self.base.base.depth - } - - pub fn exit(&self) -> &ExitCode { - &self.base.base.exit - } - - pub fn number_of_subtraces(&self) -> u32 { - self.base.number_of_subtraces - } - - pub fn return_data(&self) -> &Bytes { - &self.base.base.return_data - } - - // TODO avoid clone - pub fn steps(&self) -> Vec { - self.base - .steps - .iter() - .cloned() - .map(MessageTraceStep::from) - .collect() - } - - // TODO avoid conversion - pub fn set_steps(&mut self, steps: impl IntoIterator) { - self.base.steps = steps - .into_iter() - .map(VmTracerMessageTraceStep::from) - .collect(); - } - - pub fn value(&self) -> &U256 { - &self.base.base.value - } -} - -/// Represents a message trace step. Naive Rust port of the `MessageTraceStep` -/// from Hardhat. -#[derive(Clone, Debug)] -pub enum VmTracerMessageTraceStep { - /// [`MessageTrace`] variant. - // It's both read and written to (updated) by the `VmTracer`. - Message(Rc>), - /// [`EvmStep`] variant. - Evm(EvmStep), -} - -pub enum MessageTraceStep { - /// Represents a create message trace. - Create(CreateMessageTrace), - /// Represents a call message trace. - Call(CallMessageTrace), - /// Represents a precompile message trace. - Precompile(PrecompileMessageTrace), - /// Minimal EVM step that contains only PC (program counter). - Evm(EvmStep), -} - -impl From for MessageTraceStep { - fn from(step: VmTracerMessageTraceStep) -> Self { - match step { - // TODO avoid clone - VmTracerMessageTraceStep::Message(trace) => match trace.as_ref().borrow().clone() { - MessageTrace::Create(create_trace) => MessageTraceStep::Create(create_trace), - MessageTrace::Call(call_trace) => MessageTraceStep::Call(call_trace), - MessageTrace::Precompile(precompile_trace) => { - MessageTraceStep::Precompile(precompile_trace) - } - }, - VmTracerMessageTraceStep::Evm(evm_step) => MessageTraceStep::Evm(evm_step), - } - } -} - -impl From for VmTracerMessageTraceStep { - fn from(step: MessageTraceStep) -> Self { - match step { - MessageTraceStep::Evm(evm_step) => VmTracerMessageTraceStep::Evm(evm_step), - // message => VmTracerMessageTraceStep::Message(Rc::new(RefCell::new(message))), - MessageTraceStep::Create(create) => VmTracerMessageTraceStep::Message(Rc::new( - RefCell::new(MessageTrace::Create(create)), - )), - MessageTraceStep::Call(call) => { - VmTracerMessageTraceStep::Message(Rc::new(RefCell::new(MessageTrace::Call(call)))) - } - MessageTraceStep::Precompile(precompile) => VmTracerMessageTraceStep::Message(Rc::new( - RefCell::new(MessageTrace::Precompile(precompile)), - )), - } - } -} - -/// Minimal EVM step that contains only PC (program counter). -#[derive(Clone, Debug)] -pub struct EvmStep { - /// Program counter - pub pc: u32, -} diff --git a/crates/edr_solidity/src/nested_trace.rs b/crates/edr_solidity/src/nested_trace.rs new file mode 100644 index 000000000..c1b8209e1 --- /dev/null +++ b/crates/edr_solidity/src/nested_trace.rs @@ -0,0 +1,216 @@ +//! Naive Rust port of the `MessageTrace` et al. from Hardhat. + +use std::rc::Rc; + +use edr_eth::{Address, Bytes, U256}; + +use crate::{build_model::ContractMetadata, exit_code::ExitCode}; + +/// An EVM trace where the steps are nested according to the call stack. +#[derive(Clone, Debug)] +pub enum NestedTrace { + /// Represents a create trace. + Create(CreateMessage), + /// Represents a call trace. + Call(CallMessage), + /// Represents a precompile trace. + Precompile(PrecompileMessage), +} + +impl NestedTrace { + /// Returns the exit code of the trace. + pub fn exit_code(&self) -> &ExitCode { + match self { + Self::Create(create) => &create.exit, + Self::Call(call) => &call.exit, + Self::Precompile(precompile) => &precompile.exit, + } + } +} + +/// Represents a precompile message. +#[derive(Clone, Debug)] +pub struct PrecompileMessage { + /// Precompile number. + pub precompile: u32, + /// Calldata buffer + pub calldata: Bytes, + /// Value of the message. + pub value: U256, + /// Return data buffer. + pub return_data: Bytes, + /// EVM exit code. + pub exit: ExitCode, + /// How much gas was used. + pub gas_used: u64, + /// Depth of the message. + pub depth: usize, +} + +/// Represents a create message. +#[derive(Clone, Debug)] +pub struct CreateMessage { + // The following is just an optimization: When processing this traces it's useful to know ahead + // of time how many subtraces there are. + /// Number of subtraces. Used to speed up the processing of the traces in + /// JS. + pub number_of_subtraces: u32, + /// Children messages. + pub steps: Vec, + /// Resolved metadata of the contract that is being executed. + pub contract_meta: Option>, + /// Address of the deployed contract. + pub deployed_contract: Option, + /// Code of the contract that is being executed. + pub code: Bytes, + /// Value of the message. + pub value: U256, + /// Return data buffer. + pub return_data: Bytes, + /// EVM exit code. + pub exit: ExitCode, + /// How much gas was used. + pub gas_used: u64, + /// Depth of the message. + pub depth: usize, +} + +/// Represents a call message with contract metadata. +#[derive(Clone, Debug)] +pub struct CallMessage { + // The following is just an optimization: When processing this traces it's useful to know ahead + // of time how many subtraces there are. + /// Number of subtraces. Used to speed up the processing of the traces in + /// JS. + pub number_of_subtraces: u32, + /// Children messages. + pub steps: Vec, + /// Resolved metadata of the contract that is being executed. + pub contract_meta: Option>, + /// Calldata buffer + pub calldata: Bytes, + /// Address of the contract that is being executed. + pub address: Address, + /// Address of the code that is being executed. + pub code_address: Address, + /// Code of the contract that is being executed. + pub code: Bytes, + /// Value of the message. + pub value: U256, + /// Return data buffer. + pub return_data: Bytes, + /// EVM exit code. + pub exit: ExitCode, + /// How much gas was used. + pub gas_used: u64, + /// Depth of the message. + pub depth: usize, +} + +/// Represents a create or call message. +#[derive(Clone, Debug)] +pub enum CreateOrCallMessage { + /// Represents a create message. + Create(CreateMessage), + /// Represents a call message. + Call(CallMessage), +} + +impl From for CreateOrCallMessage { + fn from(value: CreateMessage) -> Self { + CreateOrCallMessage::Create(value) + } +} + +impl From for CreateOrCallMessage { + fn from(value: CallMessage) -> Self { + CreateOrCallMessage::Call(value) + } +} + +/// Represents a create or call message. +#[derive(Clone, Copy, Debug)] +pub(crate) enum CreateOrCallMessageRef<'a> { + /// Represents a create message. + Create(&'a CreateMessage), + /// Represents a call message. + Call(&'a CallMessage), +} + +impl<'a> CreateOrCallMessageRef<'a> { + pub fn contract_meta(&self) -> Option> { + match self { + CreateOrCallMessageRef::Create(create) => create.contract_meta.as_ref().map(Rc::clone), + CreateOrCallMessageRef::Call(call) => call.contract_meta.as_ref().map(Rc::clone), + } + } + + pub fn exit_code(&self) -> &ExitCode { + match self { + CreateOrCallMessageRef::Create(create) => &create.exit, + CreateOrCallMessageRef::Call(call) => &call.exit, + } + } + + pub fn number_of_subtraces(&self) -> u32 { + match self { + CreateOrCallMessageRef::Create(create) => create.number_of_subtraces, + CreateOrCallMessageRef::Call(call) => call.number_of_subtraces, + } + } + + pub fn return_data(&self) -> &Bytes { + match self { + CreateOrCallMessageRef::Create(create) => &create.return_data, + CreateOrCallMessageRef::Call(call) => &call.return_data, + } + } + + pub fn steps(&self) -> &'a [NestedTraceStep] { + match self { + CreateOrCallMessageRef::Create(create) => create.steps.as_slice(), + CreateOrCallMessageRef::Call(call) => call.steps.as_slice(), + } + } +} + +impl<'a> From<&'a CreateOrCallMessage> for CreateOrCallMessageRef<'a> { + fn from(value: &'a CreateOrCallMessage) -> Self { + match value { + CreateOrCallMessage::Create(create) => CreateOrCallMessageRef::Create(create), + CreateOrCallMessage::Call(call) => CreateOrCallMessageRef::Call(call), + } + } +} + +impl<'a> From<&'a CreateMessage> for CreateOrCallMessageRef<'a> { + fn from(value: &'a CreateMessage) -> Self { + CreateOrCallMessageRef::Create(value) + } +} + +impl<'a> From<&'a CallMessage> for CreateOrCallMessageRef<'a> { + fn from(value: &'a CallMessage) -> Self { + CreateOrCallMessageRef::Call(value) + } +} + +/// Represents a nested trace step with contract metadata. +#[derive(Clone, Debug)] +pub enum NestedTraceStep { + /// Represents a create message. + Create(CreateMessage), + /// Represents a call message. + Call(CallMessage), + /// Represents a precompile message. + Precompile(PrecompileMessage), + /// Minimal EVM step that contains only PC (program counter). + Evm(EvmStep), +} + +/// Minimal EVM step that contains only PC (program counter). +#[derive(Clone, Debug)] +pub struct EvmStep { + /// Program counter + pub pc: u32, +} diff --git a/crates/edr_solidity/src/nested_trace_decoder.rs b/crates/edr_solidity/src/nested_trace_decoder.rs new file mode 100644 index 000000000..267eabeaf --- /dev/null +++ b/crates/edr_solidity/src/nested_trace_decoder.rs @@ -0,0 +1,234 @@ +//! Enriches the [`NestedTrace`] with the resolved [`ContractMetadata`]. +use std::rc::Rc; + +use edr_eth::Bytes; +use serde::{Deserialize, Serialize}; + +use super::{ + nested_trace::CreateMessage, + solidity_stack_trace::{ + FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, + UNRECOGNIZED_FUNCTION_NAME, + }, +}; +use crate::{ + artifacts::BuildInfo, + build_model::{ContractFunctionType, ContractMetadata}, + compiler::create_models_and_decode_bytecodes, + contracts_identifier::ContractsIdentifier, + nested_trace::{NestedTrace, NestedTraceStep}, +}; + +/// Configuration for the [`NestedTraceDecoder`]. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TracingConfig { + /// Build information to use for decoding contracts. + pub build_infos: Option>, + /// Whether to ignore contracts. + pub ignore_contracts: Option, +} + +/// Errors that can occur during the decoding of the nested trace. +#[derive(Debug, thiserror::Error)] +pub enum NestedTraceDecoderError { + /// Errors that can occur when initializing the decoder. + #[error("{0}")] + Initialization(String), +} + +/// Enriches the [`NestedTrace`] with the resolved [`ContractMetadata`]. +#[derive(Default)] +pub struct NestedTraceDecoder { + contracts_identifier: ContractsIdentifier, +} + +impl NestedTraceDecoder { + /// Creates a new [`NestedTraceDecoder`]. + pub fn new(config: &TracingConfig) -> Result { + let contracts_identifier = initialize_contracts_identifier(config) + .map_err(|err| NestedTraceDecoderError::Initialization(err.to_string()))?; + Ok(Self { + contracts_identifier, + }) + } + + /// Adds a bytecode to the decoder. + pub fn add_bytecode(&mut self, bytecode: ContractMetadata) { + self.contracts_identifier.add_bytecode(Rc::new(bytecode)); + } + + /// Decodes the nested trace. + pub fn try_to_decode_message_trace(&mut self, message_trace: NestedTrace) -> NestedTrace { + match message_trace { + precompile @ NestedTrace::Precompile(..) => precompile, + // NOTE: The branches below are the same with the difference of `is_create` + NestedTrace::Call(mut call) => { + let is_create = false; + + let contract_meta = self + .contracts_identifier + .get_bytecode_for_call(call.code.as_ref(), is_create); + + let steps = call + .steps + .into_iter() + .map(|step| { + let trace = match step { + NestedTraceStep::Evm(step) => return NestedTraceStep::Evm(step), + NestedTraceStep::Precompile(precompile) => { + NestedTrace::Precompile(precompile) + } + NestedTraceStep::Create(create) => NestedTrace::Create(create), + NestedTraceStep::Call(call) => NestedTrace::Call(call), + }; + + match self.try_to_decode_message_trace(trace) { + NestedTrace::Precompile(precompile) => { + NestedTraceStep::Precompile(precompile) + } + NestedTrace::Create(create) => NestedTraceStep::Create(create), + NestedTrace::Call(call) => NestedTraceStep::Call(call), + } + }) + .collect::>(); + + call.contract_meta = contract_meta; + call.steps = steps; + + NestedTrace::Call(call) + } + NestedTrace::Create(mut create @ CreateMessage { .. }) => { + let is_create = true; + + let contract_meta = self + .contracts_identifier + .get_bytecode_for_call(create.code.as_ref(), is_create); + + let steps = create + .steps + .into_iter() + .map(|step| { + let trace = match step { + NestedTraceStep::Evm(step) => return NestedTraceStep::Evm(step), + NestedTraceStep::Precompile(precompile) => { + NestedTrace::Precompile(precompile) + } + NestedTraceStep::Create(create) => NestedTrace::Create(create), + NestedTraceStep::Call(call) => NestedTrace::Call(call), + }; + + match self.try_to_decode_message_trace(trace) { + NestedTrace::Precompile(precompile) => { + NestedTraceStep::Precompile(precompile) + } + NestedTrace::Create(create) => NestedTraceStep::Create(create), + NestedTrace::Call(call) => NestedTraceStep::Call(call), + } + }) + .collect::>(); + + create.contract_meta = contract_meta; + create.steps = steps; + + NestedTrace::Create(create) + } + } + } + + /// Returns the contract and function names for the provided calldata. + pub fn get_contract_and_function_names_for_call( + &mut self, + code: &Bytes, + calldata: Option<&Bytes>, + ) -> ContractAndFunctionName { + let is_create = calldata.is_none(); + let bytecode = self + .contracts_identifier + .get_bytecode_for_call(code.as_ref(), is_create); + + let contract = bytecode.map(|bytecode| bytecode.contract.clone()); + let contract = contract.as_ref().map(|c| c.borrow()); + + let contract_name = contract.as_ref().map_or_else( + || UNRECOGNIZED_CONTRACT_NAME.to_string(), + |c| c.name.clone(), + ); + + if is_create { + ContractAndFunctionName { + contract_name, + function_name: None, + } + } else { + match contract { + None => ContractAndFunctionName { + contract_name, + function_name: Some("".to_string()), + }, + Some(contract) => { + let calldata = match calldata { + Some(calldata) => calldata, + None => { + unreachable!("calldata should be Some if is_create is false") + } + }; + + let selector = &calldata.get(..4).unwrap_or(&calldata[..]); + + let func = contract.get_function_from_selector(selector); + + let function_name = match func { + Some(func) => match func.r#type { + ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(), + ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(), + _ => func.name.clone(), + }, + None => UNRECOGNIZED_FUNCTION_NAME.to_string(), + }; + + ContractAndFunctionName { + contract_name, + function_name: Some(function_name), + } + } + } + } + } +} + +/// A contract and a function name in the contract. +pub struct ContractAndFunctionName { + /// The name of the contract. + pub contract_name: String, + /// The name of the function. + pub function_name: Option, +} + +fn initialize_contracts_identifier(config: &TracingConfig) -> anyhow::Result { + let mut contracts_identifier = ContractsIdentifier::default(); + + let Some(build_infos) = &config.build_infos else { + return Ok(contracts_identifier); + }; + + for build_info in build_infos { + let bytecodes = create_models_and_decode_bytecodes( + build_info.solc_version.clone(), + &build_info.input, + &build_info.output, + )?; + + for bytecode in bytecodes { + if config.ignore_contracts == Some(true) + && bytecode.contract.borrow().name.starts_with("Ignored") + { + continue; + } + + contracts_identifier.add_bytecode(Rc::new(bytecode)); + } + } + + Ok(contracts_identifier) +} diff --git a/crates/edr_solidity/src/nested_tracer.rs b/crates/edr_solidity/src/nested_tracer.rs new file mode 100644 index 000000000..5103bc472 --- /dev/null +++ b/crates/edr_solidity/src/nested_tracer.rs @@ -0,0 +1,484 @@ +//! Naive Rust port of the `VmTracer` from Hardhat. + +use std::{cell::RefCell, rc::Rc}; + +use edr_eth::{Address, Bytes, U256}; +use edr_evm::{ + alloy_primitives::U160, + trace::{BeforeMessage, Step}, + ExecutionResult, +}; + +use crate::{ + exit_code::ExitCode, + nested_trace::{ + CallMessage, CreateMessage, EvmStep, NestedTrace, NestedTraceStep, PrecompileMessage, + }, +}; + +/// Errors that can occur during the generation of the nested trace. +#[derive(Debug, thiserror::Error)] +pub enum NestedTracerError { + /// Invalid input: The created address should be defined in the successful + #[error("Created address should be defined in successful create trace")] + MissingAddressInExecutionResult, + /// Invalid input: Missing code address. + #[error("Missing code address")] + MissingCodeAddress, + /// Invalid input: Missing code. + #[error("Missing code")] + MissingCode, + /// Invalid input: Message execution started while a precompile was + /// executing. + #[error("Message execution started while a precompile was executing")] + MessageDuringPreCompile, + /// Invalid input: Step event fired while a precompile was executing. + #[error("Step event fired while a precompile was executing")] + StepDuringPreCompile, +} + +/// The result of converting a trace to a hierarchical trace. +/// If there was an error, the error is stored in `error` and the `result` is +/// the last successfully decoded trace if any. +pub struct NestedTracerResult { + /// The error that occurred during the conversion. + pub error: Option, + /// The last successfully decoded trace. + pub result: Option, +} + +impl From for Result, NestedTracerError> { + fn from(value: NestedTracerResult) -> Self { + match value.error { + Some(error) => Err(error), + None => Ok(value.result), + } + } +} + +/// Observes a trace, collecting information about the execution of the EVM. +pub fn convert_trace_messages_to_hierarchical_trace( + trace: edr_evm::trace::Trace, +) -> NestedTracerResult { + let mut tracer = HierarchicalTracer::new(); + + let error = tracer.add_messages(trace.messages).err(); + let result = tracer.get_last_top_level_message_trace(); + + NestedTracerResult { error, result } +} + +/// Naive Rust port of the `VmTracer` from Hardhat. +struct HierarchicalTracer { + tracing_steps: Vec, + message_traces: Vec>>, +} + +impl Default for HierarchicalTracer { + fn default() -> Self { + Self::new() + } +} + +// Temporarily hardcoded to remove the need of using ethereumjs' common and evm +// TODO(#565): We should be using a more robust check by checking the hardfork +// config (and potentially other config like optional support for RIP +// precompiles, which start at 0x100). +const MAX_PRECOMPILE_NUMBER: u16 = 10; + +impl HierarchicalTracer { + /// Creates a new [`HierarchicalTracer`]. + const fn new() -> Self { + HierarchicalTracer { + tracing_steps: Vec::new(), + message_traces: Vec::new(), + } + } + + /// Returns a reference to the last top-level message trace. + fn get_last_top_level_message_trace(mut self) -> Option { + self.message_traces.pop().map(convert_to_external_trace) + } + + fn add_messages( + &mut self, + messages: Vec, + ) -> Result<(), NestedTracerError> { + for msg in messages { + match msg { + edr_evm::trace::TraceMessage::Before(before) => { + self.add_before_message(before)?; + } + edr_evm::trace::TraceMessage::Step(step) => { + self.add_step(step)?; + } + edr_evm::trace::TraceMessage::After(after) => { + self.add_after_message(after.execution_result)?; + } + } + } + Ok(()) + } + + fn add_before_message(&mut self, message: BeforeMessage) -> Result<(), NestedTracerError> { + let trace: InternalNestedTrace; + + if message.depth == 0 { + self.message_traces.clear(); + self.tracing_steps.clear(); + } + + if let Some(to) = message.to { + let to_as_u160 = U160::from_be_bytes(**to); + + if to_as_u160 <= U160::from(MAX_PRECOMPILE_NUMBER) { + let precompile: u32 = to_as_u160 + .try_into() + .expect("MAX_PRECOMPILE_NUMBER is of type u16 so it fits"); + + let precompile_trace = PrecompileMessage { + value: message.value, + exit: ExitCode::Success, + return_data: Bytes::new(), + depth: message.depth, + gas_used: 0, + precompile, + calldata: message.data, + }; + + trace = InternalNestedTrace::Precompile(precompile_trace); + } else { + // if we enter here, then `to` is not None, therefore + // `code_address` and `code` should be Some + let code_address = message + .code_address + .ok_or(NestedTracerError::MissingCodeAddress)?; + let code = message.code.ok_or(NestedTracerError::MissingCode)?; + + let call_trace = InternalCallMessage { + steps: Vec::new(), + calldata: message.data, + address: to, + code_address, + depth: message.depth, + value: message.value, + exit: ExitCode::Success, + return_data: Bytes::new(), + gas_used: 0, + code: code.original_bytes(), + number_of_subtraces: 0, + }; + + trace = InternalNestedTrace::Call(call_trace); + } + } else { + let create_trace = InternalCreateMessage { + number_of_subtraces: 0, + steps: Vec::new(), + depth: message.depth, + value: message.value, + exit: ExitCode::Success, + return_data: Bytes::new(), + gas_used: 0, + code: message.data, + deployed_contract: None, + }; + + trace = InternalNestedTrace::Create(create_trace); + } + + // We need to share it so that adding steps when processing via stack + // also updates the inner elements + let trace = Rc::new(RefCell::new(trace)); + + if let Some(parent_ref) = self.message_traces.last_mut() { + let mut parent_trace = parent_ref.borrow_mut(); + match &mut *parent_trace { + InternalNestedTrace::Precompile(_) => { + return Err(NestedTracerError::MessageDuringPreCompile); + } + InternalNestedTrace::Create(create) => { + create + .steps + .push(InternalNestedTraceStep::Message(Rc::clone(&trace))); + create.number_of_subtraces += 1; + } + InternalNestedTrace::Call(call) => { + call.steps + .push(InternalNestedTraceStep::Message(Rc::clone(&trace))); + call.number_of_subtraces += 1; + } + }; + } + + self.message_traces.push(trace); + + Ok(()) + } + + fn add_step(&mut self, step: Step) -> Result<(), NestedTracerError> { + if let Some(parent_ref) = self.message_traces.last_mut() { + let mut parent_trace = parent_ref.borrow_mut(); + let steps = match &mut *parent_trace { + InternalNestedTrace::Precompile(_) => { + return Err(NestedTracerError::StepDuringPreCompile); + } + InternalNestedTrace::Create(create) => &mut create.steps, + InternalNestedTrace::Call(call) => &mut call.steps, + }; + + steps.push(InternalNestedTraceStep::Evm(EvmStep { pc: step.pc })); + } + + self.tracing_steps.push(step); + + Ok(()) + } + + fn add_after_message(&mut self, result: ExecutionResult) -> Result<(), NestedTracerError> { + if let Some(trace) = self.message_traces.last_mut() { + let mut trace = trace.borrow_mut(); + + trace.set_gas_used(result.gas_used()); + + match result { + ExecutionResult::Success { output, .. } => { + trace.set_exit_code(ExitCode::Success); + trace.set_return_data(output.data().clone()); + + if let InternalNestedTrace::Create(trace) = &mut *trace { + let address = output + .address() + .ok_or(NestedTracerError::MissingAddressInExecutionResult)?; + + trace.deployed_contract = Some(address.as_slice().to_vec().into()); + } + } + ExecutionResult::Halt { reason, .. } => { + trace.set_exit_code(ExitCode::Halt(reason)); + trace.set_return_data(Bytes::new()); + } + ExecutionResult::Revert { output, .. } => { + trace.set_exit_code(ExitCode::Revert); + trace.set_return_data(output); + } + } + } + + if self.message_traces.len() > 1 { + self.message_traces.pop(); + } + + Ok(()) + } +} + +/// A hierarchical trace where the message steps are shared and mutable via a +/// refcell. +#[derive(Clone, Debug)] +enum InternalNestedTrace { + Create(InternalCreateMessage), + Call(InternalCallMessage), + Precompile(PrecompileMessage), +} + +impl InternalNestedTrace { + fn set_gas_used(&mut self, gas_used: u64) { + match self { + InternalNestedTrace::Create(create) => create.gas_used = gas_used, + InternalNestedTrace::Call(call) => call.gas_used = gas_used, + + InternalNestedTrace::Precompile(precompile) => precompile.gas_used = gas_used, + } + } + + fn set_exit_code(&mut self, exit_code: ExitCode) { + match self { + InternalNestedTrace::Create(create) => create.exit = exit_code, + InternalNestedTrace::Call(call) => call.exit = exit_code, + + InternalNestedTrace::Precompile(precompile) => precompile.exit = exit_code, + } + } + + fn set_return_data(&mut self, return_data: Bytes) { + match self { + InternalNestedTrace::Create(create) => create.return_data = return_data, + InternalNestedTrace::Call(call) => call.return_data = return_data, + + InternalNestedTrace::Precompile(precompile) => precompile.return_data = return_data, + } + } +} + +/// Represents a call message. +#[derive(Clone, Debug)] +struct InternalCallMessage { + // The following is just an optimization: When processing this traces it's useful to know ahead + // of time how many subtraces there are. + /// Number of subtraces. Used to speed up the processing of the traces in + /// JS. + pub number_of_subtraces: u32, + /// Children messages. + pub steps: Vec, + /// Calldata buffer + pub calldata: Bytes, + /// Address of the contract that is being executed. + pub address: Address, + /// Address of the code that is being executed. + pub code_address: Address, + /// Code of the contract that is being executed. + pub code: Bytes, + /// Value of the message. + pub value: U256, + /// Return data buffer. + pub return_data: Bytes, + /// EVM exit code. + pub exit: ExitCode, + /// How much gas was used. + pub gas_used: u64, + /// Depth of the message. + pub depth: usize, +} + +/// Represents a create message. +#[derive(Clone, Debug)] +struct InternalCreateMessage { + // The following is just an optimization: When processing this traces it's useful to know ahead + // of time how many subtraces there are. + /// Number of subtraces. Used to speed up the processing of the traces in + /// JS. + pub number_of_subtraces: u32, + /// Children messages. + pub steps: Vec, + /// Address of the deployed contract. + pub deployed_contract: Option, + /// Code of the contract that is being executed. + pub code: Bytes, + /// Value of the message. + pub value: U256, + /// Return data buffer. + pub return_data: Bytes, + /// EVM exit code. + pub exit: ExitCode, + /// How much gas was used. + pub gas_used: u64, + /// Depth of the message. + pub depth: usize, +} + +/// Represents a message step. Naive Rust port of the `MessageTraceStep` +/// from Hardhat. +#[derive(Clone, Debug)] +enum InternalNestedTraceStep { + /// [`NestedTrace`] variant. + // It's both read and written to (updated) by the `[HierarchicalTracer]`. + Message(Rc>), + /// [`EvmStep`] variant. + Evm(EvmStep), +} + +enum InternalNestedTraceStepWithoutRefCell { + Message(NestedTrace), + Evm(EvmStep), +} + +/// Converts the [`InternalNestedTrace`] into a [`NestedTrace`] by +/// cloning it. +/// +/// # Panics +/// +/// Panics if the value is mutably borrowed. +fn convert_to_external_trace(value: Rc>) -> NestedTrace { + // We can't use `Rc::try_unwrap` because it requires that the `Rc` is unique. + let trace = value.borrow().clone(); + + match trace { + InternalNestedTrace::Create(create) => { + let InternalCreateMessage { + number_of_subtraces, + steps, + deployed_contract, + code, + value, + return_data, + exit, + gas_used, + depth, + } = create; + + NestedTrace::Create(CreateMessage { + number_of_subtraces, + steps: steps.into_iter().map(convert_to_external_step).collect(), + contract_meta: None, + deployed_contract, + code, + value, + return_data, + exit, + gas_used, + depth, + }) + } + InternalNestedTrace::Call(call) => { + let InternalCallMessage { + number_of_subtraces, + steps, + calldata, + address, + code_address, + code, + value, + return_data, + exit, + gas_used, + depth, + } = call; + NestedTrace::Call(CallMessage { + number_of_subtraces, + steps: steps.into_iter().map(convert_to_external_step).collect(), + contract_meta: None, + calldata, + address, + code_address, + code, + value, + return_data, + exit, + gas_used, + depth, + }) + } + InternalNestedTrace::Precompile(precompile) => NestedTrace::Precompile(precompile), + } +} + +/// # Panics +// Panics if a nested value is mutably borrowed. +fn convert_to_external_step(value: InternalNestedTraceStep) -> NestedTraceStep { + match value { + InternalNestedTraceStep::Message(message) => { + InternalNestedTraceStepWithoutRefCell::Message(convert_to_external_trace(message)) + } + InternalNestedTraceStep::Evm(evm_step) => { + InternalNestedTraceStepWithoutRefCell::Evm(evm_step) + } + } + .into() +} + +// This can be a `From` conversion, because it can't panic. +impl From for NestedTraceStep { + fn from(step: InternalNestedTraceStepWithoutRefCell) -> Self { + match step { + InternalNestedTraceStepWithoutRefCell::Message(trace) => match trace { + NestedTrace::Create(create_trace) => NestedTraceStep::Create(create_trace), + NestedTrace::Call(call_trace) => NestedTraceStep::Call(call_trace), + NestedTrace::Precompile(precompile_trace) => { + NestedTraceStep::Precompile(precompile_trace) + } + }, + InternalNestedTraceStepWithoutRefCell::Evm(evm_step) => NestedTraceStep::Evm(evm_step), + } + } +} diff --git a/crates/edr_solidity/src/return_data.rs b/crates/edr_solidity/src/return_data.rs index 3161279f8..b9371171f 100644 --- a/crates/edr_solidity/src/return_data.rs +++ b/crates/edr_solidity/src/return_data.rs @@ -43,16 +43,6 @@ impl ReturnData { self.selector == Some(Panic::SELECTOR) } - pub fn decode_error(&self) -> Result { - if self.is_empty() { - return Ok(String::new()); - } - - let result = Error::abi_decode(&self.value[..], false)?; - - Ok(result._0) - } - /// Decodes the panic error code from the return data. pub fn decode_panic(&self) -> Result { Panic::abi_decode(&self.value[..], false).map(|p| p._0) diff --git a/crates/edr_solidity/src/solidity_stack_trace.rs b/crates/edr_solidity/src/solidity_stack_trace.rs index 3dde638a7..9910772fd 100644 --- a/crates/edr_solidity/src/solidity_stack_trace.rs +++ b/crates/edr_solidity/src/solidity_stack_trace.rs @@ -1,25 +1,38 @@ +//! Stack trace entries for Solidity errors. + use edr_eth::{Address, Bytes, U256}; use crate::build_model::ContractFunctionType; -pub const FALLBACK_FUNCTION_NAME: &str = ""; -pub const RECEIVE_FUNCTION_NAME: &str = ""; -pub const CONSTRUCTOR_FUNCTION_NAME: &str = "constructor"; -pub const UNRECOGNIZED_FUNCTION_NAME: &str = ""; -pub const UNKNOWN_FUNCTION_NAME: &str = ""; -pub const PRECOMPILE_FUNCTION_NAME: &str = ""; -pub const UNRECOGNIZED_CONTRACT_NAME: &str = ""; +pub(crate) const FALLBACK_FUNCTION_NAME: &str = ""; +pub(crate) const RECEIVE_FUNCTION_NAME: &str = ""; +pub(crate) const CONSTRUCTOR_FUNCTION_NAME: &str = "constructor"; +pub(crate) const UNRECOGNIZED_FUNCTION_NAME: &str = ""; +#[allow(unused)] +pub(crate) const UNKNOWN_FUNCTION_NAME: &str = ""; +#[allow(unused)] +pub(crate) const PRECOMPILE_FUNCTION_NAME: &str = ""; +pub(crate) const UNRECOGNIZED_CONTRACT_NAME: &str = ""; +/// A Solidity source reference. #[derive(Debug, Clone, Eq, PartialEq)] pub struct SourceReference { + /// The name of the source file. pub source_name: String, + /// The content of the source file. pub source_content: String, + /// The name of the contract. pub contract: Option, + /// The name of the function. pub function: Option, + /// The line number. pub line: u32, + /// The character range on the line. pub range: (u32, u32), } +// The names are self-explanatory. +#[allow(missing_docs)] #[derive(Debug, Clone)] pub enum StackTraceEntry { CallstackEntry { @@ -110,6 +123,7 @@ pub enum StackTraceEntry { } impl StackTraceEntry { + /// Get the source reference of the stack trace entry if any. pub fn source_reference(&self) -> Option<&SourceReference> { match self { StackTraceEntry::CallstackEntry { diff --git a/crates/edr_solidity/src/solidity_tracer.rs b/crates/edr_solidity/src/solidity_tracer.rs index f78be0481..137f8466c 100644 --- a/crates/edr_solidity/src/solidity_tracer.rs +++ b/crates/edr_solidity/src/solidity_tracer.rs @@ -1,77 +1,79 @@ +//! Generates JS-style stack traces for Solidity errors. + use edr_evm::interpreter::OpCode; -use either::Either; use crate::{ - build_model::{BytecodeError, Instruction, JumpType}, + build_model::{ContractMetadataError, Instruction, JumpType}, error_inferrer, - error_inferrer::{instruction_to_callstack_stack_trace_entry, InferrerError, SubmessageData}, + error_inferrer::{InferrerError, SubmessageData}, mapped_inline_internal_functions_heuristics::{ adjust_stack_trace, stack_trace_may_require_adjustments, HeuristicsError, }, - message_trace::{ - CallMessageTrace, CreateMessageTrace, EvmStep, MessageTrace, MessageTraceStep, - PrecompileMessageTrace, + nested_trace::{ + CallMessage, CreateMessage, CreateOrCallMessage, CreateOrCallMessageRef, EvmStep, + NestedTrace, NestedTraceStep, PrecompileMessage, }, solidity_stack_trace::StackTraceEntry, }; -pub struct SolidityTracer; - +/// Errors that can occur during the generation of the stack trace. #[derive(Debug, thiserror::Error)] pub enum SolidityTracerError { + /// Errors that can occur when decoding the contract metadata. #[error(transparent)] - BytecodeError(#[from] BytecodeError), + ContractMetadata(#[from] ContractMetadataError), + /// Errors that can occur during the inference of the stack trace. #[error(transparent)] ErrorInferrer(#[from] InferrerError), + /// Errors that can occur during the heuristics. #[error(transparent)] Heuristics(#[from] HeuristicsError), } -pub fn get_stack_trace(trace: MessageTrace) -> Result, SolidityTracerError> { - if !trace.exit().is_error() { - return Ok(vec![]); +/// Generates a stack trace for the provided nested trace. +pub fn get_stack_trace(trace: NestedTrace) -> Result, SolidityTracerError> { + if !trace.exit_code().is_error() { + return Ok(Vec::default()); } match trace { - MessageTrace::Precompile(precompile) => { - Ok(get_precompile_message_stack_trace(&precompile)?) - } - MessageTrace::Call(call) if call.bytecode().is_some() => { + NestedTrace::Precompile(precompile) => Ok(get_precompile_message_stack_trace(&precompile)?), + NestedTrace::Call(call) if call.contract_meta.is_some() => { Ok(get_call_message_stack_trace(call)?) } - MessageTrace::Create(create) if create.bytecode().is_some() => { + NestedTrace::Create(create) if create.contract_meta.is_some() => { Ok(get_create_message_stack_trace(create)?) } // No bytecode is present - MessageTrace::Call(call) => Ok(get_unrecognized_message_stack_trace(Either::Left(call))?), - MessageTrace::Create(create) => { - Ok(get_unrecognized_message_stack_trace(Either::Right(create))?) - } + NestedTrace::Call(ref call) => Ok(get_unrecognized_message_stack_trace( + CreateOrCallMessageRef::Call(call), + )?), + NestedTrace::Create(ref create) => Ok(get_unrecognized_message_stack_trace( + CreateOrCallMessageRef::Create(create), + )?), } } -fn get_last_subtrace<'a>( - trace: &'a Either, -) -> Option { - let (number_of_subtraces, steps) = match trace { - Either::Left(create) => (create.number_of_subtraces(), create.steps()), - Either::Right(call) => (call.number_of_subtraces(), call.steps()), - }; - - if number_of_subtraces == 0 { +fn get_last_subtrace(trace: CreateOrCallMessageRef<'_>) -> Option { + if trace.number_of_subtraces() == 0 { return None; } - steps.into_iter().rev().find_map(|step| match step { - MessageTraceStep::Evm(EvmStep { .. }) => None, - MessageTraceStep::Precompile(precompile) => Some(MessageTrace::Precompile(precompile)), - MessageTraceStep::Call(call) => Some(MessageTrace::Call(call)), - MessageTraceStep::Create(create) => Some(MessageTrace::Create(create)), - }) + trace + .steps() + .iter() + .cloned() + .rev() + .find_map(|step| match step { + NestedTraceStep::Evm(EvmStep { .. }) => None, + NestedTraceStep::Precompile(precompile) => Some(NestedTrace::Precompile(precompile)), + NestedTraceStep::Call(call) => Some(NestedTrace::Call(call)), + NestedTraceStep::Create(create) => Some(NestedTrace::Create(create)), + }) } fn get_precompile_message_stack_trace( - trace: &PrecompileMessageTrace, + trace: &PrecompileMessage, ) -> Result, SolidityTracerError> { Ok(vec![StackTraceEntry::PrecompileError { precompile: trace.precompile, @@ -79,7 +81,7 @@ fn get_precompile_message_stack_trace( } fn get_create_message_stack_trace( - trace: CreateMessageTrace, + trace: CreateMessage, ) -> Result, SolidityTracerError> { let inferred_error = error_inferrer::infer_before_tracing_create_message(&trace)?; @@ -87,11 +89,11 @@ fn get_create_message_stack_trace( return Ok(inferred_error); } - trace_evm_execution(Either::Right(trace)) + trace_evm_execution(trace.into()) } fn get_call_message_stack_trace( - trace: CallMessageTrace, + trace: CallMessage, ) -> Result, SolidityTracerError> { let inferred_error = error_inferrer::infer_before_tracing_call_message(&trace)?; @@ -99,29 +101,24 @@ fn get_call_message_stack_trace( return Ok(inferred_error); } - trace_evm_execution(Either::Left(trace)) + trace_evm_execution(trace.into()) } fn get_unrecognized_message_stack_trace( - trace: Either, + trace: CreateOrCallMessageRef<'_>, ) -> Result, SolidityTracerError> { - let (trace_exit_kind, trace_return_data) = match &trace { - Either::Left(call) => (call.exit(), call.return_data()), - Either::Right(create) => (create.exit(), create.return_data()), - }; + let trace_exit_kind = trace.exit_code(); + let trace_return_data = trace.return_data(); - let subtrace = get_last_subtrace(&trace); + let subtrace = get_last_subtrace(trace); if let Some(subtrace) = subtrace { let (is_error, return_data) = match &subtrace { - MessageTrace::Precompile(precompile) => ( - precompile.exit().is_error(), - precompile.return_data().clone(), - ), - MessageTrace::Call(call) => (call.exit().is_error(), call.return_data().clone()), - MessageTrace::Create(create) => { - (create.exit().is_error(), create.return_data().clone()) + NestedTrace::Precompile(precompile) => { + (precompile.exit.is_error(), precompile.return_data.clone()) } + NestedTrace::Call(call) => (call.exit.is_error(), call.return_data.clone()), + NestedTrace::Create(create) => (create.exit.is_error(), create.return_data.clone()), }; // This is not a very exact heuristic, but most of the time it will be right, as @@ -129,12 +126,10 @@ fn get_unrecognized_message_stack_trace( // solidity if is_error && trace_return_data.as_ref() == return_data.as_ref() { let unrecognized_entry: StackTraceEntry = match trace { - Either::Left(CallMessageTrace { address, .. }) => { - StackTraceEntry::UnrecognizedContractCallstackEntry { - address: address.clone(), - } + CreateOrCallMessageRef::Call(CallMessage { address, .. }) => { + StackTraceEntry::UnrecognizedContractCallstackEntry { address: *address } } - Either::Right(CreateMessageTrace { .. }) => { + CreateOrCallMessageRef::Create(_) => { StackTraceEntry::UnrecognizedCreateCallstackEntry } }; @@ -155,74 +150,72 @@ fn get_unrecognized_message_stack_trace( let is_invalid_opcode_error = trace_exit_kind.is_invalid_opcode_error(); match trace { - Either::Left(trace @ CallMessageTrace { .. }) => { + CreateOrCallMessageRef::Call(call) => { Ok(vec![StackTraceEntry::UnrecognizedContractError { - address: trace.address.clone(), - return_data: trace.return_data().clone(), + address: call.address, + return_data: call.return_data.clone(), is_invalid_opcode_error, - } - .into()]) + }]) } - Either::Right(trace @ CreateMessageTrace { .. }) => { + CreateOrCallMessageRef::Create(call) => { Ok(vec![StackTraceEntry::UnrecognizedCreateError { - return_data: trace.return_data().clone(), + return_data: call.return_data.clone(), is_invalid_opcode_error, - } - .into()]) + }]) } } } fn trace_evm_execution( - trace: Either, + trace: CreateOrCallMessage, ) -> Result, SolidityTracerError> { - let stack_trace = raw_trace_evm_execution(&trace)?; + let stack_trace = raw_trace_evm_execution(CreateOrCallMessageRef::from(&trace))?; - if stack_trace_may_require_adjustments(&stack_trace, &trace)? { - return adjust_stack_trace(stack_trace, &trace).map_err(SolidityTracerError::from); + if stack_trace_may_require_adjustments(&stack_trace, CreateOrCallMessageRef::from(&trace))? { + return adjust_stack_trace(stack_trace, CreateOrCallMessageRef::from(&trace)) + .map_err(SolidityTracerError::from); } Ok(stack_trace) } fn raw_trace_evm_execution( - trace: &Either, + trace: CreateOrCallMessageRef<'_>, ) -> Result, SolidityTracerError> { - let (bytecode, steps, number_of_subtraces) = match &trace { - Either::Left(call) => (call.bytecode(), call.steps(), call.number_of_subtraces()), - Either::Right(create) => ( - create.bytecode(), - create.steps(), - create.number_of_subtraces(), - ), - }; - let bytecode = bytecode.as_ref().expect("JS code asserts"); - - let mut stacktrace: Vec = vec![]; + let contract_meta = trace + .contract_meta() + .ok_or(InferrerError::MissingContract)?; + let steps = trace.steps(); + let number_of_subtraces = trace.number_of_subtraces(); + + let mut stacktrace: Vec = Vec::default(); let mut subtraces_seen = 0; // There was a jump into a function according to the sourcemaps let mut jumped_into_function = false; - let mut function_jumpdests: Vec<&Instruction> = vec![]; + let mut function_jumpdests: Vec<&Instruction> = Vec::default(); let mut last_submessage_data: Option = None; let mut iter = steps.iter().enumerate().peekable(); while let Some((step_index, step)) = iter.next() { - if let MessageTraceStep::Evm(EvmStep { pc }) = step { - let inst = bytecode.get_instruction(*pc)?; + if let NestedTraceStep::Evm(EvmStep { pc }) = step { + let inst = contract_meta.get_instruction(*pc)?; if inst.jump_type == JumpType::IntoFunction && iter.peek().is_some() { let (_, next_step) = iter.peek().unwrap(); - let MessageTraceStep::Evm(next_evm_step) = next_step else { - unreachable!("JS code asserted that"); + let NestedTraceStep::Evm(next_evm_step) = next_step else { + return Err(InferrerError::ExpectedEvmStep.into()); }; - let next_inst = bytecode.get_instruction(next_evm_step.pc)?; + let next_inst = contract_meta.get_instruction(next_evm_step.pc)?; if next_inst.opcode == OpCode::JUMPDEST { - let frame = instruction_to_callstack_stack_trace_entry(bytecode, inst)?; + let frame = error_inferrer::instruction_to_callstack_stack_trace_entry( + &contract_meta, + inst, + )?; stacktrace.push(frame); if next_inst.location.is_some() { jumped_into_function = true; @@ -235,13 +228,12 @@ fn raw_trace_evm_execution( } } else { let message_trace = match step { - MessageTraceStep::Evm(_) => unreachable!("branch is taken above"), - // TODO avoid clones - MessageTraceStep::Precompile(precompile) => { - MessageTrace::Precompile(precompile.clone()) + NestedTraceStep::Evm(_) => unreachable!("branch is taken above"), + NestedTraceStep::Precompile(precompile) => { + NestedTrace::Precompile(precompile.clone()) } - MessageTraceStep::Call(call) => MessageTrace::Call(call.clone()), - MessageTraceStep::Create(create) => MessageTrace::Create(create.clone()), + NestedTraceStep::Call(call) => NestedTrace::Call(call.clone()), + NestedTraceStep::Create(create) => NestedTrace::Create(create.clone()), }; subtraces_seen += 1; diff --git a/crates/edr_solidity/src/vm_trace_decoder.rs b/crates/edr_solidity/src/vm_trace_decoder.rs deleted file mode 100644 index 63bfbee92..000000000 --- a/crates/edr_solidity/src/vm_trace_decoder.rs +++ /dev/null @@ -1,211 +0,0 @@ -use std::rc::Rc; - -use edr_eth::Bytes; -use serde::{Deserialize, Serialize}; - -use super::{ - message_trace::CreateMessageTrace, - solidity_stack_trace::{ - FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, - UNRECOGNIZED_FUNCTION_NAME, - }, -}; -use crate::{ - artifacts::BuildInfo, - build_model::{Bytecode, ContractFunctionType}, - compiler::create_models_and_decode_bytecodes, - contracts_identifier::ContractsIdentifier, - message_trace::{MessageTrace, MessageTraceStep}, -}; - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TracingConfig { - pub build_infos: Option>, - pub ignore_contracts: Option, -} - -#[derive(Default)] -pub struct VmTraceDecoder { - contracts_identifier: ContractsIdentifier, -} - -impl VmTraceDecoder { - pub fn new() -> Self { - Self::default() - } - - pub fn add_bytecode(&mut self, bytecode: Bytecode) { - self.add_bytecode_inner(Rc::new(bytecode)); - } - - pub fn add_bytecode_inner(&mut self, bytecode: Rc) { - self.contracts_identifier.add_bytecode(bytecode); - } - - pub fn try_to_decode_message_trace(&mut self, message_trace: MessageTrace) -> MessageTrace { - match message_trace { - precompile @ MessageTrace::Precompile(..) => precompile, - // NOTE: The branches below are the same with the difference of `is_create` - MessageTrace::Call(mut call) => { - let is_create = false; - - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(call.code().as_ref(), is_create); - - let steps = call.steps().into_iter().map(|step| { - let trace = match step { - MessageTraceStep::Evm(step) => return MessageTraceStep::Evm(step), - MessageTraceStep::Precompile(precompile) => { - MessageTrace::Precompile(precompile) - } - MessageTraceStep::Create(create) => MessageTrace::Create(create), - MessageTraceStep::Call(call) => MessageTrace::Call(call), - }; - - match self.try_to_decode_message_trace(trace) { - MessageTrace::Precompile(precompile) => { - MessageTraceStep::Precompile(precompile) - } - MessageTrace::Create(create) => MessageTraceStep::Create(create), - MessageTrace::Call(call) => MessageTraceStep::Call(call), - } - }); - - call.set_bytecode(bytecode); - call.set_steps(steps); - - MessageTrace::Call(call) - } - MessageTrace::Create(mut create @ CreateMessageTrace { .. }) => { - let is_create = true; - - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(create.code().as_ref(), is_create); - - let steps = create - .steps() - .into_iter() - .map(|step| { - let trace = match step { - MessageTraceStep::Evm(step) => return MessageTraceStep::Evm(step), - MessageTraceStep::Precompile(precompile) => { - MessageTrace::Precompile(precompile) - } - MessageTraceStep::Create(create) => MessageTrace::Create(create), - MessageTraceStep::Call(call) => MessageTrace::Call(call), - }; - - match self.try_to_decode_message_trace(trace) { - MessageTrace::Precompile(precompile) => { - MessageTraceStep::Precompile(precompile) - } - MessageTrace::Create(create) => MessageTraceStep::Create(create), - MessageTrace::Call(call) => MessageTraceStep::Call(call), - } - }) - .collect::>(); - - create.set_bytecode(bytecode); - create.set_steps(steps); - - MessageTrace::Create(create) - } - } - } - - pub fn get_contract_and_function_names_for_call( - &mut self, - code: &Bytes, - calldata: Option<&Bytes>, - ) -> ContractAndFunctionName { - let is_create = calldata.is_none(); - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(code.as_ref(), is_create); - - let contract = bytecode.map(|bytecode| bytecode.contract.clone()); - let contract = contract.as_ref().map(|c| c.borrow()); - - let contract_name = contract.as_ref().map_or_else( - || UNRECOGNIZED_CONTRACT_NAME.to_string(), - |c| c.name.clone(), - ); - - if is_create { - ContractAndFunctionName { - contract_name, - function_name: None, - } - } else { - match contract { - None => ContractAndFunctionName { - contract_name, - function_name: Some("".to_string()), - }, - Some(contract) => { - let calldata = match calldata { - Some(calldata) => calldata, - None => { - unreachable!("calldata should be Some if is_create is false") - } - }; - - let selector = &calldata.get(..4).unwrap_or(&calldata[..]); - - let func = contract.get_function_from_selector(selector); - - let function_name = match func { - Some(func) => match func.r#type { - ContractFunctionType::Fallback => FALLBACK_FUNCTION_NAME.to_string(), - ContractFunctionType::Receive => RECEIVE_FUNCTION_NAME.to_string(), - _ => func.name.clone(), - }, - None => UNRECOGNIZED_FUNCTION_NAME.to_string(), - }; - - ContractAndFunctionName { - contract_name, - function_name: Some(function_name), - } - } - } - } - } -} - -pub struct ContractAndFunctionName { - pub contract_name: String, - pub function_name: Option, -} - -pub fn initialize_vm_trace_decoder( - vm_trace_decoder: &mut VmTraceDecoder, - config: &TracingConfig, -) -> anyhow::Result<()> { - let Some(build_infos) = &config.build_infos else { - return Ok(()); - }; - - for build_info in build_infos { - let bytecodes = create_models_and_decode_bytecodes( - build_info.solc_version.clone(), - &build_info.input, - &build_info.output, - )?; - - for bytecode in bytecodes { - if config.ignore_contracts == Some(true) - && bytecode.contract.borrow().name.starts_with("Ignored") - { - continue; - } - - vm_trace_decoder.add_bytecode_inner(Rc::new(bytecode)); - } - } - - Ok(()) -} diff --git a/crates/edr_solidity/src/vm_tracer.rs b/crates/edr_solidity/src/vm_tracer.rs deleted file mode 100644 index 10c7cd541..000000000 --- a/crates/edr_solidity/src/vm_tracer.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! Naive Rust port of the `VmTracer` from Hardhat. - -use std::{cell::RefCell, rc::Rc}; - -use edr_eth::Bytes; -use edr_evm::{ - alloy_primitives::U160, - trace::{BeforeMessage, Step}, - ExecutionResult, -}; - -use crate::message_trace::{ - BaseEvmMessageTrace, BaseMessageTrace, CallMessageTrace, CreateMessageTrace, EvmStep, ExitCode, - MessageTrace, PrecompileMessageTrace, VmTracerMessageTraceStep, -}; - -type MessageTraceRefCell = Rc>; - -/// Naive Rust port of the `VmTracer` from Hardhat. -pub struct VmTracer { - tracing_steps: Vec, - message_traces: Vec, - last_error: Option<&'static str>, -} - -impl Default for VmTracer { - fn default() -> Self { - Self::new() - } -} - -// Temporarily hardcoded to remove the need of using ethereumjs' common and evm -// TODO(#565): We should be using a more robust check by checking the hardfork -// config (and potentially other config like optional support for RIP -// precompiles, which start at 0x100). -const MAX_PRECOMPILE_NUMBER: u16 = 10; - -impl VmTracer { - /// Creates a new [`VmTracer`]. - pub const fn new() -> Self { - VmTracer { - tracing_steps: vec![], - message_traces: vec![], - last_error: None, - } - } - - /// Returns a reference to the last top-level message trace. - /// # Panics - /// This function panics if executed concurrently with [`Self::observe`]. - pub fn get_last_top_level_message_trace(&self) -> Option { - self.message_traces - .last() - .map(|x| RefCell::borrow(x).clone()) - } - - /// Returns a reference to the last top-level message trace. - /// The reference is only being mutated for the duration of - /// [`Self::observe`] call. - pub fn get_last_top_level_message_trace_ref(&self) -> Option<&MessageTraceRefCell> { - self.message_traces.first() - } - - /// Retrieves the last error that occurred during the tracing process. - pub fn get_last_error(&self) -> Option<&'static str> { - self.last_error - } - - /// Observes a trace, collecting information about the execution of the EVM. - pub fn observe(&mut self, trace: &edr_evm::trace::Trace) { - for msg in &trace.messages { - match msg.clone() { - edr_evm::trace::TraceMessage::Before(before) => { - self.add_before_message(before); - } - edr_evm::trace::TraceMessage::Step(step) => { - self.add_step(step); - } - edr_evm::trace::TraceMessage::After(after) => { - self.add_after_message(after.execution_result); - } - } - } - } - - fn should_keep_tracing(&self) -> bool { - self.last_error.is_none() - } - - fn add_before_message(&mut self, message: BeforeMessage) { - if !self.should_keep_tracing() { - return; - } - - let trace: MessageTrace; - - if message.depth == 0 { - self.message_traces.clear(); - self.tracing_steps.clear(); - } - - if message.to.is_none() { - let create_trace = CreateMessageTrace { - base: BaseEvmMessageTrace { - base: BaseMessageTrace { - depth: message.depth, - value: message.value, - exit: ExitCode::Success, - return_data: Bytes::new(), - gas_used: 0, - }, - code: message.data, - steps: vec![], - number_of_subtraces: 0, - // this was not in the original code - assumed to be None/undefined? - bytecode: None, - }, - - deployed_contract: None, - }; - - trace = MessageTrace::Create(create_trace); - } else { - let to = message.to.unwrap(); - let to_as_bigint = U160::from_be_bytes(**to); - - if to_as_bigint <= U160::from(MAX_PRECOMPILE_NUMBER) { - let precompile: u32 = to_as_bigint - .try_into() - .expect("MAX_PRECOMPILE_NUMBER is of type u16 so it fits"); - - let precompile_trace = PrecompileMessageTrace { - base: BaseMessageTrace { - value: message.value, - exit: ExitCode::Success, - return_data: Bytes::new(), - depth: message.depth, - gas_used: 0, - }, - precompile, - calldata: message.data, - }; - - trace = MessageTrace::Precompile(precompile_trace); - } else { - // if we enter here, then `to` is not None, therefore - // `code_address` and `code` should be Some - let code_address = if let Some(value) = message.code_address { - value - } else { - self.last_error = Some("code_address should be Some"); - return; - }; - let code = if let Some(value) = message.code { - value - } else { - self.last_error = Some("code should be Some"); - return; - }; - - let call_trace = CallMessageTrace { - base: BaseEvmMessageTrace { - base: BaseMessageTrace { - depth: message.depth, - value: message.value, - exit: ExitCode::Success, - return_data: Bytes::new(), - gas_used: 0, - }, - code: code.original_bytes(), - steps: vec![], - number_of_subtraces: 0, - // this was not in the original code - assumed to be None/undefined? - bytecode: None, - }, - calldata: message.data, - address: message.to.unwrap(), - code_address, - }; - - trace = MessageTrace::Call(call_trace); - } - } - - // We need to share it so that adding steps when processing via stack - // also updates the inner elements - let trace = Rc::new(RefCell::new(trace)); - - if let Some(parent_ref) = self.message_traces.last_mut() { - let mut parent_trace = parent_ref.borrow_mut(); - let parent_trace = match &mut *parent_trace { - MessageTrace::Precompile(_) => { - self.last_error = Some("This should not happen: message execution started while a precompile was executing"); - return; - } - MessageTrace::Create(create) => &mut create.base, - MessageTrace::Call(call) => &mut call.base, - }; - - parent_trace - .steps - .push(VmTracerMessageTraceStep::Message(Rc::clone(&trace))); - parent_trace.number_of_subtraces += 1; - } - - self.message_traces.push(trace); - } - - fn add_step(&mut self, step: Step) { - if !self.should_keep_tracing() { - return; - } - - if let Some(parent_ref) = self.message_traces.last_mut() { - let mut parent_trace = parent_ref.borrow_mut(); - let parent_trace = match &mut *parent_trace { - MessageTrace::Precompile(_) => { - self.last_error = Some( - "This should not happen: step event fired while a precompile was executing", - ); - return; - } - MessageTrace::Create(create) => &mut create.base, - MessageTrace::Call(call) => &mut call.base, - }; - - parent_trace - .steps - .push(VmTracerMessageTraceStep::Evm(EvmStep { pc: step.pc })); - } - - self.tracing_steps.push(step); - } - - fn add_after_message(&mut self, result: ExecutionResult) { - if !self.should_keep_tracing() { - return; - } - - if let Some(trace) = self.message_traces.last_mut() { - let mut trace = trace.borrow_mut(); - - trace.base().gas_used = result.gas_used(); - - match result { - ExecutionResult::Success { output, .. } => { - trace.base().exit = ExitCode::Success; - trace.base().return_data = output.data().clone(); - - if let MessageTrace::Create(trace) = &mut *trace { - let address = output - .address() - // The original code asserted this directly - .expect("address should be defined in create trace"); - - trace.deployed_contract = Some(address.as_slice().to_vec().into()); - } - } - ExecutionResult::Halt { reason, .. } => { - trace.base().exit = ExitCode::Halt(reason); - trace.base().return_data = Bytes::new(); - } - ExecutionResult::Revert { output, .. } => { - trace.base().exit = ExitCode::Revert; - trace.base().return_data = output; - } - } - } - - if self.message_traces.len() > 1 { - self.message_traces.pop(); - } - } -} From 8de6f2ef4a1983a749b2ec0665d6a8fdd47d263a Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 19 Dec 2024 18:45:43 +0000 Subject: [PATCH 12/30] Make build model Send + Sync --- Cargo.lock | 1 + crates/edr_solidity/Cargo.toml | 1 + crates/edr_solidity/src/build_model.rs | 78 +++++++-------- crates/edr_solidity/src/compiler.rs | 98 +++++++++---------- crates/edr_solidity/src/error_inferrer.rs | 68 ++++++------- .../edr_solidity/src/nested_trace_decoder.rs | 4 +- crates/edr_solidity/src/source_map.rs | 6 +- 7 files changed, 125 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fab749c6..9c0453df8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1289,6 +1289,7 @@ dependencies = [ "edr_evm", "either", "indexmap 2.2.6", + "parking_lot 0.12.1", "semver 1.0.23", "serde", "serde_json", diff --git a/crates/edr_solidity/Cargo.toml b/crates/edr_solidity/Cargo.toml index 411b22c3b..458ae07da 100644 --- a/crates/edr_solidity/Cargo.toml +++ b/crates/edr_solidity/Cargo.toml @@ -11,6 +11,7 @@ alloy-sol-types = { version = "0.7.4", default-features = false, features = ["st edr_eth = { version = "0.3.5", path = "../edr_eth" } edr_evm = { version = "0.3.5", path = "../edr_evm" } indexmap = { version = "2", features = ["serde"] } +parking_lot = { version = "0.12.1", default-features = false } serde = { version = "1.0.158", default-features = false, features = ["std"] } serde_json = { version = "1.0.89", features = ["preserve_order"] } strum = { version = "0.26.0", features = ["derive"] } diff --git a/crates/edr_solidity/src/build_model.rs b/crates/edr_solidity/src/build_model.rs index 1df4bd73a..1649a3d3b 100644 --- a/crates/edr_solidity/src/build_model.rs +++ b/crates/edr_solidity/src/build_model.rs @@ -11,14 +11,14 @@ //! - related resolved [`Instruction`]s and their location use std::{ - cell::{OnceCell, RefCell}, collections::HashMap, - rc::{Rc, Weak}, + sync::{Arc, OnceLock, Weak}, }; use alloy_dyn_abi::ErrorExt; use edr_evm::{hex, interpreter::OpCode}; use indexmap::IndexMap; +use parking_lot::RwLock; use serde::Serialize; use serde_json::Value; @@ -28,19 +28,19 @@ use crate::artifacts::{ContractAbiEntry, ImmutableReference}; #[derive(Debug, Default)] pub struct BuildModel { /// Maps the contract ID to the contract. - pub contract_id_to_contract: IndexMap>>, + pub contract_id_to_contract: IndexMap>>, /// Maps the file ID to the source file. - pub file_id_to_source_file: Rc, + pub file_id_to_source_file: Arc, } /// Type alias for the source file mapping used by [`BuildModel`]. -pub type BuildModelSources = HashMap>>; +pub type BuildModelSources = HashMap>>; /// A source file. #[derive(Debug)] pub struct SourceFile { // Referenced because it can be later updated by outside code - functions: Vec>, + functions: Vec>, /// The name of the source file. pub source_name: String, @@ -62,7 +62,7 @@ impl SourceFile { /// Adds a [`ContractFunction`] to the source file. /// # Note /// Should only be called when resolving the source model. - pub fn add_function(&mut self, contract_function: Rc) { + pub fn add_function(&mut self, contract_function: Arc) { self.functions.push(contract_function); } @@ -71,7 +71,7 @@ impl SourceFile { pub fn get_containing_function( &self, location: &SourceLocation, - ) -> Option<&Rc> { + ) -> Option<&Arc> { self.functions .iter() .find(|func| func.location.contains(location)) @@ -86,7 +86,7 @@ impl SourceFile { /// we can no longer access it through this reference. pub struct SourceLocation { /// Cached 1-based line number of the source location. - line: OnceCell, + line: OnceLock, /// A weak reference to the source files mapping. /// /// Used to access the source file when needed. @@ -112,17 +112,17 @@ impl SourceLocation { /// Creates a new [`SourceLocation`] with the provided file ID, offset, and /// length. pub fn new( - sources: Rc, + sources: Arc, file_id: u32, offset: u32, length: u32, ) -> SourceLocation { SourceLocation { - line: OnceCell::new(), + line: OnceLock::new(), // We need to break the cycle between SourceLocation and SourceFile // (via ContractFunction); the Bytecode struct is owning the build // model sources, so we should always be alive. - sources: Rc::downgrade(&sources), + sources: Arc::downgrade(&sources), file_id, offset, length, @@ -134,7 +134,7 @@ impl SourceLocation { /// This function panics if the source location is dangling, i.e. source /// files mapping has been dropped (currently only owned by the /// [`ContractMetadata`]). - pub fn file(&self) -> Rc> { + pub fn file(&self) -> Arc> { match self.sources.upgrade() { Some(ref sources) => sources.get(&self.file_id).unwrap().clone(), None => panic!("dangling SourceLocation; did you drop the owning Bytecode?"), @@ -148,7 +148,7 @@ impl SourceLocation { } let file = self.file(); - let contents = &file.borrow().content; + let contents = &file.read().content; *self.line.get_or_init(move || { let mut line = 1; @@ -164,9 +164,9 @@ impl SourceLocation { } /// Returns the [`ContractFunction`] that contains the source location. - pub fn get_containing_function(&self) -> Option> { + pub fn get_containing_function(&self) -> Option> { let file = self.file(); - let file = file.borrow(); + let file = file.read(); file.get_containing_function(self).cloned() } @@ -216,7 +216,7 @@ pub struct ContractFunction { /// The type of the contract function. pub r#type: ContractFunctionType, /// The source location of the contract function. - pub location: Rc, + pub location: Arc, /// The name of the contract that contains the contract function. pub contract_name: Option, /// The visibility of the contract function. @@ -225,7 +225,7 @@ pub struct ContractFunction { pub is_payable: Option, /// The selector of the contract function. /// May be fixed up by [`Contract::correct_selector`]. - pub selector: RefCell>>, + pub selector: RwLock>>, /// JSON ABI parameter types of the contract function. pub param_types: Option>, } @@ -305,7 +305,7 @@ pub struct Instruction { /// The push data of the instruction, if it's a `PUSHx` instruction. pub push_data: Option>, /// The source location of the instruction, if any. - pub location: Option>, + pub location: Option>, } /// The type of a jump. @@ -339,9 +339,9 @@ pub struct ContractMetadata { // This owns the source files transitively used by the source locations // in the Instruction structs. - _sources: Rc, + _sources: Arc, /// Contract that the bytecode belongs to. - pub contract: Rc>, + pub contract: Arc>, /// Whether the bytecode is a deployment bytecode. pub is_deployment: bool, /// Normalized code of the bytecode, i.e. replaced with zeroes for the @@ -359,8 +359,8 @@ impl ContractMetadata { /// Creates a new [`ContractMetadata`] with the provided arguments. #[allow(clippy::too_many_arguments)] pub fn new( - sources: Rc, - contract: Rc>, + sources: Arc, + contract: Arc>, is_deployment: bool, normalized_code: Vec, instructions: Vec, @@ -424,21 +424,21 @@ pub struct Contract { /// Custom errors defined in the contract. pub custom_errors: Vec, /// The constructor function of the contract. - pub constructor: Option>, + pub constructor: Option>, /// The fallback function of the contract. - pub fallback: Option>, + pub fallback: Option>, /// The receive function of the contract. - pub receive: Option>, + pub receive: Option>, - local_functions: Vec>, - selector_hex_to_function: HashMap>, + local_functions: Vec>, + selector_hex_to_function: HashMap>, /// The contract's name. pub name: String, /// The contract's kind, i.e. contract or library. pub r#type: ContractKind, /// The source location of the contract. - pub location: Rc, + pub location: Arc, } impl Contract { @@ -446,7 +446,7 @@ impl Contract { pub fn new( name: String, contract_type: ContractKind, - location: Rc, + location: Arc, ) -> Contract { Contract { custom_errors: Vec::new(), @@ -464,16 +464,14 @@ impl Contract { /// Adds a local function to the contract. /// # Note /// Should only be called when resolving the source model. - pub fn add_local_function(&mut self, func: Rc) { + pub fn add_local_function(&mut self, func: Arc) { if matches!( func.visibility, Some(ContractFunctionVisibility::Public | ContractFunctionVisibility::External) ) { match func.r#type { ContractFunctionType::Function | ContractFunctionType::Getter => { - let selector = func.selector.try_borrow().expect( - "Function selector to be corrected later after creating the source model", - ); + let selector = func.selector.read(); // The original code unwrapped here let selector = selector.as_ref().unwrap(); let selector = hex::encode(selector); @@ -532,10 +530,9 @@ impl Contract { let selector = base_contract_function .selector - .try_borrow() - .expect("Function selector to be corrected later after creating the source model") + .read() .clone() - .unwrap(); + .expect("selector exists"); let selector_hex = hex::encode(&*selector); self.selector_hex_to_function @@ -545,7 +542,7 @@ impl Contract { } /// Looks up the local [`ContractFunction`] with the provided selector. - pub fn get_function_from_selector(&self, selector: &[u8]) -> Option<&Rc> { + pub fn get_function_from_selector(&self, selector: &[u8]) -> Option<&Arc> { let selector_hex = hex::encode(selector); self.selector_hex_to_function.get(&selector_hex) @@ -577,10 +574,7 @@ impl Contract { }; { - let mut selector_to_be_corrected = function_to_correct - .selector - .try_borrow_mut() - .expect("Function selector to only be corrected after creating the source model"); + let mut selector_to_be_corrected = function_to_correct.selector.write(); if let Some(selector) = &*selector_to_be_corrected { let selector_hex = hex::encode(selector); self.selector_hex_to_function.remove(&selector_hex); diff --git a/crates/edr_solidity/src/compiler.rs b/crates/edr_solidity/src/compiler.rs index 9127b7306..ca13a1a4d 100644 --- a/crates/edr_solidity/src/compiler.rs +++ b/crates/edr_solidity/src/compiler.rs @@ -1,11 +1,12 @@ //! Processes the Solidity compiler standard JSON[^1] input and output AST and //! creates the source model used to perform the stack trace decoding. -use std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr}; +use std::{collections::HashMap, str::FromStr, sync::Arc}; use anyhow::{self, Context as _}; use edr_evm::{alloy_primitives::keccak256, hex}; use indexmap::IndexMap; +use parking_lot::RwLock; use crate::{ artifacts::{CompilerInput, CompilerOutput, CompilerOutputBytecode, ContractAbiEntry}, @@ -31,7 +32,7 @@ pub fn create_models_and_decode_bytecodes( compiler_output: &CompilerOutput, ) -> anyhow::Result> { let build_model = create_sources_model_from_ast(compiler_output, compiler_input)?; - let build_model = Rc::new(build_model); + let build_model = Arc::new(build_model); let bytecodes = decode_bytecodes(solc_version, compiler_output, &build_model)?; @@ -46,7 +47,7 @@ fn create_sources_model_from_ast( ) -> anyhow::Result { // First, collect and store all the files to be able to resolve the source // locations - let sources: Rc> = Rc::new( + let sources: Arc> = Arc::new( compiler_output .sources .iter() @@ -55,7 +56,7 @@ fn create_sources_model_from_ast( source_name.clone(), compiler_input.sources[source_name].content.clone(), ); - let file = Rc::new(RefCell::new(file)); + let file = Arc::new(RwLock::new(file)); (source.id, file.clone()) }) .collect(), @@ -120,11 +121,11 @@ fn create_sources_model_from_ast( } fn apply_contracts_inheritance( - contract_id_to_contract: &IndexMap>>, + contract_id_to_contract: &IndexMap>>, contract_id_to_linearized_base_contract_ids: &HashMap>, ) { for (cid, contract) in contract_id_to_contract { - let mut contract = contract.borrow_mut(); + let mut contract = contract.write(); let inheritance_ids = &contract_id_to_linearized_base_contract_ids[cid]; @@ -138,7 +139,7 @@ fn apply_contracts_inheritance( }; if cid != base_id { - let base_contract = &base_contract.borrow(); + let base_contract = &base_contract.read(); contract.add_next_linearized_base_contract(base_contract); } } @@ -146,13 +147,13 @@ fn apply_contracts_inheritance( } fn process_contract_ast_node( - file: &RefCell, + file: &RwLock, contract_node: &serde_json::Value, contract_type: ContractKind, - sources: &Rc, + sources: &Arc, contract_id_to_linearized_base_contract_ids: &mut HashMap>, contract_abi: Option<&[ContractAbiEntry]>, -) -> anyhow::Result<(u32, Rc>)> { +) -> anyhow::Result<(u32, Arc>)> { let contract_location = ast_src_to_source_location(contract_node["src"].as_str().unwrap(), sources)? .expect("The original JS code always asserts that"); @@ -162,7 +163,7 @@ fn process_contract_ast_node( contract_type, contract_location, ); - let contract = Rc::new(RefCell::new(contract)); + let contract = Arc::new(RwLock::new(contract)); let contract_id = contract_node["id"].as_u64().unwrap() as u32; @@ -215,9 +216,9 @@ fn process_contract_ast_node( fn process_function_definition_ast_node( node: &serde_json::Value, - sources: &Rc, - contract: Option<&RefCell>, - file: &RefCell, + sources: &Arc, + contract: Option<&RwLock>, + file: &RwLock, function_abis: Option>, ) -> anyhow::Result<()> { if node.get("implemented").and_then(serde_json::Value::as_bool) == Some(false) { @@ -280,20 +281,17 @@ fn process_function_definition_ast_node( name: node["name"].as_str().unwrap().to_string(), r#type: function_type, location: function_location, - contract_name: contract - .as_ref() - .map(|c| c.borrow()) - .map(|c| c.name.clone()), + contract_name: contract.as_ref().map(|c| c.read()).map(|c| c.name.clone()), visibility: Some(visibility), is_payable: Some(node["stateMutability"].as_str().unwrap() == "payable"), - selector: RefCell::new(selector), + selector: RwLock::new(selector), param_types, }; - let contract_func = Rc::new(contract_func); + let contract_func = Arc::new(contract_func); - file.borrow_mut().add_function(contract_func.clone()); + file.write().add_function(contract_func.clone()); if let Some(contract) = contract { - contract.borrow_mut().add_local_function(contract_func); + contract.write().add_local_function(contract_func); } Ok(()) @@ -301,9 +299,9 @@ fn process_function_definition_ast_node( fn process_modifier_definition_ast_node( node: &serde_json::Value, - sources: &Rc, - contract: &RefCell, - file: &RefCell, + sources: &Arc, + contract: &RwLock, + file: &RwLock, ) -> anyhow::Result<()> { let function_location = ast_src_to_source_location(node["src"].as_str().unwrap(), sources)? .expect("The original JS code always asserts that"); @@ -312,26 +310,26 @@ fn process_modifier_definition_ast_node( name: node["name"].as_str().unwrap().to_string(), r#type: ContractFunctionType::Modifier, location: function_location, - contract_name: Some(contract.borrow().name.clone()), + contract_name: Some(contract.read().name.clone()), visibility: None, is_payable: None, - selector: RefCell::new(None), + selector: RwLock::new(None), param_types: None, }; - let contract_func = Rc::new(contract_func); + let contract_func = Arc::new(contract_func); - file.borrow_mut().add_function(contract_func.clone()); - contract.borrow_mut().add_local_function(contract_func); + file.write().add_function(contract_func.clone()); + contract.write().add_local_function(contract_func); Ok(()) } fn process_variable_declaration_ast_node( node: &serde_json::Value, - sources: &Rc, - contract: &RefCell, - file: &RefCell, + sources: &Arc, + contract: &RwLock, + file: &RwLock, getter_abi: Option<&ContractAbiEntry>, ) -> anyhow::Result<()> { let visibility = ast_visibility_to_visibility(node["visibility"].as_str().unwrap()); @@ -353,18 +351,18 @@ fn process_variable_declaration_ast_node( name: node["name"].as_str().unwrap().to_string(), r#type: ContractFunctionType::Getter, location: function_location, - contract_name: Some(contract.borrow().name.clone()), + contract_name: Some(contract.read().name.clone()), visibility: Some(visibility), is_payable: Some(false), // Getters aren't payable - selector: RefCell::new(Some( + selector: RwLock::new(Some( get_public_variable_selector_from_declaration_ast_node(node)?, )), param_types, }; - let contract_func = Rc::new(contract_func); + let contract_func = Arc::new(contract_func); - file.borrow_mut().add_function(contract_func.clone()); - contract.borrow_mut().add_local_function(contract_func); + file.write().add_function(contract_func.clone()); + contract.write().add_local_function(contract_func); Ok(()) } @@ -565,8 +563,8 @@ fn to_canonical_abi_type(type_: &str) -> String { fn ast_src_to_source_location( src: &str, - build_model_sources: &Rc, -) -> anyhow::Result>> { + build_model_sources: &Arc, +) -> anyhow::Result>> { let parts: Vec<&str> = src.split(':').collect(); if parts.len() != 3 { return Ok(None); @@ -586,8 +584,8 @@ fn ast_src_to_source_location( return Err(anyhow::anyhow!("Failed to find file by ID: {file_id}")); } - Ok(Some(Rc::new(SourceLocation::new( - Rc::clone(build_model_sources), + Ok(Some(Arc::new(SourceLocation::new( + Arc::clone(build_model_sources), file_id, offset, length, @@ -599,11 +597,11 @@ fn correct_selectors( compiler_output: &CompilerOutput, ) -> anyhow::Result<()> { for bytecode in bytecodes.iter().filter(|b| !b.is_deployment) { - let mut contract = bytecode.contract.borrow_mut(); + let mut contract = bytecode.contract.write(); // Fetch the method identifiers for the contract from the compiler output let method_identifiers = match compiler_output .contracts - .get(&contract.location.file().borrow().source_name) + .get(&contract.location.file().read().source_name) .and_then(|file| file.get(&contract.name)) .map(|contract| &contract.evm.method_identifiers) { @@ -656,11 +654,11 @@ fn abi_method_id(name: &str, param_types: Vec>) -> Vec { } fn decode_evm_bytecode( - contract: Rc>, + contract: Arc>, solc_version: String, is_deployment: bool, compiler_bytecode: &CompilerOutputBytecode, - build_model: &Rc, + build_model: &Arc, ) -> anyhow::Result { let library_address_positions = get_library_address_positions(compiler_bytecode); @@ -690,7 +688,7 @@ fn decode_evm_bytecode( ); Ok(ContractMetadata::new( - Rc::clone(&build_model.file_id_to_source_file), + Arc::clone(&build_model.file_id_to_source_file), contract, is_deployment, normalized_code, @@ -704,16 +702,16 @@ fn decode_evm_bytecode( fn decode_bytecodes( solc_version: String, compiler_output: &CompilerOutput, - build_model: &Rc, + build_model: &Arc, ) -> anyhow::Result> { let mut bytecodes = Vec::new(); for contract in build_model.contract_id_to_contract.values() { let contract_rc = contract.clone(); - let mut contract = contract.borrow_mut(); + let mut contract = contract.write(); - let contract_file = &contract.location.file().borrow().source_name.clone(); + let contract_file = &contract.location.file().read().source_name.clone(); let contract_evm_output = &compiler_output.contracts[contract_file][&contract.name].evm; let contract_abi_output = &compiler_output.contracts[contract_file][&contract.name].abi; diff --git a/crates/edr_solidity/src/error_inferrer.rs b/crates/edr_solidity/src/error_inferrer.rs index a6fb5a21c..12f287b66 100644 --- a/crates/edr_solidity/src/error_inferrer.rs +++ b/crates/edr_solidity/src/error_inferrer.rs @@ -213,7 +213,7 @@ pub(crate) fn infer_before_tracing_call_message( .contract_meta .as_ref() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); let called_function = contract.get_function_from_selector(trace.calldata.get(..4).unwrap_or(&trace.calldata[..])); @@ -290,7 +290,7 @@ pub(crate) fn instruction_to_callstack_stack_trace_entry( contract_meta: &ContractMetadata, inst: &Instruction, ) -> Result { - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); // This means that a jump is made from within an internal solc function. // These are normally made from yul code, so they don't map to any Solidity @@ -299,7 +299,7 @@ pub(crate) fn instruction_to_callstack_stack_trace_entry( None => { let location = &contract.location; let file = location.file(); - let file = file.borrow(); + let file = file.read(); return Ok(StackTraceEntry::InternalFunctionCallstackEntry { pc: inst.pc, @@ -328,7 +328,7 @@ pub(crate) fn instruction_to_callstack_stack_trace_entry( }; let file = inst_location.file(); - let file = file.borrow(); + let file = file.read(); Ok(StackTraceEntry::CallstackEntry { source_reference: SourceReference { @@ -383,7 +383,7 @@ fn check_custom_errors( let contract_meta = trace .contract_meta() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); let return_data = trace.return_data(); let return_data = ReturnData::new(return_data.clone()); @@ -544,7 +544,7 @@ fn check_last_instruction( } } - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); let selector = calldata.get(..4).unwrap_or(&calldata[..]); let calldata = &calldata.get(4..).unwrap_or(&[]); @@ -793,7 +793,7 @@ fn check_revert_or_invalid_opcode( match &trace { CreateOrCallMessageRef::Call(CallMessage { calldata, .. }) => { - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); // This is here because of the optimizations let function_from_selector = contract @@ -872,7 +872,7 @@ fn empty_calldata_and_no_receive(trace: &CallMessage) -> Result func, @@ -1166,7 +1166,7 @@ fn get_fallback_start_source_reference( let location = &func.location; let file = location.file(); - let file = file.borrow(); + let file = file.read(); Ok(SourceReference { source_name: file.source_name.clone(), @@ -1272,7 +1272,7 @@ fn has_failed_inside_the_fallback_function(trace: &CallMessage) -> Result has_failed_inside_function(trace, fallback), @@ -1286,7 +1286,7 @@ fn has_failed_inside_the_receive_function(trace: &CallMessage) -> Result has_failed_inside_function(trace, receive), @@ -1468,7 +1468,7 @@ fn is_constructor_invalid_arguments_error(trace: &CreateMessage) -> Result Result Result { .as_ref() .ok_or(InferrerError::MissingContract)? .contract; - let contract = contract.borrow(); + let contract = contract.read(); Ok(trace.depth == 0 && contract.r#type == ContractKind::Library) } @@ -1608,7 +1608,7 @@ fn is_fallback_not_payable_error( .contract_meta .as_ref() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); match &contract.fallback { Some(fallback) => Ok(fallback.is_payable != Some(true)), @@ -1633,7 +1633,7 @@ fn is_function_not_payable_error( .contract_meta .as_ref() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); // Libraries don't have a nonpayable check if contract.r#type == ContractKind::Library { @@ -1689,7 +1689,7 @@ fn is_missing_function_and_fallback_error( .contract_meta .as_ref() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); // there's a receive function and no calldata if trace.calldata.len() == 0 && contract.receive.is_some() { @@ -1736,7 +1736,7 @@ fn is_proxy_error_propagated( return Ok(false); }; - if subtrace_contract_meta.contract.borrow().r#type == ContractKind::Library { + if subtrace_contract_meta.contract.read().r#type == ContractKind::Library { return Ok(false); } @@ -1865,7 +1865,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( .contract_meta .as_ref() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); let revert_frame = solidity_0_6_3_get_frame_for_unmapped_revert_within_function( CreateOrCallMessageRef::Call(trace), @@ -1882,7 +1882,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( if let Some(fallback) = &contract.fallback { let location = &fallback.location; let file = location.file(); - let file = file.borrow(); + let file = file.read(); let source_reference = SourceReference { contract: Some(contract.name.clone()), @@ -1910,7 +1910,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_before_function( let location = &receive.location; let file = location.file(); - let file = file.borrow(); + let file = file.read(); let source_reference = SourceReference { contract: Some(contract.name.clone()), @@ -1939,7 +1939,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( let contract_meta = trace .contract_meta() .ok_or(InferrerError::MissingContract)?; - let contract = contract_meta.contract.borrow(); + let contract = contract_meta.contract.read(); let steps = trace.steps(); @@ -2013,7 +2013,7 @@ fn solidity_0_6_3_get_frame_for_unmapped_revert_within_function( // some default sourceReference to show to the user let location = &contract.location; let file = location.file(); - let file = file.borrow(); + let file = file.read(); let mut default_source_reference = SourceReference { function: Some(CONSTRUCTOR_FUNCTION_NAME.to_string()), @@ -2098,14 +2098,14 @@ fn source_location_to_source_reference( }; let func_location_file = func.location.file(); - let func_location_file = func_location_file.borrow(); + let func_location_file = func_location_file.read(); Some(SourceReference { function: Some(func_name.clone()), contract: if func.r#type == ContractFunctionType::FreeFunction { None } else { - Some(contract_meta.contract.borrow().name.clone()) + Some(contract_meta.contract.read().name.clone()) }, source_name: func_location_file.source_name.clone(), source_content: func_location_file.content.clone(), diff --git a/crates/edr_solidity/src/nested_trace_decoder.rs b/crates/edr_solidity/src/nested_trace_decoder.rs index 267eabeaf..666517deb 100644 --- a/crates/edr_solidity/src/nested_trace_decoder.rs +++ b/crates/edr_solidity/src/nested_trace_decoder.rs @@ -148,7 +148,7 @@ impl NestedTraceDecoder { .get_bytecode_for_call(code.as_ref(), is_create); let contract = bytecode.map(|bytecode| bytecode.contract.clone()); - let contract = contract.as_ref().map(|c| c.borrow()); + let contract = contract.as_ref().map(|c| c.read()); let contract_name = contract.as_ref().map_or_else( || UNRECOGNIZED_CONTRACT_NAME.to_string(), @@ -221,7 +221,7 @@ fn initialize_contracts_identifier(config: &TracingConfig) -> anyhow::Result, bytecode: &[u8 pub fn decode_instructions( bytecode: &[u8], compressed_sourcemaps: &str, - build_model: &Rc, + build_model: &Arc, is_deployment: bool, ) -> Vec { let source_maps = uncompress_sourcemaps(compressed_sourcemaps); @@ -181,7 +181,7 @@ pub fn decode_instructions( .file_id_to_source_file .get(&(source_map.location.file as u32)) .map(|_| { - Rc::new(SourceLocation::new( + Arc::new(SourceLocation::new( build_model.file_id_to_source_file.clone(), source_map.location.file as u32, source_map.location.offset as u32, From fad2851eb77a34bf11fbbcc40249c3d838e4ea40 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 19 Dec 2024 22:46:09 +0000 Subject: [PATCH 13/30] Add hardhat_addCompilationResult --- Cargo.lock | 23 +-- crates/edr_napi/src/logger.rs | 35 +++-- crates/edr_napi/src/provider.rs | 45 +++--- crates/edr_provider/Cargo.toml | 1 + crates/edr_provider/src/data.rs | 19 +++ crates/edr_provider/src/error.rs | 4 + crates/edr_provider/src/lib.rs | 19 ++- crates/edr_provider/src/logger.rs | 6 + crates/edr_provider/src/requests/hardhat.rs | 3 +- .../src/requests/hardhat/compiler.rs | 47 ++++++ .../src/requests/hardhat/rpc_types.rs | 2 - .../requests/hardhat/rpc_types/compiler.rs | 136 ------------------ crates/edr_provider/src/requests/methods.rs | 6 +- crates/edr_provider/tests/eip4844.rs | 11 +- .../tests/eth_max_priority_fee_per_gas.rs | 5 + .../tests/hardhat_request_serialization.rs | 3 +- crates/edr_provider/tests/issues/issue_324.rs | 5 +- crates/edr_provider/tests/issues/issue_325.rs | 5 + crates/edr_provider/tests/issues/issue_326.rs | 5 +- crates/edr_provider/tests/issues/issue_346.rs | 5 + crates/edr_provider/tests/issues/issue_356.rs | 5 +- crates/edr_provider/tests/issues/issue_361.rs | 5 + crates/edr_provider/tests/issues/issue_384.rs | 5 + crates/edr_provider/tests/issues/issue_407.rs | 5 + crates/edr_provider/tests/issues/issue_503.rs | 5 +- crates/edr_provider/tests/issues/issue_533.rs | 5 +- crates/edr_provider/tests/issues/issue_570.rs | 6 +- crates/edr_provider/tests/issues/issue_588.rs | 3 + crates/edr_provider/tests/rip7212.rs | 6 + crates/edr_provider/tests/timestamp.rs | 3 + crates/edr_solidity/src/artifacts.rs | 34 ++--- ...d_trace_decoder.rs => contract_decoder.rs} | 38 ++--- .../edr_solidity/src/contracts_identifier.rs | 91 ++++++------ crates/edr_solidity/src/lib.rs | 4 +- crates/edr_solidity/src/nested_trace.rs | 12 +- crates/tools/Cargo.toml | 2 + crates/tools/src/scenario.rs | 7 + 37 files changed, 334 insertions(+), 287 deletions(-) create mode 100644 crates/edr_provider/src/requests/hardhat/compiler.rs delete mode 100644 crates/edr_provider/src/requests/hardhat/rpc_types/compiler.rs rename crates/edr_solidity/src/{nested_trace_decoder.rs => contract_decoder.rs} (88%) diff --git a/Cargo.lock b/Cargo.lock index 9c0453df8..952b5b9a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -744,7 +744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c3d2abadaa28e0d277f9f6d07a2052544f045d929cd4d6f7bcfb43567c9767" dependencies = [ "hasher", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rlp", ] @@ -1139,7 +1139,7 @@ dependencies = [ "lazy_static", "log", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "paste", "revm", "rpds", @@ -1177,7 +1177,7 @@ dependencies = [ "napi-build", "napi-derive", "openssl-sys", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "semver 1.0.23", "serde", @@ -1203,6 +1203,7 @@ dependencies = [ "edr_eth", "edr_evm", "edr_rpc_eth", + "edr_solidity", "edr_test_utils", "indexmap 2.2.6", "itertools 0.12.1", @@ -1210,7 +1211,7 @@ dependencies = [ "lazy_static", "log", "lru", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "paste", "rand", "revm-precompile", @@ -1289,7 +1290,7 @@ dependencies = [ "edr_evm", "either", "indexmap 2.2.6", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "semver 1.0.23", "serde", "serde_json", @@ -2472,9 +2473,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core 0.9.9", @@ -3328,7 +3329,7 @@ dependencies = [ "futures", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "serial_test_derive", ] @@ -3692,7 +3693,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite", "socket2", "tokio-macros", @@ -3801,9 +3802,11 @@ dependencies = [ "edr_evm", "edr_provider", "edr_rpc_eth", + "edr_solidity", "flate2", "indicatif", "mimalloc", + "parking_lot 0.12.3", "reqwest", "serde", "serde_json", @@ -3873,7 +3876,7 @@ dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "regex", "sharded-slab", "smallvec", diff --git a/crates/edr_napi/src/logger.rs b/crates/edr_napi/src/logger.rs index e8b5584d2..a9b6b0a16 100644 --- a/crates/edr_napi/src/logger.rs +++ b/crates/edr_napi/src/logger.rs @@ -17,7 +17,7 @@ use edr_evm::{ ExecutionResult, SyncBlock, }; use edr_provider::{ProviderError, TransactionFailure}; -use edr_solidity::nested_trace_decoder::{NestedTraceDecoder, TracingConfig}; +use edr_solidity::contract_decoder::ContractDecoder; use itertools::izip; use napi::{ threadsafe_function::{ @@ -26,6 +26,7 @@ use napi::{ Env, JsFunction, Status, }; use napi_derive::napi; +use parking_lot::RwLock; use crate::cast::TryCast; @@ -116,10 +117,10 @@ impl Logger { pub fn new( env: &Env, config: LoggerConfig, - tracing_config: Arc, + contract_decoder: Arc>, ) -> napi::Result { Ok(Self { - collector: LogCollector::new(env, config, tracing_config)?, + collector: LogCollector::new(env, config, contract_decoder)?, }) } } @@ -234,6 +235,15 @@ impl edr_provider::Logger for Logger { Ok(()) } + + fn print_contract_decoding_error(&mut self, error: &str) -> Result<(), Self::LoggerError> { + self.collector.log( + "Contract decoder failed to be updated. Please report this to help us improve Hardhat.", + ); + self.collector.print_empty_line()?; + self.collector.log(error); + Ok(()) + } } #[derive(Clone)] @@ -244,7 +254,7 @@ pub struct CollapsedMethod { #[derive(Clone)] struct LogCollector { - tracing_config: Arc, + contract_decoder: Arc>, decode_console_log_inputs_fn: ThreadsafeFunction, ErrorStrategy::Fatal>, indentation: usize, is_enabled: bool, @@ -258,7 +268,7 @@ impl LogCollector { pub fn new( env: &Env, config: LoggerConfig, - tracing_config: Arc, + contract_decoder: Arc>, ) -> napi::Result { let mut decode_console_log_inputs_fn = config .decode_console_log_inputs_callback @@ -301,7 +311,7 @@ impl LogCollector { print_line_fn.unref(env)?; Ok(Self { - tracing_config, + contract_decoder, decode_console_log_inputs_fn, indentation: 0, is_enabled: config.enable, @@ -534,16 +544,13 @@ impl LogCollector { code: Bytes, calldata: Option, ) -> (String, Option) { - // TODO this is hyper inefficient. Doing it like this for now because Bytecode - // is not Send. Will refactor. - // TODO remove expect - let mut vm_trace_decoder = - NestedTraceDecoder::new(&self.tracing_config).expect("can initialize vm trace decoder"); - - let edr_solidity::nested_trace_decoder::ContractAndFunctionName { + let edr_solidity::contract_decoder::ContractAndFunctionName { contract_name, function_name, - } = vm_trace_decoder.get_contract_and_function_names_for_call(&code, calldata.as_ref()); + } = self + .contract_decoder + .write() + .get_contract_and_function_names_for_call(&code, calldata.as_ref()); (contract_name, function_name) } diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index b79132e45..a8aabda7b 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -4,8 +4,10 @@ use std::sync::Arc; use edr_provider::{time::CurrentTime, InvalidRequestReason}; use edr_rpc_eth::jsonrpc; +use edr_solidity::contract_decoder::ContractDecoder; use napi::{tokio::runtime, Either, Env, JsFunction, JsObject, Status}; use napi_derive::napi; +use parking_lot::RwLock; use self::config::ProviderConfig; use crate::{ @@ -21,7 +23,7 @@ use crate::{ pub struct Provider { provider: Arc>, runtime: runtime::Handle, - tracing_config: Arc, + contract_decoder: Arc>, #[cfg(feature = "scenarios")] scenario_file: Option>, } @@ -36,7 +38,6 @@ impl Provider { _context: &EdrContext, config: ProviderConfig, logger_config: LoggerConfig, - // TODO avoid opaque type tracing_config: serde_json::Value, #[napi(ts_arg_type = "(event: SubscriptionEvent) => void")] subscriber_callback: JsFunction, ) -> napi::Result { @@ -44,15 +45,20 @@ impl Provider { let config = edr_provider::ProviderConfig::try_from(config)?; - // TODO get actual type as argument - let tracing_config: edr_solidity::nested_trace_decoder::TracingConfig = + // TODO parsing the build info config and creating the contract decoder + // shouldn't happen here as it's blocking the JS event loop, + // but it's not straightforward to do it in a non-blocking way, because `Env` is + // not `Send`. + let build_info_config: edr_solidity::contract_decoder::BuildInfoConfig = serde_json::from_value(tracing_config)?; - let tracing_config = Arc::new(tracing_config); + let contract_decoder = ContractDecoder::new(&build_info_config) + .map_err(|error| napi::Error::from_reason(error.to_string()))?; + let contract_decoder = Arc::new(RwLock::new(contract_decoder)); let logger = Box::new(Logger::new( &env, logger_config, - Arc::clone(&tracing_config), + Arc::clone(&contract_decoder), )?); let subscriber_callback = SubscriberCallback::new(&env, subscriber_callback)?; let subscriber_callback = Box::new(move |event| subscriber_callback.call(event)); @@ -71,6 +77,7 @@ impl Provider { logger, subscriber_callback, config, + Arc::clone(&contract_decoder), CurrentTime, ) .map_or_else( @@ -79,7 +86,7 @@ impl Provider { Ok(Provider { provider: Arc::new(provider), runtime, - tracing_config, + contract_decoder, #[cfg(feature = "scenarios")] scenario_file, }) @@ -201,7 +208,7 @@ impl Provider { .map(|data| { let solidity_trace = solidity_trace.map(|trace| SolidityTraceData { trace, - config: Arc::clone(&self.tracing_config), + contract_decoder: Arc::clone(&self.contract_decoder), }); Response { solidity_trace, @@ -245,7 +252,7 @@ impl Provider { #[derive(Debug)] struct SolidityTraceData { trace: Arc, - config: Arc, + contract_decoder: Arc>, } #[napi] @@ -281,7 +288,11 @@ impl Response { #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback."] #[napi] pub fn stack_trace(&self) -> napi::Result>> { - let Some(SolidityTraceData { trace, config }) = &self.solidity_trace else { + let Some(SolidityTraceData { + trace, + contract_decoder, + }) = &self.solidity_trace + else { return Ok(None); }; let hierarchical_trace = @@ -290,16 +301,10 @@ impl Response { ); if let Some(vm_trace) = hierarchical_trace.result { - let mut nested_trace_decoder = - edr_solidity::nested_trace_decoder::NestedTraceDecoder::new(config).map_err( - |err| { - napi::Error::from_reason(format!( - "Error initializing trace decoder: '{err}'" - )) - }, - )?; - - let decoded_trace = nested_trace_decoder.try_to_decode_message_trace(vm_trace); + let decoded_trace = { + let mut decoder = contract_decoder.write(); + decoder.try_to_decode_message_trace(vm_trace) + }; let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace) .map_err(|err| { napi::Error::from_reason(format!( diff --git a/crates/edr_provider/Cargo.toml b/crates/edr_provider/Cargo.toml index d37c34c86..c6e7094e7 100644 --- a/crates/edr_provider/Cargo.toml +++ b/crates/edr_provider/Cargo.toml @@ -13,6 +13,7 @@ edr_defaults = { version = "0.3.5", path = "../edr_defaults" } edr_eth = { version = "0.3.5", path = "../edr_eth", features = ["rand"] } edr_evm = { version = "0.3.5", path = "../edr_evm", features = ["tracing"] } edr_rpc_eth = { version = "0.3.5", path = "../edr_rpc_eth" } +edr_solidity = { version = "0.3.5", path = "../edr_solidity" } indexmap = { version = "2.0.0", default-features = false, features = ["std"] } itertools = { version = "0.12.0", default-features = false, features = ["use_alloc"] } k256 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa", "pem", "pkcs8", "precomputed-tables", "std"] } diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 2d0031536..58ef21e81 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -53,10 +53,12 @@ use edr_rpc_eth::{ error::HttpError, RpcTransactionType, }; +use edr_solidity::contract_decoder::{ContractDecoder, ContractDecoderError}; use gas::gas_used_ratio; use indexmap::IndexMap; use itertools::izip; use lru::LruCache; +use parking_lot::RwLock; use revm_precompile::secp256r1; use rpds::HashTrieMapSync; use tokio::runtime; @@ -148,6 +150,9 @@ pub enum CreationError { /// A blockchain error #[error(transparent)] Blockchain(BlockchainError), + /// A contract decoder error + #[error(transparent)] + ContractDecoder(#[from] ContractDecoderError), /// An error that occurred while constructing a forked blockchain. #[error(transparent)] ForkedBlockchainCreation(#[from] ForkedCreationError), @@ -208,6 +213,9 @@ pub struct ProviderData>>>, current_state_id: StateId, block_number_to_state_id: HashTrieMapSync, + // DEAD LOCK WARNING: This should only be written to from a `hardhat_addCompilationResult` or + // `hardhat_reset` handler. Otherwise there is a risk of deadlocks. + contract_decoder: Arc>, } impl ProviderData { @@ -217,6 +225,7 @@ impl ProviderData, call_override: Option>, config: ProviderConfig, + contract_metadata: Arc>, timer: TimerT, ) -> Result { let InitialAccounts { @@ -313,6 +322,7 @@ impl ProviderData ProviderData ProviderData &RwLock { + &self.contract_decoder + } + /// Returns the default caller. pub fn default_caller(&self) -> Address { self.local_accounts @@ -2881,6 +2899,7 @@ pub(crate) mod test_utils { subscription_callback_noop, None, config.clone(), + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/src/error.rs b/crates/edr_provider/src/error.rs index 1b545e87f..405d5d440 100644 --- a/crates/edr_provider/src/error.rs +++ b/crates/edr_provider/src/error.rs @@ -159,6 +159,9 @@ pub enum ProviderError { /// An error occurred while recovering a signature. #[error(transparent)] Signature(#[from] edr_eth::signature::SignatureError), + /// An error occurred while decoding the contract metadata. + #[error("Error decoding contract metadata: {0}")] + SolcDecoding(String), /// State error #[error(transparent)] State(#[from] StateError), @@ -270,6 +273,7 @@ impl From> for jsonrpc::Error { ProviderError::SetNextBlockBaseFeePerGasUnsupported { .. } => INVALID_INPUT, ProviderError::SetNextPrevRandaoUnsupported { .. } => INVALID_INPUT, ProviderError::Signature(_) => INVALID_PARAMS, + ProviderError::SolcDecoding(_) => INVALID_INPUT, ProviderError::State(_) => INVALID_INPUT, ProviderError::TimestampLowerThanPrevious { .. } => INVALID_INPUT, ProviderError::TimestampEqualsPrevious { .. } => INVALID_INPUT, diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index ca6e8b14c..f2fa61e2e 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -22,10 +22,11 @@ use core::fmt::Debug; use std::sync::Arc; use edr_evm::{blockchain::BlockchainError, trace::Trace, HashSet}; +use edr_solidity::contract_decoder::ContractDecoder; use lazy_static::lazy_static; use logger::SyncLogger; use mock::SyncCallOverride; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use requests::{eth::handle_set_interval_mining, hardhat::rpc_types::ResetProviderConfig}; use time::{CurrentTime, TimeSinceEpoch}; use tokio::{runtime, sync::Mutex as AsyncMutex, task}; @@ -114,6 +115,7 @@ impl>, subscriber_callback: Box, config: ProviderConfig, + contract_decoder: Arc>, timer: TimerT, ) -> Result { let data = ProviderData::new( @@ -122,6 +124,7 @@ impl Err(ProviderError::Unimplemented( - "AddCompilationResult".to_string(), - )), + MethodInvocation::AddCompilationResult( + solc_version, + compiler_input, + compiler_output, + ) => hardhat::handle_add_compilation_result( + data, + solc_version, + compiler_input, + compiler_output, + ) + .and_then(to_json), MethodInvocation::DropTransaction(transaction_hash) => { hardhat::handle_drop_transaction(data, transaction_hash).and_then(to_json) } diff --git a/crates/edr_provider/src/logger.rs b/crates/edr_provider/src/logger.rs index 296550b0b..7af0d9932 100644 --- a/crates/edr_provider/src/logger.rs +++ b/crates/edr_provider/src/logger.rs @@ -90,6 +90,8 @@ pub trait Logger { method: &str, error: Option<&ProviderError>, ) -> Result<(), Self::LoggerError>; + + fn print_contract_decoding_error(&mut self, error: &str) -> Result<(), Self::LoggerError>; } pub trait SyncLogger: Logger + DynClone + Send + Sync {} @@ -126,4 +128,8 @@ impl Logger for NoopLogger { ) -> Result<(), Infallible> { Ok(()) } + + fn print_contract_decoding_error(&mut self, _error: &str) -> Result<(), Self::LoggerError> { + Ok(()) + } } diff --git a/crates/edr_provider/src/requests/hardhat.rs b/crates/edr_provider/src/requests/hardhat.rs index 5aa0314aa..8990a67ab 100644 --- a/crates/edr_provider/src/requests/hardhat.rs +++ b/crates/edr_provider/src/requests/hardhat.rs @@ -1,4 +1,5 @@ mod accounts; +mod compiler; mod config; mod log; mod miner; @@ -6,4 +7,4 @@ pub mod rpc_types; mod state; mod transactions; -pub use self::{accounts::*, config::*, log::*, miner::*, state::*, transactions::*}; +pub use self::{accounts::*, compiler::*, config::*, log::*, miner::*, state::*, transactions::*}; diff --git a/crates/edr_provider/src/requests/hardhat/compiler.rs b/crates/edr_provider/src/requests/hardhat/compiler.rs new file mode 100644 index 000000000..8ba854399 --- /dev/null +++ b/crates/edr_provider/src/requests/hardhat/compiler.rs @@ -0,0 +1,47 @@ +use std::fmt::Debug; + +use edr_solidity::{ + artifacts::{CompilerInput, CompilerOutput}, + compiler::create_models_and_decode_bytecodes, +}; + +use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; + +pub fn handle_add_compilation_result( + data: &mut ProviderData, + solc_version: String, + compiler_input: CompilerInput, + compiler_output: CompilerOutput, +) -> Result> { + if let Err(error) = add_compilation_result_inner::( + data, + solc_version, + compiler_input, + compiler_output, + ) { + data.logger_mut() + .print_contract_decoding_error(&error.to_string()) + .map_err(ProviderError::Logger)?; + Ok(false) + } else { + Ok(true) + } +} + +fn add_compilation_result_inner( + data: &mut ProviderData, + solc_version: String, + compiler_input: CompilerInput, + compiler_output: CompilerOutput, +) -> Result<(), ProviderError> { + let contracts = + create_models_and_decode_bytecodes(solc_version, &compiler_input, &compiler_output) + .map_err(|err| ProviderError::SolcDecoding(err.to_string()))?; + + let mut contract_decoder = data.contract_decoder().write(); + for contract in contracts { + contract_decoder.add_contract_metadata(contract); + } + + Ok(()) +} diff --git a/crates/edr_provider/src/requests/hardhat/rpc_types.rs b/crates/edr_provider/src/requests/hardhat/rpc_types.rs index 84cbc30fa..6e69e3bf0 100644 --- a/crates/edr_provider/src/requests/hardhat/rpc_types.rs +++ b/crates/edr_provider/src/requests/hardhat/rpc_types.rs @@ -1,7 +1,5 @@ -mod compiler; mod config; mod metadata; -pub use compiler::{CompilerInput, CompilerInputSource, CompilerOutput, CompilerOutputContract}; pub use config::{ForkConfig, ResetProviderConfig}; pub use metadata::{ForkMetadata, Metadata}; diff --git a/crates/edr_provider/src/requests/hardhat/rpc_types/compiler.rs b/crates/edr_provider/src/requests/hardhat/rpc_types/compiler.rs deleted file mode 100644 index 843f8c831..000000000 --- a/crates/edr_provider/src/requests/hardhat/rpc_types/compiler.rs +++ /dev/null @@ -1,136 +0,0 @@ -use edr_eth::HashMap; - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerInput { - language: String, - /// maps sourceName to content: - sources: HashMap, - settings: CompilerSettings, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerInputSource { - content: String, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct CompilerSettings { - #[serde(rename = "viaIR")] - via_ir: Option, - optimizer: OptimizerSettings, - metadata: Option, - /// mapping: source name -> (mapping: contract name -> compiler output - /// selections) - output_selection: HashMap>>, - evm_version: Option, - /// mapping: library file name -> (mapping: library name -> library content) - libraries: Option>>, - remappings: Option>, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct OptimizerSettings { - runs: Option, - enabled: Option, - details: Option, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct OptimizerDetails { - yul_details: YulDetails, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct YulDetails { - optimizer_steps: String, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -struct MetadataSettings { - use_literal_content: bool, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerOutput { - /// mapping: source name -> `CompilerOutputSource` - sources: HashMap, - /// mapping: source name -> (mapping: contract name -> - /// `CompilerOutputContract`) - contracts: HashMap>, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerOutputSource { - id: usize, - ast: serde_json::Value, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerOutputContract { - abi: serde_json::Value, - evm: CompilerOutputContractEvm, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerOutputContractEvm { - bytecode: CompilerOutputBytecode, - deployed_bytecode: CompilerOutputBytecode, - /// mapping: method signature -> method identifier - method_identifiers: HashMap, -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CompilerOutputBytecode { - object: String, - opcodes: String, - source_map: String, - /// mapping: source name -> (mapping: library name -> `LinkReferences`) - link_references: HashMap>>, -} - -pub mod u64_that_must_be_20 { - pub fn serialize(val: &u64, s: S) -> Result - where - S: serde::Serializer, - { - if *val == 20 { - s.serialize_u64(*val) - } else { - use serde::ser::Error; - Err(S::Error::custom("value must be 20")) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: u64 = serde::de::Deserialize::deserialize(deserializer)?; - if s == 20 { - Ok(s) - } else { - use serde::de::Error; - Err(D::Error::custom("value must be 20")) - } - } -} - -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LinkReference { - start: usize, - #[serde(with = "u64_that_must_be_20")] - length: u64, -} diff --git a/crates/edr_provider/src/requests/methods.rs b/crates/edr_provider/src/requests/methods.rs index bf60eafc0..7df97c7dd 100644 --- a/crates/edr_provider/src/requests/methods.rs +++ b/crates/edr_provider/src/requests/methods.rs @@ -6,12 +6,10 @@ use edr_eth::{ Address, BlockSpec, Bytes, PreEip1898BlockSpec, B256, U256, U64, }; use edr_rpc_eth::{CallRequest, StateOverrideOptions}; +use edr_solidity::artifacts::{CompilerInput, CompilerOutput}; use super::serde::{RpcAddress, Timestamp}; -use crate::requests::{ - debug::DebugTraceConfig, - hardhat::rpc_types::{CompilerInput, CompilerOutput, ResetProviderConfig}, -}; +use crate::requests::{debug::DebugTraceConfig, hardhat::rpc_types::ResetProviderConfig}; mod optional_block_spec { use super::BlockSpec; diff --git a/crates/edr_provider/tests/eip4844.rs b/crates/edr_provider/tests/eip4844.rs index e68cadad1..318f0c0e3 100644 --- a/crates/edr_provider/tests/eip4844.rs +++ b/crates/edr_provider/tests/eip4844.rs @@ -1,6 +1,6 @@ #![cfg(feature = "test-utils")] -use std::{convert::Infallible, str::FromStr}; +use std::{convert::Infallible, str::FromStr, sync::Arc}; use edr_defaults::SECRET_KEYS; use edr_eth::{ @@ -20,6 +20,8 @@ use edr_provider::{ MethodInvocation, NoopLogger, Provider, ProviderError, ProviderRequest, }; use edr_rpc_eth::CallRequest; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use tokio::runtime; /// Helper struct to modify the pooled transaction from the value in @@ -221,6 +223,7 @@ async fn call_unsupported() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -252,6 +255,7 @@ async fn estimate_gas_unsupported() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -283,6 +287,7 @@ async fn send_transaction_unsupported() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -326,6 +331,7 @@ async fn send_raw_transaction() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -365,6 +371,7 @@ async fn get_transaction() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -412,6 +419,7 @@ async fn block_header() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -598,6 +606,7 @@ async fn blob_hash_opcode() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs b/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs index 258ecdf60..4b5069f6d 100644 --- a/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs +++ b/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs @@ -1,9 +1,13 @@ #![cfg(feature = "test-utils")] +use std::sync::Arc; + use edr_provider::{ test_utils::create_test_config, time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -16,6 +20,7 @@ async fn eth_max_priority_fee_per_gas() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/hardhat_request_serialization.rs b/crates/edr_provider/tests/hardhat_request_serialization.rs index a4527f363..33a9b493b 100644 --- a/crates/edr_provider/tests/hardhat_request_serialization.rs +++ b/crates/edr_provider/tests/hardhat_request_serialization.rs @@ -3,9 +3,10 @@ mod common; use edr_eth::{Address, Bytes, B256, U256}; use edr_evm::alloy_primitives::U160; use edr_provider::{ - hardhat_rpc_types::{CompilerInput, CompilerOutput, ForkConfig, ResetProviderConfig}, + hardhat_rpc_types::{ForkConfig, ResetProviderConfig}, MethodInvocation, }; +use edr_solidity::artifacts::{CompilerInput, CompilerOutput}; use crate::common::help_test_method_invocation_serde; diff --git a/crates/edr_provider/tests/issues/issue_324.rs b/crates/edr_provider/tests/issues/issue_324.rs index bf1ad7780..569d88864 100644 --- a/crates/edr_provider/tests/issues/issue_324.rs +++ b/crates/edr_provider/tests/issues/issue_324.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{str::FromStr, sync::Arc}; use edr_eth::{Address, Bytes, SpecId, U256}; use edr_provider::{ @@ -6,7 +6,9 @@ use edr_provider::{ MethodInvocation, NoopLogger, Provider, ProviderRequest, }; use edr_rpc_eth::CallRequest; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -35,6 +37,7 @@ async fn issue_324() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_325.rs b/crates/edr_provider/tests/issues/issue_325.rs index 8557e3467..12262e2d4 100644 --- a/crates/edr_provider/tests/issues/issue_325.rs +++ b/crates/edr_provider/tests/issues/issue_325.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use edr_eth::{ transaction::EthTransactionRequest, AccountInfo, Address, PreEip1898BlockSpec, SpecId, B256, }; @@ -7,6 +9,8 @@ use edr_provider::{ time::CurrentTime, MethodInvocation, MiningConfig, NoopLogger, Provider, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -37,6 +41,7 @@ async fn issue_325() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_326.rs b/crates/edr_provider/tests/issues/issue_326.rs index b93503f73..a8507fbdb 100644 --- a/crates/edr_provider/tests/issues/issue_326.rs +++ b/crates/edr_provider/tests/issues/issue_326.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{str::FromStr, sync::Arc}; use edr_eth::{transaction::EthTransactionRequest, AccountInfo, Address, SpecId, U256}; use edr_evm::KECCAK_EMPTY; @@ -8,6 +8,8 @@ use edr_provider::{ MethodInvocation, MiningConfig, NoopLogger, Provider, ProviderRequest, }; use edr_rpc_eth::CallRequest; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -39,6 +41,7 @@ async fn issue_326() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_346.rs b/crates/edr_provider/tests/issues/issue_346.rs index 1ec258401..6530e6cd1 100644 --- a/crates/edr_provider/tests/issues/issue_346.rs +++ b/crates/edr_provider/tests/issues/issue_346.rs @@ -1,4 +1,8 @@ +use std::sync::Arc; + use edr_provider::{test_utils::create_test_config, time::CurrentTime, NoopLogger, Provider}; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use serde_json::json; use tokio::runtime; @@ -13,6 +17,7 @@ async fn issue_346() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_356.rs b/crates/edr_provider/tests/issues/issue_356.rs index 2bd407d9a..9f4650785 100644 --- a/crates/edr_provider/tests/issues/issue_356.rs +++ b/crates/edr_provider/tests/issues/issue_356.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{str::FromStr, sync::Arc}; use anyhow::Context; use edr_eth::{Address, Bytes, SpecId}; @@ -7,7 +7,9 @@ use edr_provider::{ MethodInvocation, NoopLogger, Provider, ProviderRequest, }; use edr_rpc_eth::CallRequest; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; +use parking_lot::RwLock; use sha3::{Digest, Keccak256}; use tokio::runtime; @@ -37,6 +39,7 @@ async fn issue_356() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_361.rs b/crates/edr_provider/tests/issues/issue_361.rs index 08319d536..e27503265 100644 --- a/crates/edr_provider/tests/issues/issue_361.rs +++ b/crates/edr_provider/tests/issues/issue_361.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use edr_eth::{ filter::LogFilterOptions, transaction::EthTransactionRequest, AccountInfo, Address, BlockSpec, SpecId, @@ -8,6 +10,8 @@ use edr_provider::{ time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -34,6 +38,7 @@ async fn issue_361() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_384.rs b/crates/edr_provider/tests/issues/issue_384.rs index 8b913a33e..c7eb4e578 100644 --- a/crates/edr_provider/tests/issues/issue_384.rs +++ b/crates/edr_provider/tests/issues/issue_384.rs @@ -1,8 +1,12 @@ +use std::sync::Arc; + use edr_provider::{ hardhat_rpc_types::ForkConfig, test_utils::create_test_config_with_fork, time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_infura_url; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -23,6 +27,7 @@ async fn avalanche_chain_mine_local_block() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_407.rs b/crates/edr_provider/tests/issues/issue_407.rs index 1975caac6..b35864b0e 100644 --- a/crates/edr_provider/tests/issues/issue_407.rs +++ b/crates/edr_provider/tests/issues/issue_407.rs @@ -1,4 +1,8 @@ +use std::sync::Arc; + use edr_provider::{test_utils::create_test_config, time::CurrentTime, NoopLogger, Provider}; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use serde_json::json; use tokio::runtime; @@ -14,6 +18,7 @@ async fn issue_407_uint() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_503.rs b/crates/edr_provider/tests/issues/issue_503.rs index 4902c4927..80805b429 100644 --- a/crates/edr_provider/tests/issues/issue_503.rs +++ b/crates/edr_provider/tests/issues/issue_503.rs @@ -1,11 +1,13 @@ -use std::str::FromStr as _; +use std::{str::FromStr as _, sync::Arc}; use edr_eth::{Address, SpecId, U256}; use edr_provider::{ hardhat_rpc_types::ForkConfig, test_utils::create_test_config_with_fork, time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; +use parking_lot::RwLock; use tokio::runtime; // https://github.com/NomicFoundation/edr/issues/503 @@ -26,6 +28,7 @@ async fn issue_503() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_533.rs b/crates/edr_provider/tests/issues/issue_533.rs index 09bc3061d..0feb8b858 100644 --- a/crates/edr_provider/tests/issues/issue_533.rs +++ b/crates/edr_provider/tests/issues/issue_533.rs @@ -1,11 +1,13 @@ -use std::str::FromStr as _; +use std::{str::FromStr as _, sync::Arc}; use edr_eth::B256; use edr_provider::{ hardhat_rpc_types::ForkConfig, test_utils::create_test_config_with_fork, time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; +use parking_lot::RwLock; use tokio::runtime; // https://github.com/NomicFoundation/edr/issues/533 @@ -28,6 +30,7 @@ async fn issue_533() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_570.rs b/crates/edr_provider/tests/issues/issue_570.rs index ab9ee23a0..28abd623a 100644 --- a/crates/edr_provider/tests/issues/issue_570.rs +++ b/crates/edr_provider/tests/issues/issue_570.rs @@ -1,14 +1,15 @@ -use std::{convert::Infallible, str::FromStr as _}; +use std::{convert::Infallible, str::FromStr as _, sync::Arc}; use edr_eth::{spec::HardforkActivations, SpecId, B256}; use edr_provider::{ hardhat_rpc_types::ForkConfig, test_utils::create_test_config_with_fork, time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderError, ProviderRequest, }; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; +use parking_lot::RwLock; use serial_test::serial; use tokio::runtime; - // SAFETY: tests that modify the environment should be run serially. fn get_provider() -> anyhow::Result> { @@ -36,6 +37,7 @@ fn get_provider() -> anyhow::Result> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?) } diff --git a/crates/edr_provider/tests/issues/issue_588.rs b/crates/edr_provider/tests/issues/issue_588.rs index 6c09bd087..914cd82b3 100644 --- a/crates/edr_provider/tests/issues/issue_588.rs +++ b/crates/edr_provider/tests/issues/issue_588.rs @@ -8,7 +8,9 @@ use edr_provider::{ hardhat_rpc_types::ForkConfig, test_utils::create_test_config_with_fork, time::MockTime, NoopLogger, Provider, }; +use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; +use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -29,6 +31,7 @@ async fn issue_588() -> anyhow::Result<()> { logger, subscriber, early_mainnet_fork, + Arc::>::default(), current_time_is_1970, )?; diff --git a/crates/edr_provider/tests/rip7212.rs b/crates/edr_provider/tests/rip7212.rs index a2d7ab1d9..d20c86061 100644 --- a/crates/edr_provider/tests/rip7212.rs +++ b/crates/edr_provider/tests/rip7212.rs @@ -1,5 +1,7 @@ #![cfg(feature = "test-utils")] +use std::sync::Arc; + use edr_eth::Bytes; use edr_evm::bytes; use edr_provider::{ @@ -7,6 +9,8 @@ use edr_provider::{ ProviderRequest, }; use edr_rpc_eth::CallRequest; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use revm_precompile::secp256r1; use tokio::runtime; @@ -26,6 +30,7 @@ async fn rip7212_disabled() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; @@ -56,6 +61,7 @@ async fn rip7212_enabled() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/timestamp.rs b/crates/edr_provider/tests/timestamp.rs index 8bafb7fb3..00ab36899 100644 --- a/crates/edr_provider/tests/timestamp.rs +++ b/crates/edr_provider/tests/timestamp.rs @@ -8,6 +8,8 @@ use edr_provider::{ time::{MockTime, TimeSinceEpoch}, MethodInvocation, NoopLogger, Provider, ProviderRequest, Timestamp, }; +use edr_solidity::contract_decoder::ContractDecoder; +use parking_lot::RwLock; use tokio::runtime; struct TimestampFixture { @@ -30,6 +32,7 @@ impl TimestampFixture { logger, subscription_callback_noop, config.clone(), + Arc::>::default(), mock_timer.clone(), )?; diff --git a/crates/edr_solidity/src/artifacts.rs b/crates/edr_solidity/src/artifacts.rs index d3370e1f7..f2e5c8284 100644 --- a/crates/edr_solidity/src/artifacts.rs +++ b/crates/edr_solidity/src/artifacts.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; /// A `BuildInfo` is a file that contains all the information of a solc run. It /// includes all the necessary information to recreate that exact same run, and /// all of its output. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BuildInfo { #[serde(rename = "_format")] @@ -25,17 +25,17 @@ pub struct BuildInfo { } /// References: of source name -> library name -> link references. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct LinkReferences(HashMap>>); /// The source code of a contract. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct Source { pub content: String, } /// The main input to the Solidity compiler. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CompilerInput { pub language: String, pub sources: HashMap, @@ -43,7 +43,7 @@ pub struct CompilerInput { } /// Additional settings like the optimizer, metadata, etc. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CompilerSettings { #[serde(rename = "viaIR")] @@ -57,7 +57,7 @@ pub struct CompilerSettings { } /// Specifies the optimizer settings. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct OptimizerSettings { runs: Option, enabled: Option, @@ -65,28 +65,28 @@ pub struct OptimizerSettings { } /// Specifies the optimizer details. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct OptimizerDetails { yul_details: Option, } /// Yul-specific optimizer details. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct YulDetails { optimizer_steps: Option, } /// Specifies the metadata settings. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct MetadataSettings { use_literal_content: Option, } /// The main output of the Solidity compiler. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CompilerOutput { // Retain the order of the sources as emitted by the compiler. // Our post processing relies on this order to build the codebase model. @@ -95,13 +95,13 @@ pub struct CompilerOutput { } /// The output of a contract compilation. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CompilerOutputContract { pub abi: Vec, pub evm: CompilerOutputEvm, } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct ContractAbiEntry { pub name: Option, pub r#type: Option, @@ -109,7 +109,7 @@ pub struct ContractAbiEntry { } /// The EVM-specific output of a contract compilation. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CompilerOutputEvm { pub bytecode: CompilerOutputBytecode, @@ -118,14 +118,14 @@ pub struct CompilerOutputEvm { } /// The ID and the AST of the compiled sources. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CompilerOutputSource { pub id: u32, pub ast: serde_json::Value, } /// The bytecode output for a given compiled contract. -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CompilerOutputBytecode { pub object: String, @@ -136,14 +136,14 @@ pub struct CompilerOutputBytecode { } /// A reference to a library. -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct LinkReference { pub start: u32, pub length: u32, } /// A reference to an immutable value. -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +#[derive(Clone, Debug, Copy, PartialEq, Eq, Deserialize, Serialize)] pub struct ImmutableReference { pub start: u32, pub length: u32, diff --git a/crates/edr_solidity/src/nested_trace_decoder.rs b/crates/edr_solidity/src/contract_decoder.rs similarity index 88% rename from crates/edr_solidity/src/nested_trace_decoder.rs rename to crates/edr_solidity/src/contract_decoder.rs index 666517deb..06d354604 100644 --- a/crates/edr_solidity/src/nested_trace_decoder.rs +++ b/crates/edr_solidity/src/contract_decoder.rs @@ -1,5 +1,5 @@ //! Enriches the [`NestedTrace`] with the resolved [`ContractMetadata`]. -use std::rc::Rc; +use std::sync::Arc; use edr_eth::Bytes; use serde::{Deserialize, Serialize}; @@ -19,10 +19,10 @@ use crate::{ nested_trace::{NestedTrace, NestedTraceStep}, }; -/// Configuration for the [`NestedTraceDecoder`]. -#[derive(Debug, Deserialize, Serialize)] +/// Configuration for the [`ContractDecoder`]. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub struct TracingConfig { +pub struct BuildInfoConfig { /// Build information to use for decoding contracts. pub build_infos: Option>, /// Whether to ignore contracts. @@ -31,34 +31,34 @@ pub struct TracingConfig { /// Errors that can occur during the decoding of the nested trace. #[derive(Debug, thiserror::Error)] -pub enum NestedTraceDecoderError { +pub enum ContractDecoderError { /// Errors that can occur when initializing the decoder. #[error("{0}")] Initialization(String), } -/// Enriches the [`NestedTrace`] with the resolved [`ContractMetadata`]. -#[derive(Default)] -pub struct NestedTraceDecoder { +/// Get contract metadata from calldata and traces. +#[derive(Debug, Default)] +pub struct ContractDecoder { contracts_identifier: ContractsIdentifier, } -impl NestedTraceDecoder { - /// Creates a new [`NestedTraceDecoder`]. - pub fn new(config: &TracingConfig) -> Result { +impl ContractDecoder { + /// Creates a new [`ContractDecoder`]. + pub fn new(config: &BuildInfoConfig) -> Result { let contracts_identifier = initialize_contracts_identifier(config) - .map_err(|err| NestedTraceDecoderError::Initialization(err.to_string()))?; + .map_err(|err| ContractDecoderError::Initialization(err.to_string()))?; Ok(Self { contracts_identifier, }) } - /// Adds a bytecode to the decoder. - pub fn add_bytecode(&mut self, bytecode: ContractMetadata) { - self.contracts_identifier.add_bytecode(Rc::new(bytecode)); + /// Adds contract metadata to the decoder. + pub fn add_contract_metadata(&mut self, bytecode: ContractMetadata) { + self.contracts_identifier.add_bytecode(Arc::new(bytecode)); } - /// Decodes the nested trace. + /// Enriches the [`NestedTrace`] with the resolved [`ContractMetadata`]. pub fn try_to_decode_message_trace(&mut self, message_trace: NestedTrace) -> NestedTrace { match message_trace { precompile @ NestedTrace::Precompile(..) => precompile, @@ -205,7 +205,9 @@ pub struct ContractAndFunctionName { pub function_name: Option, } -fn initialize_contracts_identifier(config: &TracingConfig) -> anyhow::Result { +fn initialize_contracts_identifier( + config: &BuildInfoConfig, +) -> anyhow::Result { let mut contracts_identifier = ContractsIdentifier::default(); let Some(build_infos) = &config.build_infos else { @@ -226,7 +228,7 @@ fn initialize_contracts_identifier(config: &TracingConfig) -> anyhow::Result { /// An exact match was found. - ExactHit(Rc), + ExactHit(Arc), /// No exact match found; a node with the longest prefix is returned. LongestPrefixNode(&'a BytecodeTrie), } @@ -24,11 +24,11 @@ enum TrieSearch<'a> { /// /// What makes it special is that every node has a set of all of its descendants /// and its depth. -#[derive(Clone)] +#[derive(Debug, Clone)] struct BytecodeTrie { child_nodes: HashMap>, - descendants: Vec>, - match_: Option>, + descendants: Vec>, + match_: Option>, depth: Option, } @@ -42,7 +42,7 @@ impl BytecodeTrie { } } - fn add(&mut self, bytecode: Rc) { + fn add(&mut self, bytecode: Arc) { let mut cursor = self; let bytecode_normalized_code = &bytecode.normalized_code; @@ -116,9 +116,10 @@ fn is_matching_metadata(code: &[u8], last_byte: u32) -> bool { } /// A data structure that allows searching for well-known bytecodes. +#[derive(Debug)] pub struct ContractsIdentifier { trie: BytecodeTrie, - cache: HashMap, Rc>, + cache: HashMap, Arc>, enable_cache: bool, } @@ -141,7 +142,7 @@ impl ContractsIdentifier { } /// Adds a bytecode to the tree. - pub fn add_bytecode(&mut self, bytecode: Rc) { + pub fn add_bytecode(&mut self, bytecode: Arc) { self.trie.add(bytecode); self.cache.clear(); } @@ -150,7 +151,7 @@ impl ContractsIdentifier { &mut self, is_create: bool, code: &[u8], - ) -> Option> { + ) -> Option> { let normalize_libraries = true; let first_byte_to_search = 0; @@ -169,7 +170,7 @@ impl ContractsIdentifier { normalize_libraries: bool, trie: &BytecodeTrie, first_byte_to_search: u32, - ) -> Option> { + ) -> Option> { let search_result = match trie.search(code, first_byte_to_search) { None => return None, Some(TrieSearch::ExactHit(bytecode)) => return Some(bytecode.clone()), @@ -265,7 +266,7 @@ impl ContractsIdentifier { &mut self, code: &[u8], is_create: bool, - ) -> Option> { + ) -> Option> { let normalized_code = normalize_library_runtime_bytecode_if_necessary(code); if self.enable_cache { @@ -308,7 +309,9 @@ fn normalize_library_runtime_bytecode_if_necessary(bytecode: &[u8]) -> Cow<'_, [ #[cfg(test)] mod tests { - use std::{cell::RefCell, vec}; + use std::vec; + + use parking_lot::RwLock; use super::*; use crate::{ @@ -316,31 +319,31 @@ mod tests { build_model::{Contract, ContractKind, SourceFile, SourceLocation}, }; - fn create_sources() -> Rc>>> { + fn create_sources() -> Arc>>> { let mut sources = HashMap::new(); - let file = Rc::new(RefCell::new(SourceFile::new( + let file = Arc::new(RwLock::new(SourceFile::new( "test.sol".to_string(), "".to_string(), ))); sources.insert(0, file.clone()); - Rc::new(sources) + Arc::new(sources) } - fn create_test_contract() -> Rc> { + fn create_test_contract() -> Arc> { let sources = create_sources(); - let location = Rc::new(SourceLocation::new(sources.clone(), 0, 0, 0)); + let location = Arc::new(SourceLocation::new(sources.clone(), 0, 0, 0)); - Rc::new(RefCell::new(Contract::new( + Arc::new(RwLock::new(Contract::new( "TestContract".to_string(), ContractKind::Contract, location, ))) } - fn create_test_bytecode(normalized_code: Vec) -> Rc { + fn create_test_bytecode(normalized_code: Vec) -> Arc { let sources = create_sources(); let contract = create_test_contract(); let is_deployment = false; @@ -349,7 +352,7 @@ mod tests { let library_offsets = vec![]; let immutable_references = vec![]; - Rc::new(ContractMetadata::new( + Arc::new(ContractMetadata::new( sources, contract, is_deployment, @@ -361,7 +364,7 @@ mod tests { )) } - fn create_test_deployment_bytecode(normalized_code: Vec) -> Rc { + fn create_test_deployment_bytecode(normalized_code: Vec) -> Arc { let sources = create_sources(); let contract = create_test_contract(); let is_deployment = true; @@ -370,7 +373,7 @@ mod tests { let library_offsets = vec![]; let immutable_references = vec![]; - Rc::new(ContractMetadata::new( + Arc::new(ContractMetadata::new( sources, contract, is_deployment, @@ -386,14 +389,14 @@ mod tests { normalized_code: Vec, library_offsets: Vec, immutable_references: Vec, - ) -> Rc { + ) -> Arc { let sources = create_sources(); let contract = create_test_contract(); let is_deployment = false; let instructions = vec![]; - Rc::new(ContractMetadata::new( + Arc::new(ContractMetadata::new( sources, contract, is_deployment, @@ -431,8 +434,8 @@ mod tests { let is_create = false; let contract = contracts_identifier.search_bytecode_from_root(is_create, &[1, 2, 3, 4, 5]); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); // should not find a bytecode that doesn't match @@ -453,16 +456,16 @@ mod tests { // should find the exact match let contract = contracts_identifier.search_bytecode_from_root(false, &[1, 2, 3, 4, 5]); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode1)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode1)) ); // should find the exact match let contract = contracts_identifier.search_bytecode_from_root(false, &[1, 2, 3, 4, 5, 6, 7, 8]); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode2)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode2)) ); // should not find a bytecode that doesn't match @@ -499,8 +502,8 @@ mod tests { let contract = contracts_identifier.search_bytecode_from_root(is_create, &[1, 2, 3, 4, 5, 10, 11]); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); // the same bytecode, but for a call trace, should not match @@ -557,8 +560,8 @@ mod tests { ); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); } @@ -599,8 +602,8 @@ mod tests { ); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); } @@ -652,8 +655,8 @@ mod tests { ); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); } @@ -734,8 +737,8 @@ mod tests { ); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); } @@ -759,8 +762,8 @@ mod tests { ], ); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); } @@ -795,8 +798,8 @@ mod tests { ); assert_eq!( - contract.as_ref().map(Rc::as_ptr), - Some(Rc::as_ptr(&bytecode)) + contract.as_ref().map(Arc::as_ptr), + Some(Arc::as_ptr(&bytecode)) ); } } diff --git a/crates/edr_solidity/src/lib.rs b/crates/edr_solidity/src/lib.rs index 292399359..769f84c38 100644 --- a/crates/edr_solidity/src/lib.rs +++ b/crates/edr_solidity/src/lib.rs @@ -9,15 +9,15 @@ pub mod contracts_identifier; pub mod utils; pub mod artifacts; +pub mod compiler; +pub mod contract_decoder; pub mod exit_code; pub mod library_utils; pub mod nested_trace; -pub mod nested_trace_decoder; pub mod nested_tracer; pub mod solidity_stack_trace; pub mod solidity_tracer; -mod compiler; mod error_inferrer; mod mapped_inline_internal_functions_heuristics; mod return_data; diff --git a/crates/edr_solidity/src/nested_trace.rs b/crates/edr_solidity/src/nested_trace.rs index c1b8209e1..15c838eb8 100644 --- a/crates/edr_solidity/src/nested_trace.rs +++ b/crates/edr_solidity/src/nested_trace.rs @@ -1,6 +1,6 @@ //! Naive Rust port of the `MessageTrace` et al. from Hardhat. -use std::rc::Rc; +use std::sync::Arc; use edr_eth::{Address, Bytes, U256}; @@ -58,7 +58,7 @@ pub struct CreateMessage { /// Children messages. pub steps: Vec, /// Resolved metadata of the contract that is being executed. - pub contract_meta: Option>, + pub contract_meta: Option>, /// Address of the deployed contract. pub deployed_contract: Option, /// Code of the contract that is being executed. @@ -86,7 +86,7 @@ pub struct CallMessage { /// Children messages. pub steps: Vec, /// Resolved metadata of the contract that is being executed. - pub contract_meta: Option>, + pub contract_meta: Option>, /// Calldata buffer pub calldata: Bytes, /// Address of the contract that is being executed. @@ -138,10 +138,10 @@ pub(crate) enum CreateOrCallMessageRef<'a> { } impl<'a> CreateOrCallMessageRef<'a> { - pub fn contract_meta(&self) -> Option> { + pub fn contract_meta(&self) -> Option> { match self { - CreateOrCallMessageRef::Create(create) => create.contract_meta.as_ref().map(Rc::clone), - CreateOrCallMessageRef::Call(call) => call.contract_meta.as_ref().map(Rc::clone), + CreateOrCallMessageRef::Create(create) => create.contract_meta.as_ref().map(Arc::clone), + CreateOrCallMessageRef::Call(call) => call.contract_meta.as_ref().map(Arc::clone), } } diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index 51bd4202c..d8af21d07 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -13,9 +13,11 @@ edr_eth = { version = "0.3.5", path = "../edr_eth" } edr_evm = { version = "0.3.5", path = "../edr_evm", features = ["tracing"] } edr_provider = { version = "0.3.5", path = "../edr_provider", features = ["test-utils"] } edr_rpc_eth = { version = "0.3.5", path = "../edr_rpc_eth" } +edr_solidity = { version = "0.3.5", path = "../edr_solidity" } flate2 = "1.0.28" indicatif = { version = "0.17.7", features = ["rayon"] } mimalloc = { version = "0.1.39", default-features = false } +parking_lot = { version = "0.12.1", default-features = false } reqwest = { version = "0.11.12", features = ["blocking"] } serde_json = "1.0.107" serde = { version = "1.0.189", features = ["derive"] } diff --git a/crates/tools/src/scenario.rs b/crates/tools/src/scenario.rs index 291004632..f8623a156 100644 --- a/crates/tools/src/scenario.rs +++ b/crates/tools/src/scenario.rs @@ -9,8 +9,10 @@ use anyhow::Context; use edr_evm::blockchain::BlockchainError; use edr_provider::{time::CurrentTime, Logger, ProviderError, ProviderRequest}; use edr_rpc_eth::jsonrpc; +use edr_solidity::contract_decoder::ContractDecoder; use flate2::bufread::GzDecoder; use indicatif::ProgressBar; +use parking_lot::RwLock; use serde::Deserialize; use tokio::{runtime, task}; #[cfg(feature = "tracing")] @@ -55,6 +57,7 @@ pub async fn execute(scenario_path: &Path, max_count: Option) -> anyhow:: logger, subscription_callback, config.provider_config, + Arc::new(RwLock::new(ContractDecoder::default())), CurrentTime, ) }) @@ -186,4 +189,8 @@ impl Logger for DisabledLogger { ) -> Result<(), Infallible> { Ok(()) } + + fn print_contract_decoding_error(&mut self, _error: &str) -> Result<(), Self::LoggerError> { + Ok(()) + } } From 19eed936c63c490a395f04adab719c6f879abc5b Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 20 Dec 2024 09:32:34 +0100 Subject: [PATCH 14/30] Remove hardhat_getStackTraceFailuresCount logic --- crates/edr_provider/src/lib.rs | 13 ++----------- crates/edr_provider/src/requests/methods.rs | 7 ------- .../tests/hardhat_request_serialization.rs | 5 ----- .../test/internal/hardhat-network/provider/logs.ts | 1 - 4 files changed, 2 insertions(+), 24 deletions(-) diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index f2fa61e2e..e1b58b034 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -51,14 +51,8 @@ use self::{ }; lazy_static! { - pub static ref PRIVATE_RPC_METHODS: HashSet<&'static str> = { - [ - "hardhat_getStackTraceFailuresCount", - "hardhat_setLoggingEnabled", - ] - .into_iter() - .collect() - }; + pub static ref PRIVATE_RPC_METHODS: HashSet<&'static str> = + ["hardhat_setLoggingEnabled",].into_iter().collect(); } #[derive(Clone, Debug)] @@ -411,9 +405,6 @@ impl { hardhat::handle_get_automine_request(data).and_then(to_json) } - MethodInvocation::GetStackTraceFailuresCount(()) => Err(ProviderError::Unimplemented( - "GetStackTraceFailuresCount".to_string(), - )), MethodInvocation::ImpersonateAccount(address) => { hardhat::handle_impersonate_account_request(data, *address).and_then(to_json) } diff --git a/crates/edr_provider/src/requests/methods.rs b/crates/edr_provider/src/requests/methods.rs index 7df97c7dd..8409bc318 100644 --- a/crates/edr_provider/src/requests/methods.rs +++ b/crates/edr_provider/src/requests/methods.rs @@ -309,12 +309,6 @@ pub enum MethodInvocation { /// `hardhat_getAutomine` #[serde(rename = "hardhat_getAutomine", with = "edr_eth::serde::empty_params")] GetAutomine(()), - /// `hardhat_getStackTraceFailuresCount` - #[serde( - rename = "hardhat_getStackTraceFailuresCount", - with = "edr_eth::serde::empty_params" - )] - GetStackTraceFailuresCount(()), /// `hardhat_impersonateAccount` #[serde( rename = "hardhat_impersonateAccount", @@ -475,7 +469,6 @@ impl MethodInvocation { MethodInvocation::AddCompilationResult(_, _, _) => "hardhat_addCompilationResult", MethodInvocation::DropTransaction(_) => "hardhat_dropTransaction", MethodInvocation::GetAutomine(_) => "hardhat_getAutomine", - MethodInvocation::GetStackTraceFailuresCount(_) => "hardhat_getStackTraceFailuresCount", MethodInvocation::ImpersonateAccount(_) => "hardhat_impersonateAccount", MethodInvocation::IntervalMine(_) => "hardhat_intervalMine", MethodInvocation::Metadata(_) => "hardhat_metadata", diff --git a/crates/edr_provider/tests/hardhat_request_serialization.rs b/crates/edr_provider/tests/hardhat_request_serialization.rs index 33a9b493b..8eaba126c 100644 --- a/crates/edr_provider/tests/hardhat_request_serialization.rs +++ b/crates/edr_provider/tests/hardhat_request_serialization.rs @@ -55,11 +55,6 @@ fn serde_hardhat_get_automine() { help_test_method_invocation_serde(MethodInvocation::GetAutomine(())); } -#[test] -fn serde_hardhat_get_stack_trace_failures_count() { - help_test_method_invocation_serde(MethodInvocation::GetStackTraceFailuresCount(())); -} - #[test] fn serde_hardhat_impersonate_account() { help_test_method_invocation_serde(MethodInvocation::ImpersonateAccount( diff --git a/hardhat-tests/test/internal/hardhat-network/provider/logs.ts b/hardhat-tests/test/internal/hardhat-network/provider/logs.ts index e82b03b6e..9c325cf32 100644 --- a/hardhat-tests/test/internal/hardhat-network/provider/logs.ts +++ b/hardhat-tests/test/internal/hardhat-network/provider/logs.ts @@ -47,7 +47,6 @@ describe("Provider logs", function () { }); it("should not log private methods", async function () { - await this.provider.send("hardhat_getStackTraceFailuresCount", []); await this.provider.send("hardhat_setLoggingEnabled", [true]); assert.lengthOf(this.logger.lines, 0); From 5f34c0466f85541c9783db5ff51042468baa2a60 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 20 Dec 2024 09:32:46 +0100 Subject: [PATCH 15/30] Fix missing argument in test --- crates/edr_provider/tests/issues/issue_407.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/edr_provider/tests/issues/issue_407.rs b/crates/edr_provider/tests/issues/issue_407.rs index b35864b0e..8796bfaea 100644 --- a/crates/edr_provider/tests/issues/issue_407.rs +++ b/crates/edr_provider/tests/issues/issue_407.rs @@ -84,6 +84,7 @@ async fn issue_407_int() -> anyhow::Result<()> { logger, subscriber, config, + Arc::>::default(), CurrentTime, )?; From 0e9e26d13fcf2967d4913bcffcc11ea360a83393 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 20 Dec 2024 09:38:57 +0100 Subject: [PATCH 16/30] Fix lint:fix script in hardhat-tests --- hardhat-tests/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hardhat-tests/package.json b/hardhat-tests/package.json index c04d0f0e9..e741d246e 100644 --- a/hardhat-tests/package.json +++ b/hardhat-tests/package.json @@ -57,10 +57,10 @@ "build:edr": "cd ../crates/edr_napi && pnpm build", "clean": "rimraf build-test tsconfig.tsbuildinfo test/internal/hardhat-network/provider/.hardhat_node_test_cache test/internal/hardhat-network/stack-traces/test-files/artifacts", "eslint": "eslint '**/*.ts'", - "lint": "pnpm prettier && pnpm eslint", + "lint": "pnpm prettier --check && pnpm eslint", "lint:fix": "pnpm prettier --write && pnpm eslint --fix", "pretest": "pnpm build", - "prettier": "prettier --check \"**/*.{js,ts,md,json}\"", + "prettier": "prettier \"**/*.{js,ts,md,json}\"", "test": "mocha --recursive \"test/**/*.ts\"", "test:ci": "pnpm test && pnpm test:integration", "test:integration": "bash run-integration-tests.sh" From 27f6e3ca280f3835c12954be9a51fe57fc34417c Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 20 Dec 2024 09:39:20 +0100 Subject: [PATCH 17/30] Remove useless code --- .../internal/hardhat-network/stack-traces/execution.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 1fa963087..b49b99301 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -121,13 +121,6 @@ export async function traceTransaction( ], }); - if (txData.to !== undefined) { - const code = await provider.request({ - method: "eth_getCode", - params: [bytesToHex(txData.to), "latest"], - }); - } - const responseObject: Response = await provider["_provider"].handleRequest(stringifiedArgs); From 71d47224dc69525306d3dd446af26ed8ab149ed7 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 20 Dec 2024 09:43:02 +0100 Subject: [PATCH 18/30] Ignore dot-notation rule for private field --- .../hardhat-network/stack-traces/execution.ts | 1 + .../hardhat-network/stack-traces/test.ts | 33 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index b49b99301..9af8f9ef7 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -122,6 +122,7 @@ export async function traceTransaction( }); const responseObject: Response = + // eslint-disable-next-line @typescript-eslint/dot-notation await provider["_provider"].handleRequest(stringifiedArgs); let response; diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index b4fb1688a..6164d11b6 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -1,4 +1,7 @@ -import { linkHexStringBytecode, stackTraceEntryTypeToString } from "@nomicfoundation/edr"; +import { + linkHexStringBytecode, + stackTraceEntryTypeToString, +} from "@nomicfoundation/edr"; import { toBytes } from "@nomicfoundation/ethereumjs-util"; import { assert } from "chai"; import { BUILD_INFO_FORMAT_VERSION } from "hardhat/internal/constants"; @@ -318,7 +321,7 @@ function compareStackTraces( "message" in actual ? actual.message : "returnData" in actual && - new ReturnData(actual.returnData).isErrorReturnData() + new ReturnData(actual.returnData).isErrorReturnData() ? new ReturnData(actual.returnData).decodeError() : ""; @@ -517,16 +520,18 @@ async function runTest( try { if (tx.stackTrace === undefined) { - if (!(stackTraceOrContractAddress === undefined || - typeof stackTraceOrContractAddress === "string")) { - assert.fail( - `Transaction ${txIndex} shouldn't have failed` - ); + if ( + !( + stackTraceOrContractAddress === undefined || + typeof stackTraceOrContractAddress === "string" + ) + ) { + assert.fail(`Transaction ${txIndex} shouldn't have failed`); } } else { assert.isFalse( stackTraceOrContractAddress === undefined || - typeof stackTraceOrContractAddress === "string", + typeof stackTraceOrContractAddress === "string", `Transaction ${txIndex} should have failed` ); } @@ -694,13 +699,13 @@ const onlyLatestSolcVersions = const filterSolcVersionBy = (versionRange: string) => - ({ solidityVersion, latestSolcVersion }: SolidityCompiler) => { - if (onlyLatestSolcVersions && latestSolcVersion !== true) { - return false; - } + ({ solidityVersion, latestSolcVersion }: SolidityCompiler) => { + if (onlyLatestSolcVersions && latestSolcVersion !== true) { + return false; + } - return semver.satisfies(solidityVersion, versionRange); - }; + return semver.satisfies(solidityVersion, versionRange); + }; const solidity05Compilers = solidityCompilers.filter( filterSolcVersionBy("^0.5.0") From c3a8cd36d61942308cb66371b1126c625b921b84 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 20 Dec 2024 09:53:18 +0100 Subject: [PATCH 19/30] Fix compilation issues in edr_napi ts tests --- crates/edr_napi/test/issues.ts | 1 + crates/edr_napi/test/provider.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/crates/edr_napi/test/issues.ts b/crates/edr_napi/test/issues.ts index 36811eec3..f9e9805e5 100644 --- a/crates/edr_napi/test/issues.ts +++ b/crates/edr_napi/test/issues.ts @@ -81,6 +81,7 @@ describe("Provider", () => { ...providerConfig, }, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); diff --git a/crates/edr_napi/test/provider.ts b/crates/edr_napi/test/provider.ts index 2dd23b355..3fc3e37e2 100644 --- a/crates/edr_napi/test/provider.ts +++ b/crates/edr_napi/test/provider.ts @@ -76,6 +76,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -96,6 +97,7 @@ describe("Provider", () => { ...providerConfig, }, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -108,6 +110,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -148,6 +151,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -193,6 +197,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -232,6 +237,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -278,6 +284,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -317,6 +324,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); @@ -369,6 +377,7 @@ describe("Provider", () => { context, providerConfig, loggerConfig, + {}, (_event: SubscriptionEvent) => {} ); From 218290f82ac1cd76e1ba5c38d9507c26166df336 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Fri, 20 Dec 2024 15:12:31 +0000 Subject: [PATCH 20/30] Move lock inside contract decoder --- crates/edr_napi/src/logger.rs | 8 ++-- crates/edr_napi/src/provider.rs | 16 ++------ crates/edr_provider/src/data.rs | 13 +++---- crates/edr_provider/src/lib.rs | 4 +- .../src/requests/hardhat/compiler.rs | 2 +- crates/edr_provider/tests/eip4844.rs | 15 ++++--- .../tests/eth_max_priority_fee_per_gas.rs | 3 +- crates/edr_provider/tests/issues/issue_324.rs | 3 +- crates/edr_provider/tests/issues/issue_325.rs | 3 +- crates/edr_provider/tests/issues/issue_326.rs | 3 +- crates/edr_provider/tests/issues/issue_346.rs | 3 +- crates/edr_provider/tests/issues/issue_356.rs | 3 +- crates/edr_provider/tests/issues/issue_361.rs | 3 +- crates/edr_provider/tests/issues/issue_384.rs | 3 +- crates/edr_provider/tests/issues/issue_407.rs | 5 +-- crates/edr_provider/tests/issues/issue_503.rs | 3 +- crates/edr_provider/tests/issues/issue_533.rs | 3 +- crates/edr_provider/tests/issues/issue_570.rs | 3 +- crates/edr_provider/tests/issues/issue_588.rs | 3 +- crates/edr_provider/tests/rip7212.rs | 5 +-- crates/edr_provider/tests/timestamp.rs | 3 +- crates/edr_solidity/src/contract_decoder.rs | 39 ++++++++++++------- crates/tools/src/scenario.rs | 3 +- 23 files changed, 64 insertions(+), 85 deletions(-) diff --git a/crates/edr_napi/src/logger.rs b/crates/edr_napi/src/logger.rs index a9b6b0a16..9fac3673a 100644 --- a/crates/edr_napi/src/logger.rs +++ b/crates/edr_napi/src/logger.rs @@ -26,7 +26,6 @@ use napi::{ Env, JsFunction, Status, }; use napi_derive::napi; -use parking_lot::RwLock; use crate::cast::TryCast; @@ -117,7 +116,7 @@ impl Logger { pub fn new( env: &Env, config: LoggerConfig, - contract_decoder: Arc>, + contract_decoder: Arc, ) -> napi::Result { Ok(Self { collector: LogCollector::new(env, config, contract_decoder)?, @@ -254,7 +253,7 @@ pub struct CollapsedMethod { #[derive(Clone)] struct LogCollector { - contract_decoder: Arc>, + contract_decoder: Arc, decode_console_log_inputs_fn: ThreadsafeFunction, ErrorStrategy::Fatal>, indentation: usize, is_enabled: bool, @@ -268,7 +267,7 @@ impl LogCollector { pub fn new( env: &Env, config: LoggerConfig, - contract_decoder: Arc>, + contract_decoder: Arc, ) -> napi::Result { let mut decode_console_log_inputs_fn = config .decode_console_log_inputs_callback @@ -549,7 +548,6 @@ impl LogCollector { function_name, } = self .contract_decoder - .write() .get_contract_and_function_names_for_call(&code, calldata.as_ref()); (contract_name, function_name) } diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index a8aabda7b..687e04dd8 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -7,7 +7,6 @@ use edr_rpc_eth::jsonrpc; use edr_solidity::contract_decoder::ContractDecoder; use napi::{tokio::runtime, Either, Env, JsFunction, JsObject, Status}; use napi_derive::napi; -use parking_lot::RwLock; use self::config::ProviderConfig; use crate::{ @@ -23,7 +22,7 @@ use crate::{ pub struct Provider { provider: Arc>, runtime: runtime::Handle, - contract_decoder: Arc>, + contract_decoder: Arc, #[cfg(feature = "scenarios")] scenario_file: Option>, } @@ -45,15 +44,11 @@ impl Provider { let config = edr_provider::ProviderConfig::try_from(config)?; - // TODO parsing the build info config and creating the contract decoder - // shouldn't happen here as it's blocking the JS event loop, - // but it's not straightforward to do it in a non-blocking way, because `Env` is - // not `Send`. let build_info_config: edr_solidity::contract_decoder::BuildInfoConfig = serde_json::from_value(tracing_config)?; let contract_decoder = ContractDecoder::new(&build_info_config) .map_err(|error| napi::Error::from_reason(error.to_string()))?; - let contract_decoder = Arc::new(RwLock::new(contract_decoder)); + let contract_decoder = Arc::new(contract_decoder); let logger = Box::new(Logger::new( &env, @@ -252,7 +247,7 @@ impl Provider { #[derive(Debug)] struct SolidityTraceData { trace: Arc, - contract_decoder: Arc>, + contract_decoder: Arc, } #[napi] @@ -301,10 +296,7 @@ impl Response { ); if let Some(vm_trace) = hierarchical_trace.result { - let decoded_trace = { - let mut decoder = contract_decoder.write(); - decoder.try_to_decode_message_trace(vm_trace) - }; + let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace); let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace) .map_err(|err| { napi::Error::from_reason(format!( diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 58ef21e81..ac1432587 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -58,7 +58,6 @@ use gas::gas_used_ratio; use indexmap::IndexMap; use itertools::izip; use lru::LruCache; -use parking_lot::RwLock; use revm_precompile::secp256r1; use rpds::HashTrieMapSync; use tokio::runtime; @@ -213,9 +212,7 @@ pub struct ProviderData>>>, current_state_id: StateId, block_number_to_state_id: HashTrieMapSync, - // DEAD LOCK WARNING: This should only be written to from a `hardhat_addCompilationResult` or - // `hardhat_reset` handler. Otherwise there is a risk of deadlocks. - contract_decoder: Arc>, + contract_decoder: Arc, } impl ProviderData { @@ -225,7 +222,7 @@ impl ProviderData, call_override: Option>, config: ProviderConfig, - contract_metadata: Arc>, + contract_decoder: Arc, timer: TimerT, ) -> Result { let InitialAccounts { @@ -322,7 +319,7 @@ impl ProviderData ProviderData &RwLock { + pub fn contract_decoder(&self) -> &ContractDecoder { &self.contract_decoder } @@ -2899,7 +2896,7 @@ pub(crate) mod test_utils { subscription_callback_noop, None, config.clone(), - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index e1b58b034..ad254882e 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -26,7 +26,7 @@ use edr_solidity::contract_decoder::ContractDecoder; use lazy_static::lazy_static; use logger::SyncLogger; use mock::SyncCallOverride; -use parking_lot::{Mutex, RwLock}; +use parking_lot::Mutex; use requests::{eth::handle_set_interval_mining, hardhat::rpc_types::ResetProviderConfig}; use time::{CurrentTime, TimeSinceEpoch}; use tokio::{runtime, sync::Mutex as AsyncMutex, task}; @@ -109,7 +109,7 @@ impl>, subscriber_callback: Box, config: ProviderConfig, - contract_decoder: Arc>, + contract_decoder: Arc, timer: TimerT, ) -> Result { let data = ProviderData::new( diff --git a/crates/edr_provider/src/requests/hardhat/compiler.rs b/crates/edr_provider/src/requests/hardhat/compiler.rs index 8ba854399..b7b9a3630 100644 --- a/crates/edr_provider/src/requests/hardhat/compiler.rs +++ b/crates/edr_provider/src/requests/hardhat/compiler.rs @@ -38,7 +38,7 @@ fn add_compilation_result_inner anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -255,7 +254,7 @@ async fn estimate_gas_unsupported() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -287,7 +286,7 @@ async fn send_transaction_unsupported() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -331,7 +330,7 @@ async fn send_raw_transaction() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -371,7 +370,7 @@ async fn get_transaction() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -419,7 +418,7 @@ async fn block_header() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -606,7 +605,7 @@ async fn blob_hash_opcode() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs b/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs index 4b5069f6d..4c026e839 100644 --- a/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs +++ b/crates/edr_provider/tests/eth_max_priority_fee_per_gas.rs @@ -7,7 +7,6 @@ use edr_provider::{ ProviderRequest, }; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -20,7 +19,7 @@ async fn eth_max_priority_fee_per_gas() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_324.rs b/crates/edr_provider/tests/issues/issue_324.rs index 569d88864..3c2bada8c 100644 --- a/crates/edr_provider/tests/issues/issue_324.rs +++ b/crates/edr_provider/tests/issues/issue_324.rs @@ -8,7 +8,6 @@ use edr_provider::{ use edr_rpc_eth::CallRequest; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -37,7 +36,7 @@ async fn issue_324() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_325.rs b/crates/edr_provider/tests/issues/issue_325.rs index 12262e2d4..919280bea 100644 --- a/crates/edr_provider/tests/issues/issue_325.rs +++ b/crates/edr_provider/tests/issues/issue_325.rs @@ -10,7 +10,6 @@ use edr_provider::{ MethodInvocation, MiningConfig, NoopLogger, Provider, ProviderRequest, }; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -41,7 +40,7 @@ async fn issue_325() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_326.rs b/crates/edr_provider/tests/issues/issue_326.rs index a8507fbdb..9f386114e 100644 --- a/crates/edr_provider/tests/issues/issue_326.rs +++ b/crates/edr_provider/tests/issues/issue_326.rs @@ -9,7 +9,6 @@ use edr_provider::{ }; use edr_rpc_eth::CallRequest; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -41,7 +40,7 @@ async fn issue_326() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_346.rs b/crates/edr_provider/tests/issues/issue_346.rs index 6530e6cd1..de74300ca 100644 --- a/crates/edr_provider/tests/issues/issue_346.rs +++ b/crates/edr_provider/tests/issues/issue_346.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use edr_provider::{test_utils::create_test_config, time::CurrentTime, NoopLogger, Provider}; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use serde_json::json; use tokio::runtime; @@ -17,7 +16,7 @@ async fn issue_346() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_356.rs b/crates/edr_provider/tests/issues/issue_356.rs index 9f4650785..f1053a572 100644 --- a/crates/edr_provider/tests/issues/issue_356.rs +++ b/crates/edr_provider/tests/issues/issue_356.rs @@ -9,7 +9,6 @@ use edr_provider::{ use edr_rpc_eth::CallRequest; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; -use parking_lot::RwLock; use sha3::{Digest, Keccak256}; use tokio::runtime; @@ -39,7 +38,7 @@ async fn issue_356() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_361.rs b/crates/edr_provider/tests/issues/issue_361.rs index e27503265..5af23d6ce 100644 --- a/crates/edr_provider/tests/issues/issue_361.rs +++ b/crates/edr_provider/tests/issues/issue_361.rs @@ -11,7 +11,6 @@ use edr_provider::{ MethodInvocation, NoopLogger, Provider, ProviderRequest, }; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -38,7 +37,7 @@ async fn issue_361() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_384.rs b/crates/edr_provider/tests/issues/issue_384.rs index c7eb4e578..516496c22 100644 --- a/crates/edr_provider/tests/issues/issue_384.rs +++ b/crates/edr_provider/tests/issues/issue_384.rs @@ -6,7 +6,6 @@ use edr_provider::{ }; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_infura_url; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -27,7 +26,7 @@ async fn avalanche_chain_mine_local_block() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_407.rs b/crates/edr_provider/tests/issues/issue_407.rs index 8796bfaea..c40307c0d 100644 --- a/crates/edr_provider/tests/issues/issue_407.rs +++ b/crates/edr_provider/tests/issues/issue_407.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use edr_provider::{test_utils::create_test_config, time::CurrentTime, NoopLogger, Provider}; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use serde_json::json; use tokio::runtime; @@ -18,7 +17,7 @@ async fn issue_407_uint() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -84,7 +83,7 @@ async fn issue_407_int() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_503.rs b/crates/edr_provider/tests/issues/issue_503.rs index 80805b429..4b73a385a 100644 --- a/crates/edr_provider/tests/issues/issue_503.rs +++ b/crates/edr_provider/tests/issues/issue_503.rs @@ -7,7 +7,6 @@ use edr_provider::{ }; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; -use parking_lot::RwLock; use tokio::runtime; // https://github.com/NomicFoundation/edr/issues/503 @@ -28,7 +27,7 @@ async fn issue_503() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_533.rs b/crates/edr_provider/tests/issues/issue_533.rs index 0feb8b858..005c5af1a 100644 --- a/crates/edr_provider/tests/issues/issue_533.rs +++ b/crates/edr_provider/tests/issues/issue_533.rs @@ -7,7 +7,6 @@ use edr_provider::{ }; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; -use parking_lot::RwLock; use tokio::runtime; // https://github.com/NomicFoundation/edr/issues/533 @@ -30,7 +29,7 @@ async fn issue_533() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/issues/issue_570.rs b/crates/edr_provider/tests/issues/issue_570.rs index 28abd623a..30e1813ac 100644 --- a/crates/edr_provider/tests/issues/issue_570.rs +++ b/crates/edr_provider/tests/issues/issue_570.rs @@ -7,7 +7,6 @@ use edr_provider::{ }; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; -use parking_lot::RwLock; use serial_test::serial; use tokio::runtime; // SAFETY: tests that modify the environment should be run serially. @@ -37,7 +36,7 @@ fn get_provider() -> anyhow::Result> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?) } diff --git a/crates/edr_provider/tests/issues/issue_588.rs b/crates/edr_provider/tests/issues/issue_588.rs index 914cd82b3..e19e09d27 100644 --- a/crates/edr_provider/tests/issues/issue_588.rs +++ b/crates/edr_provider/tests/issues/issue_588.rs @@ -10,7 +10,6 @@ use edr_provider::{ }; use edr_solidity::contract_decoder::ContractDecoder; use edr_test_utils::env::get_alchemy_url; -use parking_lot::RwLock; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -31,7 +30,7 @@ async fn issue_588() -> anyhow::Result<()> { logger, subscriber, early_mainnet_fork, - Arc::>::default(), + Arc::::default(), current_time_is_1970, )?; diff --git a/crates/edr_provider/tests/rip7212.rs b/crates/edr_provider/tests/rip7212.rs index d20c86061..bd8512cb1 100644 --- a/crates/edr_provider/tests/rip7212.rs +++ b/crates/edr_provider/tests/rip7212.rs @@ -10,7 +10,6 @@ use edr_provider::{ }; use edr_rpc_eth::CallRequest; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use revm_precompile::secp256r1; use tokio::runtime; @@ -30,7 +29,7 @@ async fn rip7212_disabled() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; @@ -61,7 +60,7 @@ async fn rip7212_enabled() -> anyhow::Result<()> { logger, subscriber, config, - Arc::>::default(), + Arc::::default(), CurrentTime, )?; diff --git a/crates/edr_provider/tests/timestamp.rs b/crates/edr_provider/tests/timestamp.rs index 00ab36899..04606c619 100644 --- a/crates/edr_provider/tests/timestamp.rs +++ b/crates/edr_provider/tests/timestamp.rs @@ -9,7 +9,6 @@ use edr_provider::{ MethodInvocation, NoopLogger, Provider, ProviderRequest, Timestamp, }; use edr_solidity::contract_decoder::ContractDecoder; -use parking_lot::RwLock; use tokio::runtime; struct TimestampFixture { @@ -32,7 +31,7 @@ impl TimestampFixture { logger, subscription_callback_noop, config.clone(), - Arc::>::default(), + Arc::::default(), mock_timer.clone(), )?; diff --git a/crates/edr_solidity/src/contract_decoder.rs b/crates/edr_solidity/src/contract_decoder.rs index 06d354604..5d798c61e 100644 --- a/crates/edr_solidity/src/contract_decoder.rs +++ b/crates/edr_solidity/src/contract_decoder.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use edr_eth::Bytes; +use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use super::{ @@ -40,7 +41,7 @@ pub enum ContractDecoderError { /// Get contract metadata from calldata and traces. #[derive(Debug, Default)] pub struct ContractDecoder { - contracts_identifier: ContractsIdentifier, + contracts_identifier: RwLock, } impl ContractDecoder { @@ -49,26 +50,30 @@ impl ContractDecoder { let contracts_identifier = initialize_contracts_identifier(config) .map_err(|err| ContractDecoderError::Initialization(err.to_string()))?; Ok(Self { - contracts_identifier, + contracts_identifier: RwLock::new(contracts_identifier), }) } /// Adds contract metadata to the decoder. - pub fn add_contract_metadata(&mut self, bytecode: ContractMetadata) { - self.contracts_identifier.add_bytecode(Arc::new(bytecode)); + pub fn add_contract_metadata(&self, bytecode: ContractMetadata) { + self.contracts_identifier + .write() + .add_bytecode(Arc::new(bytecode)); } /// Enriches the [`NestedTrace`] with the resolved [`ContractMetadata`]. - pub fn try_to_decode_message_trace(&mut self, message_trace: NestedTrace) -> NestedTrace { + pub fn try_to_decode_message_trace(&self, message_trace: NestedTrace) -> NestedTrace { match message_trace { precompile @ NestedTrace::Precompile(..) => precompile, // NOTE: The branches below are the same with the difference of `is_create` NestedTrace::Call(mut call) => { let is_create = false; - let contract_meta = self - .contracts_identifier - .get_bytecode_for_call(call.code.as_ref(), is_create); + let contract_meta = { + self.contracts_identifier + .write() + .get_bytecode_for_call(call.code.as_ref(), is_create) + }; let steps = call .steps @@ -101,9 +106,11 @@ impl ContractDecoder { NestedTrace::Create(mut create @ CreateMessage { .. }) => { let is_create = true; - let contract_meta = self - .contracts_identifier - .get_bytecode_for_call(create.code.as_ref(), is_create); + let contract_meta = { + self.contracts_identifier + .write() + .get_bytecode_for_call(create.code.as_ref(), is_create) + }; let steps = create .steps @@ -138,14 +145,16 @@ impl ContractDecoder { /// Returns the contract and function names for the provided calldata. pub fn get_contract_and_function_names_for_call( - &mut self, + &self, code: &Bytes, calldata: Option<&Bytes>, ) -> ContractAndFunctionName { let is_create = calldata.is_none(); - let bytecode = self - .contracts_identifier - .get_bytecode_for_call(code.as_ref(), is_create); + let bytecode = { + self.contracts_identifier + .write() + .get_bytecode_for_call(code.as_ref(), is_create) + }; let contract = bytecode.map(|bytecode| bytecode.contract.clone()); let contract = contract.as_ref().map(|c| c.read()); diff --git a/crates/tools/src/scenario.rs b/crates/tools/src/scenario.rs index f8623a156..348c1f409 100644 --- a/crates/tools/src/scenario.rs +++ b/crates/tools/src/scenario.rs @@ -12,7 +12,6 @@ use edr_rpc_eth::jsonrpc; use edr_solidity::contract_decoder::ContractDecoder; use flate2::bufread::GzDecoder; use indicatif::ProgressBar; -use parking_lot::RwLock; use serde::Deserialize; use tokio::{runtime, task}; #[cfg(feature = "tracing")] @@ -57,7 +56,7 @@ pub async fn execute(scenario_path: &Path, max_count: Option) -> anyhow:: logger, subscription_callback, config.provider_config, - Arc::new(RwLock::new(ContractDecoder::default())), + Arc::new(ContractDecoder::default()), CurrentTime, ) }) From a16338cdc1b1471f7818835121faf1f0fe411a20 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Fri, 20 Dec 2024 17:10:12 +0000 Subject: [PATCH 21/30] Fix naming --- crates/edr_napi/src/provider.rs | 11 +++++------ crates/edr_solidity/src/nested_tracer.rs | 22 ++++++++++------------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 687e04dd8..2e3a87601 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -290,12 +290,11 @@ impl Response { else { return Ok(None); }; - let hierarchical_trace = - edr_solidity::nested_tracer::convert_trace_messages_to_hierarchical_trace( - trace.as_ref().clone(), - ); + let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace( + trace.as_ref().clone(), + ); - if let Some(vm_trace) = hierarchical_trace.result { + if let Some(vm_trace) = nested_trace.result { let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace); let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace) .map_err(|err| { @@ -309,7 +308,7 @@ impl Response { .collect::, _>>()?; Ok(Some(Either::A(stack_trace))) - } else if let Some(vm_tracer_error) = hierarchical_trace.error { + } else if let Some(vm_tracer_error) = nested_trace.error { Ok(Some(Either::B(vm_tracer_error.to_string()))) } else { Ok(None) diff --git a/crates/edr_solidity/src/nested_tracer.rs b/crates/edr_solidity/src/nested_tracer.rs index 5103bc472..436a855eb 100644 --- a/crates/edr_solidity/src/nested_tracer.rs +++ b/crates/edr_solidity/src/nested_tracer.rs @@ -37,7 +37,7 @@ pub enum NestedTracerError { StepDuringPreCompile, } -/// The result of converting a trace to a hierarchical trace. +/// The result of converting a trace to a nested trace. /// If there was an error, the error is stored in `error` and the `result` is /// the last successfully decoded trace if any. pub struct NestedTracerResult { @@ -57,10 +57,8 @@ impl From for Result, NestedTracerError> } /// Observes a trace, collecting information about the execution of the EVM. -pub fn convert_trace_messages_to_hierarchical_trace( - trace: edr_evm::trace::Trace, -) -> NestedTracerResult { - let mut tracer = HierarchicalTracer::new(); +pub fn convert_trace_messages_to_nested_trace(trace: edr_evm::trace::Trace) -> NestedTracerResult { + let mut tracer = NestedTracer::new(); let error = tracer.add_messages(trace.messages).err(); let result = tracer.get_last_top_level_message_trace(); @@ -69,12 +67,12 @@ pub fn convert_trace_messages_to_hierarchical_trace( } /// Naive Rust port of the `VmTracer` from Hardhat. -struct HierarchicalTracer { +struct NestedTracer { tracing_steps: Vec, message_traces: Vec>>, } -impl Default for HierarchicalTracer { +impl Default for NestedTracer { fn default() -> Self { Self::new() } @@ -86,10 +84,10 @@ impl Default for HierarchicalTracer { // precompiles, which start at 0x100). const MAX_PRECOMPILE_NUMBER: u16 = 10; -impl HierarchicalTracer { - /// Creates a new [`HierarchicalTracer`]. +impl NestedTracer { + /// Creates a new [`NestedTracer`]. const fn new() -> Self { - HierarchicalTracer { + NestedTracer { tracing_steps: Vec::new(), message_traces: Vec::new(), } @@ -273,7 +271,7 @@ impl HierarchicalTracer { } } -/// A hierarchical trace where the message steps are shared and mutable via a +/// A nested trace where the message steps are shared and mutable via a /// refcell. #[derive(Clone, Debug)] enum InternalNestedTrace { @@ -372,7 +370,7 @@ struct InternalCreateMessage { #[derive(Clone, Debug)] enum InternalNestedTraceStep { /// [`NestedTrace`] variant. - // It's both read and written to (updated) by the `[HierarchicalTracer]`. + // It's both read and written to (updated) by the `[NestedTracer]`. Message(Rc>), /// [`EvmStep`] variant. Evm(EvmStep), From 86a617812613ffd39d7eea634d09043fb85e8d64 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 23 Dec 2024 11:27:10 +0100 Subject: [PATCH 22/30] Add test for hardhat_addCompilationResult --- .../hardhat-network/helpers/buildInfos.ts | 684 ++++++++++++++++++ .../internal/hardhat-network/provider/logs.ts | 67 ++ 2 files changed, 751 insertions(+) create mode 100644 hardhat-tests/test/internal/hardhat-network/helpers/buildInfos.ts diff --git a/hardhat-tests/test/internal/hardhat-network/helpers/buildInfos.ts b/hardhat-tests/test/internal/hardhat-network/helpers/buildInfos.ts new file mode 100644 index 000000000..de3e824ea --- /dev/null +++ b/hardhat-tests/test/internal/hardhat-network/helpers/buildInfos.ts @@ -0,0 +1,684 @@ +export const exampleBuildInfo = { + id: "6dccf5d5d3b5e72e738c81b1acfbf389", + _format: "hh-sol-build-info-1", + solcVersion: "0.8.0", + solcLongVersion: "0.8.0+commit.c7dfd78e", + input: { + language: "Solidity", + sources: { + "contracts/Example.sol": { + content: + "// SPDX-License-Identifier: Unlicense\npragma solidity ^0.8.0;\ncontract Example {\n uint public x;\n\n function inc() public {\n x++;\n }\n}", + }, + }, + settings: { + optimizer: { + enabled: false, + runs: 200, + }, + outputSelection: { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + ], + "": ["ast"], + }, + }, + }, + }, + output: { + sources: { + "contracts/Example.sol": { + ast: { + absolutePath: "contracts/Example.sol", + exportedSymbols: { + Example: [11], + }, + id: 12, + license: "Unlicense", + nodeType: "SourceUnit", + nodes: [ + { + id: 1, + literals: ["solidity", "^", "0.8", ".0"], + nodeType: "PragmaDirective", + src: "38:23:0", + }, + { + abstract: false, + baseContracts: [], + contractDependencies: [], + contractKind: "contract", + fullyImplemented: true, + id: 11, + linearizedBaseContracts: [11], + name: "Example", + nodeType: "ContractDefinition", + nodes: [ + { + constant: false, + functionSelector: "0c55699c", + id: 3, + mutability: "mutable", + name: "x", + nodeType: "VariableDeclaration", + scope: 11, + src: "85:13:0", + stateVariable: true, + storageLocation: "default", + typeDescriptions: { + typeIdentifier: "t_uint256", + typeString: "uint256", + }, + typeName: { + id: 2, + name: "uint", + nodeType: "ElementaryTypeName", + src: "85:4:0", + typeDescriptions: { + typeIdentifier: "t_uint256", + typeString: "uint256", + }, + }, + visibility: "public", + }, + { + body: { + id: 9, + nodeType: "Block", + src: "127:20:0", + statements: [ + { + expression: { + id: 7, + isConstant: false, + isLValue: false, + isPure: false, + lValueRequested: false, + nodeType: "UnaryOperation", + operator: "++", + prefix: false, + src: "137:3:0", + subExpression: { + id: 6, + name: "x", + nodeType: "Identifier", + overloadedDeclarations: [], + referencedDeclaration: 3, + src: "137:1:0", + typeDescriptions: { + typeIdentifier: "t_uint256", + typeString: "uint256", + }, + }, + typeDescriptions: { + typeIdentifier: "t_uint256", + typeString: "uint256", + }, + }, + id: 8, + nodeType: "ExpressionStatement", + src: "137:3:0", + }, + ], + }, + functionSelector: "371303c0", + id: 10, + implemented: true, + kind: "function", + modifiers: [], + name: "inc", + nodeType: "FunctionDefinition", + parameters: { + id: 4, + nodeType: "ParameterList", + parameters: [], + src: "117:2:0", + }, + returnParameters: { + id: 5, + nodeType: "ParameterList", + parameters: [], + src: "127:0:0", + }, + scope: 11, + src: "105:42:0", + stateMutability: "nonpayable", + virtual: false, + visibility: "public", + }, + ], + scope: 12, + src: "62:87:0", + }, + ], + src: "38:111:0", + }, + id: 0, + }, + }, + contracts: { + "contracts/Example.sol": { + Example: { + abi: [ + { + inputs: [], + name: "inc", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "x", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + ], + evm: { + bytecode: { + generatedSources: [], + linkReferences: {}, + object: + "608060405234801561001057600080fd5b50610164806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80630c55699c1461003b578063371303c014610059575b600080fd5b610043610063565b6040516100509190610091565b60405180910390f35b610061610069565b005b60005481565b60008081548092919061007b906100b6565b9190505550565b61008b816100ac565b82525050565b60006020820190506100a66000830184610082565b92915050565b6000819050919050565b60006100c1826100ac565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156100f4576100f36100ff565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220f059e1364306f26c1c4d0e1654e8eb8c7aa23a31ade199b12e96e2c80bc4b6ce64736f6c63430008000033", + opcodes: + "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x164 DUP1 PUSH2 0x20 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x36 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xC55699C EQ PUSH2 0x3B JUMPI DUP1 PUSH4 0x371303C0 EQ PUSH2 0x59 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x43 PUSH2 0x63 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x50 SWAP2 SWAP1 PUSH2 0x91 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x61 PUSH2 0x69 JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 DUP2 SLOAD DUP1 SWAP3 SWAP2 SWAP1 PUSH2 0x7B SWAP1 PUSH2 0xB6 JUMP JUMPDEST SWAP2 SWAP1 POP SSTORE POP JUMP JUMPDEST PUSH2 0x8B DUP2 PUSH2 0xAC JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xA6 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0x82 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xC1 DUP3 PUSH2 0xAC JUMP JUMPDEST SWAP2 POP PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 EQ ISZERO PUSH2 0xF4 JUMPI PUSH2 0xF3 PUSH2 0xFF JUMP JUMPDEST JUMPDEST PUSH1 0x1 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 CREATE MSIZE 0xE1 CALLDATASIZE NUMBER MOD CALLCODE PUSH13 0x1C4D0E1654E8EB8C7AA23A31AD 0xE1 SWAP10 0xB1 0x2E SWAP7 0xE2 0xC8 SIGNEXTEND 0xC4 0xB6 0xCE PUSH5 0x736F6C6343 STOP ADDMOD STOP STOP CALLER ", + sourceMap: "62:87:0:-:0;;;;;;;;;;;;;;;;;;;", + }, + deployedBytecode: { + generatedSources: [ + { + ast: { + nodeType: "YulBlock", + src: "0:864:1", + statements: [ + { + body: { + nodeType: "YulBlock", + src: "72:53:1", + statements: [ + { + expression: { + arguments: [ + { + name: "pos", + nodeType: "YulIdentifier", + src: "89:3:1", + }, + { + arguments: [ + { + name: "value", + nodeType: "YulIdentifier", + src: "112:5:1", + }, + ], + functionName: { + name: "cleanup_t_uint256", + nodeType: "YulIdentifier", + src: "94:17:1", + }, + nodeType: "YulFunctionCall", + src: "94:24:1", + }, + ], + functionName: { + name: "mstore", + nodeType: "YulIdentifier", + src: "82:6:1", + }, + nodeType: "YulFunctionCall", + src: "82:37:1", + }, + nodeType: "YulExpressionStatement", + src: "82:37:1", + }, + ], + }, + name: "abi_encode_t_uint256_to_t_uint256_fromStack", + nodeType: "YulFunctionDefinition", + parameters: [ + { + name: "value", + nodeType: "YulTypedName", + src: "60:5:1", + type: "", + }, + { + name: "pos", + nodeType: "YulTypedName", + src: "67:3:1", + type: "", + }, + ], + src: "7:118:1", + }, + { + body: { + nodeType: "YulBlock", + src: "229:124:1", + statements: [ + { + nodeType: "YulAssignment", + src: "239:26:1", + value: { + arguments: [ + { + name: "headStart", + nodeType: "YulIdentifier", + src: "251:9:1", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "262:2:1", + type: "", + value: "32", + }, + ], + functionName: { + name: "add", + nodeType: "YulIdentifier", + src: "247:3:1", + }, + nodeType: "YulFunctionCall", + src: "247:18:1", + }, + variableNames: [ + { + name: "tail", + nodeType: "YulIdentifier", + src: "239:4:1", + }, + ], + }, + { + expression: { + arguments: [ + { + name: "value0", + nodeType: "YulIdentifier", + src: "319:6:1", + }, + { + arguments: [ + { + name: "headStart", + nodeType: "YulIdentifier", + src: "332:9:1", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "343:1:1", + type: "", + value: "0", + }, + ], + functionName: { + name: "add", + nodeType: "YulIdentifier", + src: "328:3:1", + }, + nodeType: "YulFunctionCall", + src: "328:17:1", + }, + ], + functionName: { + name: "abi_encode_t_uint256_to_t_uint256_fromStack", + nodeType: "YulIdentifier", + src: "275:43:1", + }, + nodeType: "YulFunctionCall", + src: "275:71:1", + }, + nodeType: "YulExpressionStatement", + src: "275:71:1", + }, + ], + }, + name: "abi_encode_tuple_t_uint256__to_t_uint256__fromStack_reversed", + nodeType: "YulFunctionDefinition", + parameters: [ + { + name: "headStart", + nodeType: "YulTypedName", + src: "201:9:1", + type: "", + }, + { + name: "value0", + nodeType: "YulTypedName", + src: "213:6:1", + type: "", + }, + ], + returnVariables: [ + { + name: "tail", + nodeType: "YulTypedName", + src: "224:4:1", + type: "", + }, + ], + src: "131:222:1", + }, + { + body: { + nodeType: "YulBlock", + src: "404:32:1", + statements: [ + { + nodeType: "YulAssignment", + src: "414:16:1", + value: { + name: "value", + nodeType: "YulIdentifier", + src: "425:5:1", + }, + variableNames: [ + { + name: "cleaned", + nodeType: "YulIdentifier", + src: "414:7:1", + }, + ], + }, + ], + }, + name: "cleanup_t_uint256", + nodeType: "YulFunctionDefinition", + parameters: [ + { + name: "value", + nodeType: "YulTypedName", + src: "386:5:1", + type: "", + }, + ], + returnVariables: [ + { + name: "cleaned", + nodeType: "YulTypedName", + src: "396:7:1", + type: "", + }, + ], + src: "359:77:1", + }, + { + body: { + nodeType: "YulBlock", + src: "485:190:1", + statements: [ + { + nodeType: "YulAssignment", + src: "495:33:1", + value: { + arguments: [ + { + name: "value", + nodeType: "YulIdentifier", + src: "522:5:1", + }, + ], + functionName: { + name: "cleanup_t_uint256", + nodeType: "YulIdentifier", + src: "504:17:1", + }, + nodeType: "YulFunctionCall", + src: "504:24:1", + }, + variableNames: [ + { + name: "value", + nodeType: "YulIdentifier", + src: "495:5:1", + }, + ], + }, + { + body: { + nodeType: "YulBlock", + src: "618:22:1", + statements: [ + { + expression: { + arguments: [], + functionName: { + name: "panic_error_0x11", + nodeType: "YulIdentifier", + src: "620:16:1", + }, + nodeType: "YulFunctionCall", + src: "620:18:1", + }, + nodeType: "YulExpressionStatement", + src: "620:18:1", + }, + ], + }, + condition: { + arguments: [ + { + name: "value", + nodeType: "YulIdentifier", + src: "543:5:1", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "550:66:1", + type: "", + value: + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + ], + functionName: { + name: "eq", + nodeType: "YulIdentifier", + src: "540:2:1", + }, + nodeType: "YulFunctionCall", + src: "540:77:1", + }, + nodeType: "YulIf", + src: "537:2:1", + }, + { + nodeType: "YulAssignment", + src: "649:20:1", + value: { + arguments: [ + { + name: "value", + nodeType: "YulIdentifier", + src: "660:5:1", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "667:1:1", + type: "", + value: "1", + }, + ], + functionName: { + name: "add", + nodeType: "YulIdentifier", + src: "656:3:1", + }, + nodeType: "YulFunctionCall", + src: "656:13:1", + }, + variableNames: [ + { + name: "ret", + nodeType: "YulIdentifier", + src: "649:3:1", + }, + ], + }, + ], + }, + name: "increment_t_uint256", + nodeType: "YulFunctionDefinition", + parameters: [ + { + name: "value", + nodeType: "YulTypedName", + src: "471:5:1", + type: "", + }, + ], + returnVariables: [ + { + name: "ret", + nodeType: "YulTypedName", + src: "481:3:1", + type: "", + }, + ], + src: "442:233:1", + }, + { + body: { + nodeType: "YulBlock", + src: "709:152:1", + statements: [ + { + expression: { + arguments: [ + { + kind: "number", + nodeType: "YulLiteral", + src: "726:1:1", + type: "", + value: "0", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "729:77:1", + type: "", + value: + "35408467139433450592217433187231851964531694900788300625387963629091585785856", + }, + ], + functionName: { + name: "mstore", + nodeType: "YulIdentifier", + src: "719:6:1", + }, + nodeType: "YulFunctionCall", + src: "719:88:1", + }, + nodeType: "YulExpressionStatement", + src: "719:88:1", + }, + { + expression: { + arguments: [ + { + kind: "number", + nodeType: "YulLiteral", + src: "823:1:1", + type: "", + value: "4", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "826:4:1", + type: "", + value: "0x11", + }, + ], + functionName: { + name: "mstore", + nodeType: "YulIdentifier", + src: "816:6:1", + }, + nodeType: "YulFunctionCall", + src: "816:15:1", + }, + nodeType: "YulExpressionStatement", + src: "816:15:1", + }, + { + expression: { + arguments: [ + { + kind: "number", + nodeType: "YulLiteral", + src: "847:1:1", + type: "", + value: "0", + }, + { + kind: "number", + nodeType: "YulLiteral", + src: "850:4:1", + type: "", + value: "0x24", + }, + ], + functionName: { + name: "revert", + nodeType: "YulIdentifier", + src: "840:6:1", + }, + nodeType: "YulFunctionCall", + src: "840:15:1", + }, + nodeType: "YulExpressionStatement", + src: "840:15:1", + }, + ], + }, + name: "panic_error_0x11", + nodeType: "YulFunctionDefinition", + src: "681:180:1", + }, + ], + }, + contents: + "{\n\n function abi_encode_t_uint256_to_t_uint256_fromStack(value, pos) {\n mstore(pos, cleanup_t_uint256(value))\n }\n\n function abi_encode_tuple_t_uint256__to_t_uint256__fromStack_reversed(headStart , value0) -> tail {\n tail := add(headStart, 32)\n\n abi_encode_t_uint256_to_t_uint256_fromStack(value0, add(headStart, 0))\n\n }\n\n function cleanup_t_uint256(value) -> cleaned {\n cleaned := value\n }\n\n function increment_t_uint256(value) -> ret {\n value := cleanup_t_uint256(value)\n if eq(value, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) { panic_error_0x11() }\n ret := add(value, 1)\n }\n\n function panic_error_0x11() {\n mstore(0, 35408467139433450592217433187231851964531694900788300625387963629091585785856)\n mstore(4, 0x11)\n revert(0, 0x24)\n }\n\n}\n", + id: 1, + language: "Yul", + name: "#utility.yul", + }, + ], + immutableReferences: {}, + linkReferences: {}, + object: + "608060405234801561001057600080fd5b50600436106100365760003560e01c80630c55699c1461003b578063371303c014610059575b600080fd5b610043610063565b6040516100509190610091565b60405180910390f35b610061610069565b005b60005481565b60008081548092919061007b906100b6565b9190505550565b61008b816100ac565b82525050565b60006020820190506100a66000830184610082565b92915050565b6000819050919050565b60006100c1826100ac565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156100f4576100f36100ff565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220f059e1364306f26c1c4d0e1654e8eb8c7aa23a31ade199b12e96e2c80bc4b6ce64736f6c63430008000033", + opcodes: + "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x36 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xC55699C EQ PUSH2 0x3B JUMPI DUP1 PUSH4 0x371303C0 EQ PUSH2 0x59 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0x43 PUSH2 0x63 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x50 SWAP2 SWAP1 PUSH2 0x91 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x61 PUSH2 0x69 JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 DUP2 SLOAD DUP1 SWAP3 SWAP2 SWAP1 PUSH2 0x7B SWAP1 PUSH2 0xB6 JUMP JUMPDEST SWAP2 SWAP1 POP SSTORE POP JUMP JUMPDEST PUSH2 0x8B DUP2 PUSH2 0xAC JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xA6 PUSH1 0x0 DUP4 ADD DUP5 PUSH2 0x82 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH2 0xC1 DUP3 PUSH2 0xAC JUMP JUMPDEST SWAP2 POP PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 EQ ISZERO PUSH2 0xF4 JUMPI PUSH2 0xF3 PUSH2 0xFF JUMP JUMPDEST JUMPDEST PUSH1 0x1 DUP3 ADD SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH32 0x4E487B7100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x11 PUSH1 0x4 MSTORE PUSH1 0x24 PUSH1 0x0 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 CREATE MSIZE 0xE1 CALLDATASIZE NUMBER MOD CALLCODE PUSH13 0x1C4D0E1654E8EB8C7AA23A31AD 0xE1 SWAP10 0xB1 0x2E SWAP7 0xE2 0xC8 SIGNEXTEND 0xC4 0xB6 0xCE PUSH5 0x736F6C6343 STOP ADDMOD STOP STOP CALLER ", + sourceMap: + "62:87:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;85:13;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;105:42;;;:::i;:::-;;85:13;;;;:::o;105:42::-;137:1;;:3;;;;;;;;;:::i;:::-;;;;;;105:42::o;7:118:1:-;94:24;112:5;94:24;:::i;:::-;89:3;82:37;72:53;;:::o;131:222::-;;262:2;251:9;247:18;239:26;;275:71;343:1;332:9;328:17;319:6;275:71;:::i;:::-;229:124;;;;:::o;359:77::-;;425:5;414:16;;404:32;;;:::o;442:233::-;;504:24;522:5;504:24;:::i;:::-;495:33;;550:66;543:5;540:77;537:2;;;620:18;;:::i;:::-;537:2;667:1;660:5;656:13;649:20;;485:190;;;:::o;681:180::-;729:77;726:1;719:88;826:4;823:1;816:15;850:4;847:1;840:15", + }, + methodIdentifiers: { + "inc()": "371303c0", + "x()": "0c55699c", + }, + }, + metadata: + '{"compiler":{"version":"0.8.0+commit.c7dfd78e"},"language":"Solidity","output":{"abi":[{"inputs":[],"name":"inc","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"x","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"contracts/Example.sol":"Example"},"evmVersion":"istanbul","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"contracts/Example.sol":{"keccak256":"0x59b0856e75cd15dc2e53235ca43c23e33d46bd284bd87e25fae9c40a1b1f1f6e","license":"Unlicense","urls":["bzz-raw://7e94cba4b8a3d60b45c89e71ef6b594b9c1974ae1ab81d308fac7b6a4bde5d66","dweb:/ipfs/QmW41rG5XT6ADihjEswvg9V34LxJE6KonCK9iyD6CkVu7V"]}},"version":1}', + }, + }, + }, + }, +}; diff --git a/hardhat-tests/test/internal/hardhat-network/provider/logs.ts b/hardhat-tests/test/internal/hardhat-network/provider/logs.ts index 9c325cf32..5d83d2d8d 100644 --- a/hardhat-tests/test/internal/hardhat-network/provider/logs.ts +++ b/hardhat-tests/test/internal/hardhat-network/provider/logs.ts @@ -15,6 +15,7 @@ import { } from "../helpers/providers"; import { deployContract } from "../helpers/transactions"; import { useHelpers } from "../helpers/useHelpers"; +import { exampleBuildInfo } from "../helpers/buildInfos"; import { ansiColor } from "./utils/color"; // eslint-disable prefer-template @@ -336,6 +337,72 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[4], ""); } }); + + it("should update the logged information when a relevant compilation result is added", async function () { + // given: a deployed contract whose compilation info is not available + const address = await deployContract( + this.provider, + exampleBuildInfo.output.contracts["contracts/Example.sol"].Example + .evm.bytecode.object + ); + this.logger.reset(); + + // when: a call is made to the contract + await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: address, + gas: numberToRpcQuantity(1000000), + data: "0x0c55699c", // x() + }, + ]); + + // then: the log should not contain the contract name + assert.lengthOf(this.logger.lines, 5); + assert.equal( + this.logger.lines[0], + ansiColor(this.provider, "eth_call", chalk.green) + ); + // prettier-ignore + { + assert.match(this.logger.lines[1], /^ Contract call: $/); + assert.match(this.logger.lines[2], /^ From: 0x[0-9a-f]{40}$/); + assert.match(this.logger.lines[3], /^ To: 0x[0-9a-f]{40}$/); + assert.equal(this.logger.lines[4], ""); + } + + // when: the compilation result is added and the call is made again + await this.provider.send("hardhat_addCompilationResult", [ + exampleBuildInfo.solcVersion, + exampleBuildInfo.input, + exampleBuildInfo.output, + ]); + + this.logger.reset(); + + await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: address, + gas: numberToRpcQuantity(1000000), + data: "0x0c55699c", // x() + }, + ]); + + // then: the log should contain the contract name + assert.lengthOf(this.logger.lines, 5); + assert.equal( + this.logger.lines[0], + ansiColor(this.provider, "eth_call", chalk.green) + ); + // prettier-ignore + { + assert.match(this.logger.lines[1], /^ Contract call: Example#x$/); + assert.match(this.logger.lines[2], /^ From: 0x[0-9a-f]{40}$/); + assert.match(this.logger.lines[3], /^ To: 0x[0-9a-f]{40}$/); + assert.equal(this.logger.lines[4], ""); + } + }); }); describe("eth_estimateGas", function () { From 5308fcc07c715b377e3b365111152a9923cdd989 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 23 Dec 2024 12:03:53 +0100 Subject: [PATCH 23/30] Cleanup stack traces tests code --- .../hardhat-network/stack-traces/execution.ts | 14 +++- .../hardhat-network/stack-traces/test.ts | 75 +++++++++---------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 9af8f9ef7..3198eea38 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -102,6 +102,11 @@ interface TxData { gas?: bigint; } +/** + * Returns a SolidityStackTrace object if the transaction failed, the contract address if + * the transaction successfully deployed a contract, or undefined if the transaction + * succeeded and didn't deploy a contract. + */ export async function traceTransaction( provider: EdrProviderWrapper, txData: TxData @@ -137,14 +142,15 @@ export async function traceTransaction( params: [response.result ?? response.error.data.transactionHash], }); - const stackTrace = responseObject.stackTrace(); - - const contractAddress = receipt.contractAddress?.slice(2); + const stackTrace: SolidityStackTrace | string | null = + responseObject.stackTrace(); if (typeof stackTrace === "string") { - throw new Error("shouldn't happen"); // FVTODO + throw new Error("this shouldn't happen"); } + const contractAddress = receipt.contractAddress?.slice(2); + if (stackTrace === null) { return contractAddress; } diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index 6164d11b6..dcb1a05d1 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -483,10 +483,10 @@ async function runTest( const txIndexToContract: Map = new Map(); for (const [txIndex, tx] of testDefinition.transactions.entries()) { - let stackTraceOrContractAddress: SolidityStackTrace | string | undefined; + let stackTrace: SolidityStackTrace | undefined; if ("file" in tx) { - stackTraceOrContractAddress = await runDeploymentTransactionTest( + const stackTraceOrContractAddress = await runDeploymentTransactionTest( txIndex, tx, provider, @@ -500,6 +500,8 @@ async function runTest( name: tx.contract, address: Buffer.from(stackTraceOrContractAddress, "hex"), }); + } else { + stackTrace = stackTraceOrContractAddress; } } else { const contract = txIndexToContract.get(tx.to); @@ -509,7 +511,7 @@ async function runTest( `No contract was deployed in tx ${tx.to} but transaction ${txIndex} is trying to call it` ); - stackTraceOrContractAddress = await runCallTransactionTest( + stackTrace = await runCallTransactionTest( txIndex, tx, provider, @@ -518,45 +520,26 @@ async function runTest( ); } - try { - if (tx.stackTrace === undefined) { - if ( - !( - stackTraceOrContractAddress === undefined || - typeof stackTraceOrContractAddress === "string" - ) - ) { - assert.fail(`Transaction ${txIndex} shouldn't have failed`); - } - } else { - assert.isFalse( - stackTraceOrContractAddress === undefined || - typeof stackTraceOrContractAddress === "string", - `Transaction ${txIndex} should have failed` - ); + if (tx.stackTrace === undefined) { + if (stackTrace !== undefined) { + assert.fail(`Transaction ${txIndex} shouldn't have failed`); } - } catch (error) { - // printMessageTrace(decodedTrace); FVTODO - - throw error; + } else { + assert.isFalse( + stackTrace === undefined, + `Transaction ${txIndex} should have failed` + ); } - if ( - stackTraceOrContractAddress !== undefined && - typeof stackTraceOrContractAddress !== "string" - ) { - try { - compareStackTraces( - txIndex, - stackTraceOrContractAddress, - tx.stackTrace!, - compilerOptions.optimizer - ); - if (testDefinition.print !== undefined && testDefinition.print) { - console.log(`Transaction ${txIndex} stack trace`); - } - } catch (err) { - throw err; + if (stackTrace !== undefined) { + compareStackTraces( + txIndex, + stackTrace, + tx.stackTrace!, + compilerOptions.optimizer + ); + if (testDefinition.print !== undefined && testDefinition.print) { + console.log(`Transaction ${txIndex} stack trace`); } } @@ -622,7 +605,7 @@ async function runDeploymentTransactionTest( provider: EdrProviderWrapper, compilerOutput: CompilerOutput, txIndexToContract: Map -): Promise { +): Promise { const file = compilerOutput.contracts[tx.file]; assert.isDefined( @@ -657,6 +640,12 @@ async function runDeploymentTransactionTest( gas: tx.gas !== undefined ? BigInt(tx.gas) : undefined, }); + if (trace === undefined) { + throw new Error( + "deployment transactions should either deploy a contract or fail" + ); + } + return trace; } @@ -666,7 +655,7 @@ async function runCallTransactionTest( provider: EdrProviderWrapper, compilerOutput: CompilerOutput, contract: DeployedContract -): Promise { +): Promise { const compilerContract = compilerOutput.contracts[contract.file][contract.name]; @@ -691,6 +680,10 @@ async function runCallTransactionTest( gas: tx.gas !== undefined ? BigInt(tx.gas) : undefined, }); + if (typeof trace === "string") { + throw new Error("call transactions should not deploy contracts"); + } + return trace; } From b8f0703d68921c80b332842ee76d83b4e6ca6a59 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 23 Dec 2024 12:09:40 +0100 Subject: [PATCH 24/30] Create dirty-humans-exercise.md --- .changeset/dirty-humans-exercise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dirty-humans-exercise.md diff --git a/.changeset/dirty-humans-exercise.md b/.changeset/dirty-humans-exercise.md new file mode 100644 index 000000000..fe5ff563a --- /dev/null +++ b/.changeset/dirty-humans-exercise.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/edr": minor +--- + +Add a `stackTrace` getter to the provider's response and remove the old, lower-level `solidityTrace` getter. From a05caebf78ec6e034ae84ae0c48515376e8866db Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 27 Dec 2024 13:58:18 +0100 Subject: [PATCH 25/30] Upgrade Hardhat dependency --- crates/tools/js/benchmark/package.json | 2 +- hardhat-tests/integration/smock/package.json | 2 +- hardhat-tests/package.json | 2 +- .../stack-traces/compilers-list.ts | 18 +- .../hardhat-network/stack-traces/test.ts | 4 +- package.json | 2 +- patches/hardhat@2.22.15.patch | 230 ---------- patches/hardhat@2.22.17.patch | 423 ++++++++++++++++++ pnpm-lock.yaml | 121 +++-- 9 files changed, 498 insertions(+), 306 deletions(-) delete mode 100644 patches/hardhat@2.22.15.patch create mode 100644 patches/hardhat@2.22.17.patch diff --git a/crates/tools/js/benchmark/package.json b/crates/tools/js/benchmark/package.json index 0987309ca..eb1d37991 100644 --- a/crates/tools/js/benchmark/package.json +++ b/crates/tools/js/benchmark/package.json @@ -17,7 +17,7 @@ "eslint-plugin-import": "2.27.5", "eslint-plugin-mocha": "10.4.1", "eslint-plugin-prettier": "5.2.1", - "hardhat": "2.22.15", + "hardhat": "2.22.17", "lodash": "^4.17.11", "mocha": "^10.0.0", "prettier": "^3.2.5", diff --git a/hardhat-tests/integration/smock/package.json b/hardhat-tests/integration/smock/package.json index fb32ed9b4..836e2d74c 100644 --- a/hardhat-tests/integration/smock/package.json +++ b/hardhat-tests/integration/smock/package.json @@ -9,7 +9,7 @@ "@types/node": "^20.0.0", "chai": "^4.3.6", "ethers": "5.7.2", - "hardhat": "2.22.15", + "hardhat": "2.22.17", "mocha": "^10.0.0" }, "keywords": [], diff --git a/hardhat-tests/package.json b/hardhat-tests/package.json index e741d246e..abecdd674 100644 --- a/hardhat-tests/package.json +++ b/hardhat-tests/package.json @@ -39,7 +39,7 @@ "ethereumjs-abi": "^0.6.8", "ethers": "^6.1.0", "fs-extra": "^7.0.1", - "hardhat": "2.22.15", + "hardhat": "2.22.17", "mocha": "^10.0.0", "prettier": "^3.2.5", "rimraf": "^3.0.2", diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/compilers-list.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/compilers-list.ts index 71c925f81..6d4c1ad3b 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/compilers-list.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/compilers-list.ts @@ -256,7 +256,6 @@ export const solidityCompilers: SolidityCompiler[] = [ { solidityVersion: "0.8.25", compilerPath: "soljson-v0.8.25+commit.b61c2a91.js", - latestSolcVersion: true, }, { solidityVersion: "0.8.25", @@ -265,7 +264,6 @@ export const solidityCompilers: SolidityCompiler[] = [ runs: 200, viaIR: true, }, - latestSolcVersion: true, }, { solidityVersion: "0.8.26", @@ -295,9 +293,23 @@ export const solidityCompilers: SolidityCompiler[] = [ }, latestSolcVersion: true, }, + { + solidityVersion: "0.8.28", + compilerPath: "soljson-v0.8.28+commit.7893614a.js", + latestSolcVersion: true, + }, + { + solidityVersion: "0.8.28", + compilerPath: "soljson-v0.8.28+commit.7893614a.js", + optimizer: { + runs: 200, + viaIR: true, + }, + latestSolcVersion: true, + }, ]; -export const getLatestSupportedVersion = () => +export const getLatestTestedSolcVersion = () => solidityCompilers.map((sc) => sc.solidityVersion).sort(semver.compare)[ solidityCompilers.length - 1 ]; diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index dcb1a05d1..e6161b6f8 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -37,7 +37,7 @@ import { downloadCompiler, } from "./compilation"; import { - getLatestSupportedVersion, + getLatestTestedSolcVersion, SolidityCompiler, SolidityCompilerOptimizer, solidityCompilers, @@ -795,7 +795,7 @@ describe("Stack traces", function () { describe("Solidity support", function () { it("check that the latest tested version is within the supported version range", async function () { - const latestSupportedVersion = getLatestSupportedVersion(); + const latestSupportedVersion = getLatestTestedSolcVersion(); assert.isTrue( semver.satisfies( latestSupportedVersion, diff --git a/package.json b/package.json index d01f5f0bb..21400553f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "hardhat>@nomicfoundation/edr": "workspace:*" }, "patchedDependencies": { - "hardhat@2.22.15": "patches/hardhat@2.22.15.patch" + "hardhat@2.22.17": "patches/hardhat@2.22.17.patch" } }, "private": true, diff --git a/patches/hardhat@2.22.15.patch b/patches/hardhat@2.22.15.patch deleted file mode 100644 index ce644a840..000000000 --- a/patches/hardhat@2.22.15.patch +++ /dev/null @@ -1,230 +0,0 @@ -diff --git a/internal/hardhat-network/provider/provider.js b/internal/hardhat-network/provider/provider.js -index a4b921c8a37b7d5967955d0449df3d05dbe725a8..2a5ed80f3defa71f18c0df97b719820622460fba 100644 ---- a/internal/hardhat-network/provider/provider.js -+++ b/internal/hardhat-network/provider/provider.js -@@ -1,52 +1,22 @@ - "use strict"; --var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { -- if (k2 === undefined) k2 = k; -- var desc = Object.getOwnPropertyDescriptor(m, k); -- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { -- desc = { enumerable: true, get: function() { return m[k]; } }; -- } -- Object.defineProperty(o, k2, desc); --}) : (function(o, m, k, k2) { -- if (k2 === undefined) k2 = k; -- o[k2] = m[k]; --})); --var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { -- Object.defineProperty(o, "default", { enumerable: true, value: v }); --}) : function(o, v) { -- o["default"] = v; --}); --var __importStar = (this && this.__importStar) || function (mod) { -- if (mod && mod.__esModule) return mod; -- var result = {}; -- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); -- __setModuleDefault(result, mod); -- return result; --}; - var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; - }; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.createHardhatNetworkProvider = exports.EdrProviderWrapper = exports.getNodeConfig = exports.getGlobalEdrContext = exports.DEFAULT_COINBASE = void 0; --const chalk_1 = __importDefault(require("chalk")); -+const picocolors_1 = __importDefault(require("picocolors")); - const debug_1 = __importDefault(require("debug")); - const events_1 = require("events"); - const fs_extra_1 = __importDefault(require("fs-extra")); --const t = __importStar(require("io-ts")); - const semver_1 = __importDefault(require("semver")); - const napi_rs_1 = require("../../../common/napi-rs"); - const constants_1 = require("../../constants"); --const solc_1 = require("../../core/jsonrpc/types/input/solc"); --const validation_1 = require("../../core/jsonrpc/types/input/validation"); - const errors_1 = require("../../core/providers/errors"); - const http_1 = require("../../core/providers/http"); - const hardforks_1 = require("../../util/hardforks"); --const compiler_to_model_1 = require("../stack-traces/compiler-to-model"); - const consoleLogger_1 = require("../stack-traces/consoleLogger"); --const vm_trace_decoder_1 = require("../stack-traces/vm-trace-decoder"); - const constants_2 = require("../stack-traces/constants"); - const solidity_errors_1 = require("../stack-traces/solidity-errors"); --const solidityTracer_1 = require("../stack-traces/solidityTracer"); --const vm_tracer_1 = require("../stack-traces/vm-tracer"); - const packageInfo_1 = require("../../util/packageInfo"); - const convertToEdr_1 = require("./utils/convertToEdr"); - const makeCommon_1 = require("./utils/makeCommon"); -@@ -94,18 +64,13 @@ class EdrProviderEventAdapter extends events_1.EventEmitter { - class EdrProviderWrapper extends events_1.EventEmitter { - constructor(_provider, - // we add this for backwards-compatibility with plugins like solidity-coverage -- _node, _vmTraceDecoder, -+ _node, - // The common configuration for EthereumJS VM is not used by EDR, but tests expect it as part of the provider. -- _common, tracingConfig) { -+ _common) { - super(); - this._provider = _provider; - this._node = _node; -- this._vmTraceDecoder = _vmTraceDecoder; - this._common = _common; -- this._failedStackTraces = 0; -- if (tracingConfig !== undefined) { -- (0, vm_trace_decoder_1.initializeVmTraceDecoder)(this._vmTraceDecoder, tracingConfig); -- } - } - static async create(config, loggerConfig, tracingConfig) { - const { Provider } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); -@@ -138,7 +103,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { - const eventAdapter = new EdrProviderEventAdapter(); - const printLineFn = loggerConfig.printLineFn ?? logger_1.printLine; - const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? logger_1.replaceLastLine; -- const vmTraceDecoder = new vm_trace_decoder_1.VmTraceDecoder(); - const hardforkName = (0, hardforks_1.getHardforkName)(config.hardfork); - const provider = await Provider.withConfig(getGlobalEdrContext(), { - allowBlocksWithSameTimestamp: config.allowBlocksWithSameTimestamp ?? false, -@@ -186,7 +150,7 @@ class EdrProviderWrapper extends events_1.EventEmitter { - enable: loggerConfig.enabled, - decodeConsoleLogInputsCallback: consoleLogger_1.ConsoleLogger.getDecodedLogs, - getContractAndFunctionNameCallback: (code, calldata) => { -- return vmTraceDecoder.getContractAndFunctionNamesForCall(code, calldata); -+ return { contractName: "", functionName: "" }; - }, - printLineCallback: (message, replace) => { - if (replace) { -@@ -196,14 +160,14 @@ class EdrProviderWrapper extends events_1.EventEmitter { - printLineFn(message); - } - }, -- }, (event) => { -+ }, tracingConfig ?? {}, (event) => { - eventAdapter.emit("ethEvent", event); - }); - const minimalEthereumJsNode = { - _vm: (0, minimal_vm_1.getMinimalEthereumJsVm)(provider), - }; - const common = (0, makeCommon_1.makeCommon)(getNodeConfig(config)); -- const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode, vmTraceDecoder, common, tracingConfig); -+ const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode, common); - // Pass through all events from the provider - eventAdapter.addListener("ethEvent", wrapper._ethEventListener.bind(wrapper)); - return wrapper; -@@ -213,12 +177,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { - throw new errors_1.InvalidInputError("Hardhat Network doesn't support JSON-RPC params sent as an object"); - } - const params = args.params ?? []; -- if (args.method === "hardhat_addCompilationResult") { -- return this._addCompilationResultAction(...this._addCompilationResultParams(params)); -- } -- else if (args.method === "hardhat_getStackTraceFailuresCount") { -- return this._getStackTraceFailuresCountAction(...this._getStackTraceFailuresCountParams(params)); -- } - const stringifiedArgs = JSON.stringify({ - method: args.method, - params, -@@ -232,12 +190,10 @@ class EdrProviderWrapper extends events_1.EventEmitter { - response = responseObject.data; - } - const needsTraces = this._node._vm.evm.events.eventNames().length > 0 || -- this._node._vm.events.eventNames().length > 0 || -- this._vmTracer !== undefined; -+ this._node._vm.events.eventNames().length > 0; - if (needsTraces) { - const rawTraces = responseObject.traces; - for (const rawTrace of rawTraces) { -- this._vmTracer?.observe(rawTrace); - // For other consumers in JS we need to marshall the entire trace over FFI - const trace = rawTrace.trace(); - // beforeTx event -@@ -272,12 +228,8 @@ class EdrProviderWrapper extends events_1.EventEmitter { - } - if ((0, http_1.isErrorResponse)(response)) { - let error; -- const solidityTrace = responseObject.solidityTrace; -- let stackTrace; -- if (solidityTrace !== null) { -- stackTrace = await this._rawTraceToSolidityStackTrace(solidityTrace); -- } -- if (stackTrace !== undefined) { -+ const stackTrace = responseObject.stackTrace(); -+ if (stackTrace !== null) { - error = (0, solidity_errors_1.encodeSolidityStackTrace)(response.error.message, stackTrace); - // Pass data and transaction hash from the original error - error.data = response.error.data?.data ?? undefined; -@@ -315,14 +267,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { - return response.result; - } - } -- /** -- * Sets a `VMTracer` that observes EVM throughout requests. -- * -- * Used for internal stack traces integration tests. -- */ -- setVmTracer(vmTracer) { -- this._vmTracer = vmTracer; -- } - // temporarily added to make smock work with HH+EDR - _setCallOverrideCallback(callback) { - this._callOverrideCallback = callback; -@@ -357,50 +301,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { - }; - this.emit("message", message); - } -- _addCompilationResultParams(params) { -- return (0, validation_1.validateParams)(params, t.string, solc_1.rpcCompilerInput, solc_1.rpcCompilerOutput); -- } -- async _addCompilationResultAction(solcVersion, compilerInput, compilerOutput) { -- let bytecodes; -- try { -- bytecodes = (0, compiler_to_model_1.createModelsAndDecodeBytecodes)(solcVersion, compilerInput, compilerOutput); -- } -- catch (error) { -- console.warn(chalk_1.default.yellow("The Hardhat Network tracing engine could not be updated. Run Hardhat with --verbose to learn more.")); -- log("VmTraceDecoder failed to be updated. Please report this to help us improve Hardhat.\n", error); -- return false; -- } -- for (const bytecode of bytecodes) { -- this._vmTraceDecoder.addBytecode(bytecode); -- } -- return true; -- } -- _getStackTraceFailuresCountParams(params) { -- return (0, validation_1.validateParams)(params); -- } -- _getStackTraceFailuresCountAction() { -- return this._failedStackTraces; -- } -- async _rawTraceToSolidityStackTrace(rawTrace) { -- const vmTracer = new vm_tracer_1.VMTracer(); -- vmTracer.observe(rawTrace); -- let vmTrace = vmTracer.getLastTopLevelMessageTrace(); -- const vmTracerError = vmTracer.getLastError(); -- if (vmTrace !== undefined) { -- vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); -- } -- try { -- if (vmTrace === undefined || vmTracerError !== undefined) { -- throw vmTracerError; -- } -- const solidityTracer = new solidityTracer_1.SolidityTracer(); -- return solidityTracer.getStackTrace(vmTrace); -- } -- catch (err) { -- this._failedStackTraces += 1; -- log("Could not generate stack trace. Please report this to help us improve Hardhat.\n", err); -- } -- } - } - exports.EdrProviderWrapper = EdrProviderWrapper; - async function clientVersion(edrClientVersion) { -@@ -433,7 +333,7 @@ async function makeTracingConfig(artifacts) { - }; - } - catch (error) { -- console.warn(chalk_1.default.yellow("Stack traces engine could not be initialized. Run Hardhat with --verbose to learn more.")); -+ console.warn(picocolors_1.default.yellow("Stack traces engine could not be initialized. Run Hardhat with --verbose to learn more.")); - log("Solidity stack traces disabled: Failed to read solc's input and output files. Please report this to help us improve Hardhat.\n", error); - } - } diff --git a/patches/hardhat@2.22.17.patch b/patches/hardhat@2.22.17.patch new file mode 100644 index 000000000..4b53d4d67 --- /dev/null +++ b/patches/hardhat@2.22.17.patch @@ -0,0 +1,423 @@ +diff --git a/internal/cli/cli.js b/internal/cli/cli.js +index 74c6821a494cf9cf4e8ef5f04e629bb71b94bcea..95ef961445ef3d4ead59e96c68de1facdf8404fc 100644 +--- a/internal/cli/cli.js ++++ b/internal/cli/cli.js +@@ -186,7 +186,7 @@ async function main() { + } + const [abortAnalytics, hitPromise] = await analytics.sendTaskHit(scopeName, taskName); + let taskArguments; +- // --help is a also special case ++ // --help is an also special case + if (hardhatArguments.help && taskName !== task_names_1.TASK_HELP) { + // we "move" the task and scope names to the task arguments, + // and run the help task +diff --git a/internal/cli/cli.js.map b/internal/cli/cli.js.map +index 2594086e008aec98ee77db7d9f37cce5da6459e8..04df6c03bd5dc4facdd11ec021d71cb5e9601545 100644 +--- a/internal/cli/cli.js.map ++++ b/internal/cli/cli.js.map +@@ -1 +1 @@ +-{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/internal/cli/cli.ts"],"names":[],"mappings":"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"} +\ No newline at end of file ++{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/internal/cli/cli.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4DAAoC;AACpC,kDAA0B;AAC1B,uCAAqC;AAErC,+DAIwC;AAExC,4CAA4C;AAC5C,wCAA4C;AAC5C,kEAGuC;AACvC,2CAIwB;AACxB,qDAA2D;AAC3D,2DAA2E;AAC3E,gEAAsE;AACtE,kEAA0E;AAC1E,iEAGmC;AACnC,qEAA0D;AAC1D,mEAA+E;AAC/E,iDAA8C;AAC9C,uDAA2D;AAC3D,mDAI4B;AAC5B,qDAAqD;AACrD,mDAAoD;AACpD,2CAAiE;AACjE,uDAAoD;AACpD,mCAAsC;AACtC,yDAA8E;AAC9E,qCAAuD;AACvD,+EAIuC;AACvC,iCAAoC;AAEpC,MAAM,GAAG,GAAG,IAAA,eAAK,EAAC,kBAAkB,CAAC,CAAC;AAEtC,MAAM,6BAA6B,GAAG,GAAG,CAAC;AAC1C,MAAM,mCAAmC,GAAG,IAAA,kCAAmB,GAAE,CAAC;AAElE,KAAK,UAAU,mBAAmB;IAChC,MAAM,WAAW,GAAG,MAAM,IAAA,4BAAc,GAAE,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,8BAA8B;IAC3C,MAAM,eAAe,GAAG,IAAA,mCAAsB,GAAE,CAAC;IACjD,IAAI,eAAe,EAAE;QACnB,OAAO;KACR;IAED,MAAM,WAAW,GAAG,IAAA,sDAAwB,GAAE,CAAC;IAC/C,IAAA,qCAAwB,GAAE,CAAC;IAE3B,IAAI,WAAW,KAAK,+CAAiB,CAAC,uBAAuB,EAAE;QAC7D,OAAO;KACR;IAED,MAAM,mBAAmB,GAAG,MAAM,IAAA,oCAA2B,GAAE,CAAC;IAEhE,IAAI,mBAAmB,KAAK,IAAI,EAAE;QAChC,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC5D,MAAM,SAAS,GAAG,IAAA,kDAAoB,GAAE,CAAC;QAEzC,IAAI,SAAS,EAAE;YACb,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;SAC1E;aAAM;YACL,OAAO,CAAC,GAAG,CACT,wHAAwH,CACzH,CAAC;SACH;KACF;SAAM;QACL,OAAO,CAAC,GAAG,CACT,8FAA8F,CAC/F,CAAC;KACH;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,cAA6B;IACrD,MAAM,mBAAmB,GAAG,IAAA,uCAAsB,EAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC5E,MAAM,YAAY,GAAG,mBAAmB,CAAC,IAAI,CAC3C,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,KAAK,IAAI,CAChD,CAAC;IAEF,IAAI,YAAY,EAAE;QAChB,OAAO,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CACV,oBAAU,CAAC,MAAM,CACf;;6CAEqC,CACtC,CACF,CAAC;KACH;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,uEAAuE;IACvE,oDAAoD;IACpD,IAAI,eAAe,GACjB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QAC5C,mCAAmC,CAAC;IAEtC,IAAI;QACF,MAAM,oBAAoB,GAAG,IAAA,sCAAsB,EACjD,0CAAyB,EACzB,OAAO,CAAC,GAAG,CACZ,CAAC;QAEF,MAAM,eAAe,GAAG,IAAI,iCAAe,EAAE,CAAC;QAE9C,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,GAC1D,eAAe,CAAC,qBAAqB,CACnC,0CAAyB,EACzB,oBAAoB,EACpB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CACtB,CAAC;QAEJ,IAAI,gBAAgB,CAAC,OAAO,EAAE;YAC5B,mBAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC1B,eAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;SAC1B;QAED,IAAI,gBAAgB,CAAC,KAAK,EAAE;YAC1B,IAAA,mBAAW,GAAE,CAAC;SACf;QAED,eAAe,GAAG,gBAAgB,CAAC,eAAe,CAAC;QAEnD,8BAA8B;QAC9B,IAAI,gBAAgB,CAAC,OAAO,EAAE;YAC5B,MAAM,mBAAmB,EAAE,CAAC;YAC5B,OAAO;SACR;QAED,8BAA8B;QAC9B,yGAAyG;QACzG,qCAAqC;QACrC,+EAA+E;QAE/E,+BAA+B;QAC/B,IAAI,eAAe,KAAK,MAAM,EAAE;YAC9B,OAAO,MAAM,gBAAgB,EAAE,CAAC;SACjC;QACD,uCAAuC;aAClC;YACH,IACE,eAAe,KAAK,SAAS;gBAC7B,gBAAgB,CAAC,MAAM,KAAK,SAAS;gBACrC,CAAC,IAAA,sCAAkB,GAAE,EACrB;gBACA,MAAM,gBAAgB,EAAE,CAAC;gBAEzB,qCAAqC;gBACrC,OAAO,CAAC,IAAI,CACV,oBAAU,CAAC,MAAM,CAAC,oBAAU,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,EACjE,oBAAU,CAAC,MAAM,CACf,+BAA+B,oBAAU,CAAC,KAAK,CAC7C,oBAAU,CAAC,MAAM,CAAC,aAAa,CAAC,CACjC,qDAAqD,CACvD,EACD,oBAAU,CAAC,MAAM,CACf,cAAc,oBAAU,CAAC,KAAK,CAC5B,oBAAU,CAAC,MAAM,CAAC,kBAAkB,CAAC,CACtC,eAAe,CACjB,CACF,CAAC;gBAEF,OAAO;aACR;SACF;QACD,qCAAqC;QAErC,yEAAyE;QACzE,IAAI,gBAAgB,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,IAAA,sCAAkB,GAAE,EAAE;YAClE,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;SAC3D;QAED,IACE,OAAO,CAAC,GAAG,CAAC,iDAAiD;YAC3D,MAAM;YACR,CAAC,IAAA,kDAAiC,GAAE,EACpC;YACA,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;SAC/D;QAED,IAAI,IAAA,0CAAqB,EAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE;YAClD,IAAA,+BAAU,EAAC,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;SACnE;aAAM;YACL,IAAI,gBAAgB,CAAC,SAAS,KAAK,IAAI,EAAE;gBACvC,MAAM,IAAI,qBAAY,CACpB,oBAAM,CAAC,SAAS,CAAC,oCAAoC,CACtD,CAAC;aACH;SACF;QAED,MAAM,GAAG,GAAG,wBAAc,CAAC,oBAAoB,EAAE,CAAC;QAElD,IAAI,eAAe,KAAK,MAAM,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE;YAC5D,OAAO,CAAC,IAAI,CAAC,MAAM,IAAA,iBAAU,EAAC,eAAe,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;SAC1E;QAED,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,GAAG,IAAA,mCAAkB,EACvD,gBAAgB,EAChB;YACE,sBAAsB,EAAE,IAAI;YAC5B,0BAA0B,EAAE,eAAe,KAAK,yBAAY;SAC7D,CACF,CAAC;QAEF,MAAM,YAAY,GAAG,GAAG,CAAC,oBAAoB,CAAC;QAC9C,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,CAAC;QAChD,MAAM,eAAe,GAAG,GAAG,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC;QAC1D,MAAM,iBAAiB,GAAG,GAAG,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC;QAE9D,MAAM,GAAG,GAAG,IAAI,iCAAW,CACzB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,iBAAiB,CAClB,CAAC;QAEF,GAAG,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC;QAEtC,wCAAwC;QACxC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,GACvC,eAAe,CAAC,sBAAsB,CACpC,eAAe,EACf,eAAe,EACf,iBAAiB,CAClB,CAAC;QAEJ,IAAI,gBAAgB,GAAwB,IAAA,kCAAqB,GAAE,CAAC;QAEpE,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,IAAI,QAAQ,KAAK,sBAAS,CAAC;QACtE,IACE,gBAAgB,KAAK,SAAS;YAC9B,CAAC,aAAa;YACd,CAAC,IAAA,kCAAmB,GAAE;YACtB,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;YAC7B,OAAO,CAAC,GAAG,CAAC,gCAAgC,KAAK,MAAM,EACvD;YACA,gBAAgB,GAAG,MAAM,IAAA,mCAAuB,GAAE,CAAC;SACpD;QAED,MAAM,SAAS,GAAG,MAAM,qBAAS,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;QAEhE,mBAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,gBAAgB,KAAK,IAAI,EAAE;YAC7B,mBAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;SAC3B;QAED,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,GAAG,MAAM,SAAS,CAAC,WAAW,CAC9D,SAAS,EACT,QAAQ,CACT,CAAC;QAEF,IAAI,aAA4B,CAAC;QAEjC,iCAAiC;QACjC,IAAI,gBAAgB,CAAC,IAAI,IAAI,QAAQ,KAAK,sBAAS,EAAE;YACnD,4DAA4D;YAC5D,wBAAwB;YACxB,IAAI,SAAS,KAAK,SAAS,EAAE;gBAC3B,aAAa,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;aAC5D;iBAAM;gBACL,aAAa,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;aAC3C;YACD,QAAQ,GAAG,sBAAS,CAAC;YACrB,SAAS,GAAG,SAAS,CAAC;SACvB;aAAM;YACL,MAAM,cAAc,GAAG,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CACnD,SAAS,EACT,QAAQ,CACT,CAAC;YAEF,IAAI,cAAc,KAAK,SAAS,EAAE;gBAChC,IAAI,SAAS,KAAK,SAAS,EAAE;oBAC3B,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,SAAS,CAAC,wBAAwB,EAAE;wBAChE,KAAK,EAAE,SAAS;wBAChB,IAAI,EAAE,QAAQ;qBACf,CAAC,CAAC;iBACJ;gBACD,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,SAAS,CAAC,iBAAiB,EAAE;oBACzD,IAAI,EAAE,QAAQ;iBACf,CAAC,CAAC;aACJ;YAED,IAAI,cAAc,CAAC,SAAS,EAAE;gBAC5B,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,SAAS,CAAC,wBAAwB,EAAE;oBAChE,IAAI,EAAE,cAAc,CAAC,IAAI;iBAC1B,CAAC,CAAC;aACJ;YAED,aAAa,GAAG,eAAe,CAAC,kBAAkB,CAChD,cAAc,EACd,YAAY,CACb,CAAC;SACH;QAED,IAAI;YACF,MAAM,kBAAkB,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAEhD,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,aAAa,CAAC,CAAC;YAEnE,MAAM,iBAAiB,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAE/C,IACE,iBAAiB,GAAG,kBAAkB;gBACpC,6BAA6B;gBAC/B,QAAQ,KAAK,yBAAY,EACzB;gBACA,MAAM,UAAU,CAAC;aAClB;iBAAM;gBACL,cAAc,EAAE,CAAC;aAClB;SACF;gBAAS;YACR,IAAI,gBAAgB,CAAC,UAAU,KAAK,IAAI,EAAE;gBACxC,IAAA,+BAAsB,EACpB,GAAG,CAAC,gBAAgB,KAAK,SAAS,EAClC,0DAA0D,CAC3D,CAAC;gBAEF,MAAM,cAAc,GAAG,IAAA,2BAAc,EAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAC5D,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,cAAc,CAAC,CAAC;aACxD;SACF;QAED,2CAA2C;QAC3C,IACE,QAAQ,KAAK,sBAAS;YACtB,CAAC,IAAA,kCAAmB,GAAE;YACtB,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,EAC7B;YACA,MAAM,8BAA8B,EAAE,CAAC;YAEvC,mEAAmE;YACnE,kBAAkB;YAClB,IACE,OAAO,CAAC,QAAQ,KAAK,CAAC;gBACtB,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM;gBACtB,OAAO,CAAC,GAAG,CAAC,oCAAoC,KAAK,MAAM,EAC3D;gBACA,IAAA,4CAAyB,GAAE,CAAC;aAC7B;YAED,qDAAqD;YACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,EAAE;gBAC1B,gBAAgB,CAAC,cAAc,CAAC,CAAC;aAClC;YAED,qDAAqD;YACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC,EAAE;gBAC1B,IAAI;oBACF,MAAM,EAAE,0BAA0B,EAAE,GAAG,wDACrC,oBAAoB,GACrB,CAAC;oBACF,MAAM,0BAA0B,EAAE,CAAC;iBACpC;gBAAC,MAAM;oBACN,0CAA0C;iBAC3C;aACF;SACF;QAED,GAAG,CAAC,mDAAmD,QAAQ,EAAE,CAAC,CAAC;KACpE;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,cAAc,GAAG,KAAK,CAAC;QAE3B,IAAI,qBAAY,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE;YACtC,cAAc,GAAG,IAAI,CAAC;YACtB,OAAO,CAAC,KAAK,CACX,oBAAU,CAAC,GAAG,CAAC,oBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EACxC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CACnC,oBAAU,CAAC,GAAG,CAAC,oBAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CACnC,CACF,CAAC;SACH;aAAM,IAAI,2BAAkB,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE;YACzD,cAAc,GAAG,IAAI,CAAC;YACtB,OAAO,CAAC,KAAK,CACX,oBAAU,CAAC,GAAG,CAAC,oBAAU,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,EACvE,KAAK,CAAC,OAAO,CACd,CAAC;SACH;aAAM,IAAI,KAAK,YAAY,KAAK,EAAE;YACjC,OAAO,CAAC,KAAK,CAAC,oBAAU,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;YAC/D,eAAe,GAAG,IAAI,CAAC;SACxB;aAAM;YACL,OAAO,CAAC,KAAK,CAAC,oBAAU,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;YAC/D,eAAe,GAAG,IAAI,CAAC;SACxB;QAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,IAAI;YACF,mBAAQ,CAAC,WAAW,CAAC,KAAc,CAAC,CAAC;SACtC;QAAC,OAAO,CAAC,EAAE;YACV,GAAG,CAAC,qCAAqC,EAAE,CAAC,CAAC,CAAC;SAC/C;QAED,IAAI,eAAe,IAAI,mCAAmC,EAAE;YAC1D,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;SACtB;aAAM;YACL,IAAI,CAAC,cAAc,EAAE;gBACnB,OAAO,CAAC,KAAK,CACX,8FAA8F,CAC/F,CAAC;aACH;YAED,IAAI,qBAAY,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE;gBACtC,MAAM,IAAI,GAAG,uBAAuB,IAAA,0BAAY,EAC9C,KAAK,CAAC,eAAe,CACtB,EAAE,CAAC;gBAEJ,OAAO,CAAC,KAAK,CACX,uBAAuB,IAAI,WAAW,wBAAY,2BAA2B,CAC9E,CAAC;aACH;iBAAM;gBACL,OAAO,CAAC,KAAK,CACX,qBAAqB,wBAAY,2BAA2B,CAC7D,CAAC;aACH;SACF;QAED,MAAM,mBAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB;IAC7B,IAAI,IAAA,sCAAkB,GAAE,EAAE;QACxB,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,+BAA+B,EAAE;YACrE,sBAAsB,EAAE,IAAA,qCAAiB,GAAE;SAC5C,CAAC,CAAC;KACJ;IAED,IACE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;QAC7B,OAAO,CAAC,GAAG,CAAC,+CAA+C,KAAK,SAAS;QACzE,OAAO,CAAC,GAAG,CAAC,+CAA+C,KAAK,SAAS;QACzE,OAAO,CAAC,GAAG,CAAC,oDAAoD;YAC9D,SAAS,EACX;QACA,MAAM,IAAA,gCAAa,GAAE,CAAC;QACtB,OAAO;KACR;IAED,qEAAqE;IACrE,oEAAoE;IACpE,wEAAwE;IACxE,wCAAwC;IACxC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE;QAChC,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;KACtE;IAED,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAClE,CAAC;AAED,IAAI,EAAE;KACH,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;KAC1C,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"} +\ No newline at end of file +diff --git a/internal/cli/is-node-version-to-warn-on.d.ts b/internal/cli/is-node-version-to-warn-on.d.ts +index 89c25b7444f4cdbd04073671edc79158b5deca04..8a2d953adea8f1badee31a2a4e4e94b115022831 100644 +--- a/internal/cli/is-node-version-to-warn-on.d.ts ++++ b/internal/cli/is-node-version-to-warn-on.d.ts +@@ -2,7 +2,7 @@ + * Determine if the node version should trigger an unsupported + * warning. + * +- * The current rule is that a unsupported warning will be shown if ++ * The current rule is that an unsupported warning will be shown if + * + * 1. An odd numbered version of Node.js is used - as this will never go to LTS + * 2. The version is less than the minimum supported version +diff --git a/internal/cli/is-node-version-to-warn-on.js b/internal/cli/is-node-version-to-warn-on.js +index 50b3b0ed528aaccbc395afebb5e100cf330a1156..a1cb1a710002c2d5ae3cd0360aff2a1fe971a6eb 100644 +--- a/internal/cli/is-node-version-to-warn-on.js ++++ b/internal/cli/is-node-version-to-warn-on.js +@@ -11,7 +11,7 @@ const constants_1 = require("./constants"); + * Determine if the node version should trigger an unsupported + * warning. + * +- * The current rule is that a unsupported warning will be shown if ++ * The current rule is that an unsupported warning will be shown if + * + * 1. An odd numbered version of Node.js is used - as this will never go to LTS + * 2. The version is less than the minimum supported version +diff --git a/internal/core/config/config-validation.js b/internal/core/config/config-validation.js +index f4347c24c6b1231e3267c15a64c2d08b0ac2c52d..a2d26859c985540f3e4d419d088fd785d0050638 100644 +--- a/internal/core/config/config-validation.js ++++ b/internal/core/config/config-validation.js +@@ -263,7 +263,7 @@ function getValidationErrors(config) { + const hardhatNetwork = config.networks[constants_1.HARDHAT_NETWORK_NAME]; + if (hardhatNetwork !== undefined && typeof hardhatNetwork === "object") { + if ("url" in hardhatNetwork) { +- errors.push(`HardhatConfig.networks.${constants_1.HARDHAT_NETWORK_NAME} can't have an url`); ++ errors.push(`HardhatConfig.networks.${constants_1.HARDHAT_NETWORK_NAME} can't have a url`); + } + // Validating the accounts with io-ts leads to very confusing errors messages + const { accounts, ...configExceptAccounts } = hardhatNetwork; +diff --git a/internal/core/config/config-validation.js.map b/internal/core/config/config-validation.js.map +index 0ba2b34c8d4d3a825e37a8390783f14df2d26fba..fd7e6afdf3205d666481931fb8ea835ca58d815d 100644 +--- a/internal/core/config/config-validation.js.map ++++ b/internal/core/config/config-validation.js.map +@@ -1 +1 @@ +-{"version":3,"file":"config-validation.js","sourceRoot":"","sources":["../../../src/internal/core/config/config-validation.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,yCAA2B;AAE3B,+CAIyB;AACzB,4CAA4C;AAC5C,0CAA8C;AAC9C,sCAAyC;AACzC,gDAAwC;AACxC,oDAAiE;AAEjE,qDAA+D;AAE/D,SAAS,SAAS,CAAC,CAAM;IACvB,IAAI,OAAO,CAAC,KAAK,UAAU,EAAE;QAC3B,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,WAAW,CAE9C,CAAC;QACF,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC;KAC3B;IACD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;QACzC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;YACZ,OAAO,KAAK,CAAC;SACd;QACD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC;KACzC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,cAAc,CAAC,OAAgB;IACtC,MAAM,QAAQ,GAAG,OAAO;SACrB,KAAK,CAAC,CAAC,CAAC;SACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;SACjB,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU,CAAC,CAAkB;IACpC,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEpD,OAAO,CAAC,CAAC,OAAO,KAAK,SAAS;QAC5B,CAAC,CAAC,CAAC,CAAC,OAAO;QACX,CAAC,CAAC,eAAe,CACb,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,EACzB,CAAC,CAAC,KAAK,EACP,WAAW,CAAC,IAAI,CAAC,IAAI,CACtB,CAAC;AACR,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,KAAU,EAAE,YAAoB;IACrE,OAAO,iBAAiB,SAAS,CAC/B,KAAK,CACN,QAAQ,IAAI,+BAA+B,YAAY,GAAG,CAAC;AAC9D,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa,EAAE,OAAe,EAAE,OAAe;IACzE,OAAO,qBAAqB,KAAK,iBAAiB,OAAO,MAAM,OAAO,EAAE,CAAC;AAC3E,CAAC;AAED,SAAS,kBAAkB,CACzB,UAAmB,EACnB,KAAa,EACb,OAAe,EACf,MAAgB;IAEhB,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;QAClC,MAAM,CAAC,IAAI,CACT,kBAAkB,CAChB,KAAK,EACL,OAAO,EACP,6BAA6B,OAAO,UAAU,EAAE,CACjD,CACF,CAAC;KACH;SAAM;QACL,yBAAyB;QACzB,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;YACzC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;QAEtB,gDAAgD;QAChD,IAAI,YAAY,CAAC,MAAM,GAAG,EAAE,EAAE;YAC5B,MAAM,CAAC,IAAI,CACT,kBAAkB,CAChB,KAAK,EACL,OAAO,EACP,0CAA0C,CAC3C,CACF,CAAC;SACH;aAAM,IAAI,YAAY,CAAC,MAAM,GAAG,EAAE,EAAE;YACnC,MAAM,CAAC,IAAI,CACT,kBAAkB,CAChB,KAAK,EACL,OAAO,EACP,yCAAyC,CAC1C,CACF,CAAC;SACH;aAAM,IAAI,iBAAS,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;YAClD,MAAM,CAAC,IAAI,CACT,kBAAkB,CAChB,KAAK,EACL,OAAO,EACP,0CAA0C,CAC3C,CACF,CAAC;SACH;KACF;AACH,CAAC;AAED,SAAgB,OAAO,CAAC,EAAqB;IAC3C,OAAO,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAC5B,CAAC;AAFD,0BAEC;AAED,SAAgB,OAAO;IACrB,OAAO,EAAE,CAAC;AACZ,CAAC;AAFD,0BAEC;AAEY,QAAA,eAAe,GAAuB;IACjD,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1D,CAAC;AAEF,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AACnD,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAE9C,SAAS,WAAW,CAAC,CAAU;IAC7B,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;QACzB,OAAO,KAAK,CAAC;KACd;IAED,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;AACnD,CAAC;AAED,SAAS,eAAe,CAAC,CAAU;IACjC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;QACzB,OAAO,KAAK,CAAC;KACd;IAED,OAAO,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;AAC5C,CAAC;AAEY,QAAA,SAAS,GAAG,IAAI,CAAC,CAAC,IAAI,CACjC,YAAY,EACZ,WAAW,EACX,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAC3D,CAAC,CAAC,QAAQ,CACX,CAAC;AAEF,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;QACzB,OAAO,KAAK,CAAC;KACd;IAED,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAEzB,OAAO,CACL,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,IAAI;QACxC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QACxB,OAAO,CAAC,MAAM,KAAK,EAAE,CACtB,CAAC;AACJ,CAAC;AAEY,QAAA,OAAO,GAAG,IAAI,CAAC,CAAC,IAAI,CAC/B,SAAS,EACT,SAAS,EACT,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EACzD,CAAC,CAAC,QAAQ,CACX,CAAC;AAEW,QAAA,aAAa,GAAG,IAAI,CAAC,CAAC,IAAI,CACrC,gBAAgB,EAChB,eAAe,EACf,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAC/D,CAAC,CAAC,QAAQ,CACX,CAAC;AACF,gFAAgF;AAChF,sEAAsE;AAEtE,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC;IACnC,UAAU,EAAE,iBAAS;IACrB,OAAO,EAAE,qBAAa;CACvB,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG;IAC7B,YAAY,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAChC,KAAK,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IACzB,IAAI,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,8BAA8B,GAAG,CAAC,CAAC,IAAI,CAAC;IAC5C,QAAQ,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC5B,eAAe,EAAE,IAAA,gBAAQ,EAAC,qBAAa,CAAC;IACxC,UAAU,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC9B,GAAG,sBAAsB;CAC1B,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,IAAI,CACxB,SAAS,EACT,CAAC,GAAY,EAAiB,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,EACxD,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;IACP,IAAI;QACF,OAAO,OAAO,CAAC,KAAK,QAAQ;YAC1B,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;KACrB;IAAC,MAAM;QACN,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;KACxB;AACH,CAAC,EACD,CAAC,CAAC,QAAQ,CACX,CAAC;AAEF,MAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,CAAC;IACzC,OAAO,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC;IAC5B,GAAG,EAAE,CAAC,CAAC,MAAM;IACb,WAAW,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;CAChC,CAAC,CAAC;AAEH,MAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,CAAC;IACzC,KAAK,EAAE,IAAA,gBAAQ,EACb,CAAC,CAAC,KAAK,CACL,IAAA,kBAAW,EACT,4CAAgC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAC/D,CACF,CACF;CACF,CAAC,CAAC;AAEH,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC;IACxC,IAAI,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC;IACzB,QAAQ,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,EAAE,IAAA,gBAAQ,EAAC,2BAA2B,CAAC;CAC/C,CAAC,CAAC;AAEH,SAAS,mBAAmB,CAAC,IAAY;IACvC,OAAO,MAAM,CAAC,MAAM,CAAC,wBAAY,CAAC,CAAC,QAAQ,CAAC,IAAoB,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,CAAC,IAAI,CACjC,MAAM,CAAC,MAAM,CAAC,wBAAY,CAAC;KACxB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;KACpB,IAAI,CAAC,KAAK,CAAC,EACd,CAAC,IAAa,EAAwB,EAAE,CACtC,OAAO,IAAI,KAAK,QAAQ,IAAI,mBAAmB,CAAC,IAAI,CAAC,EACvD,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;IACP,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,mBAAmB,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAiB,CAAC;QAC9B,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACtB,CAAC,EACD,CAAC,CAAC,QAAQ,CACX,CAAC;AAEF,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAC5C,gBAAgB,EAChB,CAAC,CAAC,MAAM,EACR,+BAA+B,CAChC,CAAC;AAEF,MAAM,yBAAyB,GAAG,CAAC,CAAC,IAAI,CAAC;IACvC,eAAe,EAAE,6BAA6B;CAC/C,CAAC,CAAC;AAEH,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;AAEhF,MAAM,yBAAyB,GAAG;IAChC,OAAO,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC3B,IAAI,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IACxB,GAAG,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,QAAQ,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,aAAa,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC;IAClC,GAAG,yBAAyB;IAC5B,QAAQ,EAAE,IAAA,gBAAQ,EAChB,CAAC,CAAC,KAAK,CACL,IAAA,kBAAW,EAAC,+CAAmC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CACzE,CACF;IACD,QAAQ,EAAE,IAAA,gBAAQ,EAChB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,8BAA8B,CAAC,CAAC,CAC1E;IACD,aAAa,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IACjC,WAAW,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,0BAA0B,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC;IAC/C,mBAAmB,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC;IACxC,0BAA0B,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC;IAC/C,WAAW,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC/B,cAAc,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC;IACnC,OAAO,EAAE,IAAA,gBAAQ,EAAC,2BAA2B,CAAC;IAC9C,MAAM,EAAE,IAAA,gBAAQ,EAAC,0BAA0B,CAAC;IAC5C,QAAQ,EAAE,IAAA,gBAAQ,EAAC,eAAO,CAAC;IAC3B,MAAM,EAAE,IAAA,gBAAQ,EAAC,0BAA0B,CAAC;CAC7C,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,CAAC,MAAM;IAClB,UAAU,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC9B,GAAG,sBAAsB;CAC1B,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC;IACpC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACnB,CAAC,CAAC,KAAK,CAAC,iBAAS,CAAC;IAClB,gBAAgB;CACjB,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEhE,MAAM,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC/B,GAAG,yBAAyB;IAC5B,GAAG,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IACvB,QAAQ,EAAE,IAAA,gBAAQ,EAAC,qBAAqB,CAAC;IACzC,WAAW,EAAE,IAAA,gBAAQ,EAAC,WAAW,CAAC;IAClC,OAAO,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;CAC5B,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAEzE,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEnD,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC;IAC1B,IAAI,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IACxB,KAAK,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IACzB,SAAS,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC7B,OAAO,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAC3B,KAAK,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,QAAQ,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,GAAG,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC;IAC7B,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;IACpC,SAAS,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC1D,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,gBAAgB,EAAE,eAAe,CAAC,CAAC,CAAC;AAE9E,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAC1B;IACE,cAAc,EAAE,IAAA,gBAAQ,EAAC,CAAC,CAAC,MAAM,CAAC;IAClC,QAAQ,EAAE,IAAA,gBAAQ,EAAC,QAAQ,CAAC;IAC5B,KAAK,EAAE,IAAA,gBAAQ,EAAC,YAAY,CAAC;IAC7B,QAAQ,EAAE,IAAA,gBAAQ,EAAC,cAAc,CAAC;CACnC,EACD,eAAe,CAChB,CAAC;AAEF;;;GAGG;AACH,SAAgB,cAAc,CAAC,MAAW;IACxC,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE3C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE;QACvB,OAAO;KACR;IAED,IAAI,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtC,SAAS,GAAG,OAAO,SAAS,EAAE,CAAC;IAE/B,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AAC/E,CAAC;AAXD,wCAWC;AAED,SAAgB,mBAAmB,CAAC,MAAW;IAC7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,sCAAsC;IACtC,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE;QAC/D,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,gCAAoB,CAAC,CAAC;QAC7D,IAAI,cAAc,KAAK,SAAS,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE;YACtE,IAAI,KAAK,IAAI,cAAc,EAAE;gBAC3B,MAAM,CAAC,IAAI,CACT,0BAA0B,gCAAoB,oBAAoB,CACnE,CAAC;aACH;YAED,6EAA6E;YAC7E,MAAM,EAAE,QAAQ,EAAE,GAAG,oBAAoB,EAAE,GAAG,cAAc,CAAC;YAE7D,MAAM,eAAe,GAAG,oBAAoB,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC1E,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE;gBAC5B,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,gCAAoB,EAAE,EAChD,cAAc,EACd,sBAAsB,CACvB,CACF,CAAC;aACH;YAED,gCAAgC;YAChC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAC3B,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE;oBACjD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;wBAC/B,MAAM,CAAC,IAAI,CACT,kBAAkB,CAChB,KAAK,EACL,gCAAoB,EACpB,6BAA6B,OAAO,OAAO,EAAE,CAC9C,CACF,CAAC;wBACF,SAAS;qBACV;oBAED,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;oBAExC,kBAAkB,CAAC,UAAU,EAAE,KAAK,EAAE,gCAAoB,EAAE,MAAM,CAAC,CAAC;oBAEpE,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;wBAC/B,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,gCAAoB,qBAAqB,EACnE,OAAO,EACP,QAAQ,CACT,CACF,CAAC;qBACH;yBAAM,IAAI,qBAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;wBACjD,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,gCAAoB,qBAAqB,EACnE,OAAO,EACP,cAAc,CACf,CACF,CAAC;qBACH;iBACF;aACF;iBAAM,IAAI,OAAO,cAAc,CAAC,QAAQ,KAAK,QAAQ,EAAE;gBACtD,MAAM,cAAc,GAAG,8BAA8B,CAAC,MAAM,CAC1D,cAAc,CAAC,QAAQ,CACxB,CAAC;gBACF,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE;oBAC3B,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,gCAAoB,WAAW,EACzD,cAAc,CAAC,QAAQ,EACvB,sFAAsF,CACvF,CACF,CAAC;iBACH;aACF;iBAAM,IAAI,cAAc,CAAC,QAAQ,KAAK,SAAS,EAAE;gBAChD,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,gCAAoB,WAAW,EACzD,cAAc,CAAC,QAAQ,EACvB,sFAAsF,CACvF,CACF,CAAC;aACH;YAED,MAAM,QAAQ,GACZ,cAAc,CAAC,QAAQ,IAAI,4CAA2B,CAAC,QAAQ,CAAC;YAClE,IAAI,IAAA,uBAAW,EAAC,QAAQ,EAAE,wBAAY,CAAC,MAAM,CAAC,EAAE;gBAC9C,IAAI,cAAc,CAAC,WAAW,KAAK,SAAS,EAAE;oBAC5C,MAAM,CAAC,IAAI,CACT,4CAA4C,gCAAoB,8GAA8G,CAC/K,CAAC;iBACH;aACF;iBAAM;gBACL,IAAI,cAAc,CAAC,oBAAoB,KAAK,SAAS,EAAE;oBACrD,MAAM,CAAC,IAAI,CACT,4CAA4C,gCAAoB,uHAAuH,CACxL,CAAC;iBACH;aACF;YAED,IAAI,cAAc,CAAC,MAAM,KAAK,SAAS,EAAE;gBACvC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE;oBAC3D,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,GAAG,UAG9B,CAAC;oBACF,MAAM,EAAE,eAAe,EAAE,GAAG,WAAW,CAAC;oBACxC,IAAI,eAAe,KAAK,SAAS,EAAE;wBACjC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE;4BACpD,IAAI,CAAC,+CAAmC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE;gCAC/D,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,gCAAoB,WAAW,OAAO,mBAAmB,EACnF,YAAY,EACZ,IAAI,+CAAmC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CACzD,CACF,CAAC;6BACH;wBACH,CAAC,CAAC,CAAC;qBACJ;gBACH,CAAC,CAAC,CAAC;aACJ;YAED,IAAI,cAAc,CAAC,QAAQ,KAAK,SAAS,EAAE;gBACzC,IACE,CAAC,IAAA,uBAAW,EAAC,cAAc,CAAC,QAAQ,EAAE,wBAAY,CAAC,MAAM,CAAC;oBAC1D,cAAc,CAAC,sBAAsB,KAAK,IAAI,EAC9C;oBACA,MAAM,CAAC,IAAI,CACT,yKAAyK,CAC1K,CAAC;iBACH;gBACD,IACE,IAAA,uBAAW,EAAC,cAAc,CAAC,QAAQ,EAAE,wBAAY,CAAC,MAAM,CAAC;oBACzD,cAAc,CAAC,sBAAsB,KAAK,KAAK,EAC/C;oBACA,MAAM,CAAC,IAAI,CACT,6KAA6K,CAC9K,CAAC;iBACH;aACF;SACF;QAED,KAAK,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CACnD,MAAM,CAAC,QAAQ,CAChB,EAAE;YACD,IAAI,WAAW,KAAK,gCAAoB,EAAE;gBACxC,SAAS;aACV;YAED,IAAI,WAAW,KAAK,WAAW,IAAI,SAAS,CAAC,GAAG,KAAK,SAAS,EAAE;gBAC9D,IAAI,OAAO,SAAS,CAAC,GAAG,KAAK,QAAQ,EAAE;oBACrC,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,WAAW,MAAM,EAC3C,SAAS,CAAC,GAAG,EACb,QAAQ,CACT,CACF,CAAC;iBACH;aACF;YAED,MAAM,EAAE,QAAQ,EAAE,GAAG,oBAAoB,EAAE,GAAG,SAAS,CAAC;YAExD,MAAM,eAAe,GAAG,iBAAiB,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;YACvE,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE;gBAC5B,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,WAAW,EAAE,EACvC,SAAS,EACT,mBAAmB,CACpB,CACF,CAAC;aACH;YAED,gCAAgC;YAChC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAC3B,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,CACrC,kBAAkB,CAAC,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,CAC3D,CAAC;aACH;iBAAM,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE;gBACvC,MAAM,cAAc,GAAG,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE;oBAC3B,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,WAAW,EAAE,EACvC,QAAQ,EACR,6BAA6B,CAC9B,CACF,CAAC;iBACH;aACF;iBAAM,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE;gBACvC,IAAI,QAAQ,KAAK,QAAQ,EAAE;oBACzB,MAAM,CAAC,IAAI,CACT,yCAAyC,WAAW,gFAAgF,QAAQ,GAAG,CAChJ,CAAC;iBACH;aACF;iBAAM,IAAI,QAAQ,KAAK,SAAS,EAAE;gBACjC,MAAM,CAAC,IAAI,CACT,eAAe,CACb,0BAA0B,WAAW,WAAW,EAChD,QAAQ,EACR,+DAA+D,CAChE,CACF,CAAC;aACH;SACF;KACF;IAED,uEAAuE;IACvE,iFAAiF;IACjF,iCAAiC;IACjC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE;QACrB,OAAO,MAAM,CAAC;KACf;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAE5C,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE;QACpB,OAAO,MAAM,CAAC;KACf;IAED,MAAM,UAAU,GAAG,uBAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC;AACpC,CAAC;AAlOD,kDAkOC;AAED,SAAgB,sBAAsB,CAAC,cAA8B;IACnE,MAAM,WAAW,GAAG;QAClB,GAAG,cAAc,CAAC,QAAQ,CAAC,SAAS;QACpC,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC;KACpD,CAAC;IACF,MAAM,IAAI,GAAG,WAAW;SACrB,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,KAAK,SAAS,CAAC;SACjE,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAEpD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE;QACtB,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE;YAClB,MAAM,IAAI,qBAAY,CAAC,oBAAM,CAAC,OAAO,CAAC,cAAc,EAAE;gBACpD,MAAM,EAAE,+DAA+D;aACxE,CAAC,CAAC;SACJ;KACF;AACH,CAAC;AAhBD,wDAgBC"} +\ No newline at end of file ++{"version":3,"file":"config-validation.js","sourceRoot":"","sources":["../../../src/internal/core/config/config-validation.ts"],"names":[],"mappings":"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"} +\ No newline at end of file +diff --git a/internal/core/typescript-support.d.ts b/internal/core/typescript-support.d.ts +index 5a7ddebf3bd2af7e6909d4554c71b755d8dfc71d..c45d01fa158ed510e718998850ff018d7dcef5b5 100644 +--- a/internal/core/typescript-support.d.ts ++++ b/internal/core/typescript-support.d.ts +@@ -5,7 +5,7 @@ import { HardhatConfig } from "../../types"; + */ + export declare function willRunWithTypescript(configPath?: string): boolean; + /** +- * Returns true if an Hardhat is already running with typescript. ++ * Returns true if a Hardhat is already running with typescript. + */ + export declare function isRunningWithTypescript(config: HardhatConfig): boolean; + export declare function isTypescriptSupported(): boolean; +diff --git a/internal/core/typescript-support.js b/internal/core/typescript-support.js +index a92ee83941ff419de44719fe8cf426d3b4d677cf..eddde07c6cd10549e286a0497b2dd28631840608 100644 +--- a/internal/core/typescript-support.js ++++ b/internal/core/typescript-support.js +@@ -16,7 +16,7 @@ function willRunWithTypescript(configPath) { + } + exports.willRunWithTypescript = willRunWithTypescript; + /** +- * Returns true if an Hardhat is already running with typescript. ++ * Returns true if a Hardhat is already running with typescript. + */ + function isRunningWithTypescript(config) { + return isNonEsmTypescriptFile(config.paths.configFile); +diff --git a/internal/hardhat-network/provider/provider.d.ts b/internal/hardhat-network/provider/provider.d.ts +index 970671b4888c4739ce919cfb48c724b0c503b49f..32e7dd9c82f47f72970c2d8a484c86e45baeb3f9 100644 +--- a/internal/hardhat-network/provider/provider.d.ts ++++ b/internal/hardhat-network/provider/provider.d.ts +@@ -1,6 +1,6 @@ + /// + import type { Artifacts, EIP1193Provider, HardhatNetworkChainsConfig, RequestArguments } from "../../../types"; +-import type { EdrContext, VMTracer as VMTracerT } from "@nomicfoundation/edr"; ++import type { EdrContext } from "@nomicfoundation/edr"; + import { EventEmitter } from "events"; + import { ForkConfig, GenesisAccount, IntervalMiningConfig, MempoolOrder, NodeConfig, TracingConfig } from "./node-types"; + import { LoggerConfig } from "./modules/logger"; +@@ -33,31 +33,17 @@ export declare function getNodeConfig(config: HardhatNetworkProviderConfig, trac + export declare class EdrProviderWrapper extends EventEmitter implements EIP1193Provider { + private readonly _provider; + private readonly _node; +- private readonly _vmTraceDecoder; + private readonly _common; + private _failedStackTraces; + private _callOverrideCallback?; +- /** Used for internal stack trace tests. */ +- private _vmTracer?; + private constructor(); + static create(config: HardhatNetworkProviderConfig, loggerConfig: LoggerConfig, tracingConfig?: TracingConfig): Promise; + request(args: RequestArguments): Promise; +- /** +- * Sets a `VMTracer` that observes EVM throughout requests. +- * +- * Used for internal stack traces integration tests. +- */ +- setVmTracer(vmTracer?: VMTracerT): void; + private _setCallOverrideCallback; + private _setVerboseTracing; + private _ethEventListener; + private _emitLegacySubscriptionEvent; + private _emitEip1193SubscriptionEvent; +- private _addCompilationResultParams; +- private _addCompilationResultAction; +- private _getStackTraceFailuresCountParams; +- private _getStackTraceFailuresCountAction; +- private _rawTraceToSolidityStackTrace; + } + export declare function createHardhatNetworkProvider(hardhatNetworkProviderConfig: HardhatNetworkProviderConfig, loggerConfig: LoggerConfig, artifacts?: Artifacts): Promise; + export {}; +diff --git a/internal/hardhat-network/provider/provider.d.ts.map b/internal/hardhat-network/provider/provider.d.ts.map +index 9df47bce20ca6de26d13a2616bfce48eea655c67..a5e4a60cdb308c82a42bcc22dd045821a2ef535e 100644 +--- a/internal/hardhat-network/provider/provider.d.ts.map ++++ b/internal/hardhat-network/provider/provider.d.ts.map +@@ -1 +1 @@ +-{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/internal/hardhat-network/provider/provider.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,SAAS,EAGT,eAAe,EAEf,0BAA0B,EAC1B,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EACV,UAAU,EAGV,QAAQ,IAAI,SAAS,EAKtB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAmCtC,OAAO,EACL,UAAU,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,UAAU,EACV,aAAa,EACd,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAE,YAAY,EAA8B,MAAM,kBAAkB,CAAC;AAO5E,eAAO,MAAM,gBAAgB,+CAA+C,CAAC;AAI7E,wBAAgB,mBAAmB,IAAI,UAAU,CAWhD;AAED,UAAU,4BAA4B;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,oBAAoB,CAAC;IACrC,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,0BAA0B,CAAC;IACnC,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,0BAA0B,EAAE,OAAO,CAAC;IACpC,0BAA0B,EAAE,OAAO,CAAC;IACpC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,4BAA4B,EAAE,OAAO,CAAC;IAEtC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,4BAA4B,EACpC,aAAa,CAAC,EAAE,aAAa,GAC5B,UAAU,CAsBZ;AAWD,qBAAa,kBACX,SAAQ,YACR,YAAW,eAAe;IAWxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAE1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAGtB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhB1B,OAAO,CAAC,kBAAkB,CAAK;IAG/B,OAAO,CAAC,qBAAqB,CAAC,CAAuB;IAErD,2CAA2C;IAC3C,OAAO,CAAC,SAAS,CAAC,CAAY;IAE9B,OAAO;WAkBa,MAAM,CACxB,MAAM,EAAE,4BAA4B,EACpC,YAAY,EAAE,YAAY,EAC1B,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAoJjB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IA6I9D;;;;OAIG;IACI,WAAW,CAAC,QAAQ,CAAC,EAAE,SAAS;IAKvC,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,6BAA6B;IAYrC,OAAO,CAAC,2BAA2B;YAWrB,2BAA2B;IAkCzC,OAAO,CAAC,iCAAiC;IAIzC,OAAO,CAAC,iCAAiC;YAI3B,6BAA6B;CA4B5C;AAQD,wBAAsB,4BAA4B,CAChD,4BAA4B,EAAE,4BAA4B,EAC1D,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,eAAe,CAAC,CAY1B"} +\ No newline at end of file ++{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/internal/hardhat-network/provider/provider.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EAEf,0BAA0B,EAC1B,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EACV,UAAU,EAKX,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAsBtC,OAAO,EACL,UAAU,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,UAAU,EACV,aAAa,EACd,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAE,YAAY,EAA8B,MAAM,kBAAkB,CAAC;AAO5E,eAAO,MAAM,gBAAgB,+CAA+C,CAAC;AAI7E,wBAAgB,mBAAmB,IAAI,UAAU,CAWhD;AAED,UAAU,4BAA4B;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,oBAAoB,CAAC;IACrC,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,0BAA0B,CAAC;IACnC,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,0BAA0B,EAAE,OAAO,CAAC;IACpC,0BAA0B,EAAE,OAAO,CAAC;IACpC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,4BAA4B,EAAE,OAAO,CAAC;IAEtC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,4BAA4B,EACpC,aAAa,CAAC,EAAE,aAAa,GAC5B,UAAU,CAsBZ;AAWD,qBAAa,kBACX,SAAQ,YACR,YAAW,eAAe;IAQxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAE1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAItB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZ1B,OAAO,CAAC,kBAAkB,CAAK;IAG/B,OAAO,CAAC,qBAAqB,CAAC,CAAuB;IAErD,OAAO;WAYa,MAAM,CACxB,MAAM,EAAE,4BAA4B,EACpC,YAAY,EAAE,YAAY,EAC1B,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAwIjB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAkI9D,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,6BAA6B;CAWtC;AAQD,wBAAsB,4BAA4B,CAChD,4BAA4B,EAAE,4BAA4B,EAC1D,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,eAAe,CAAC,CAY1B"} +\ No newline at end of file +diff --git a/internal/hardhat-network/provider/provider.js b/internal/hardhat-network/provider/provider.js +index b981b1efa0095eb1b02c4846d83a2fe1c003b7fa..7d3ce8deb446e23a2b3c384bff765618c7bb180e 100644 +--- a/internal/hardhat-network/provider/provider.js ++++ b/internal/hardhat-network/provider/provider.js +@@ -1,27 +1,4 @@ + "use strict"; +-var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { +- if (k2 === undefined) k2 = k; +- var desc = Object.getOwnPropertyDescriptor(m, k); +- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { +- desc = { enumerable: true, get: function() { return m[k]; } }; +- } +- Object.defineProperty(o, k2, desc); +-}) : (function(o, m, k, k2) { +- if (k2 === undefined) k2 = k; +- o[k2] = m[k]; +-})); +-var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { +- Object.defineProperty(o, "default", { enumerable: true, value: v }); +-}) : function(o, v) { +- o["default"] = v; +-}); +-var __importStar = (this && this.__importStar) || function (mod) { +- if (mod && mod.__esModule) return mod; +- var result = {}; +- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); +- __setModuleDefault(result, mod); +- return result; +-}; + var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; + }; +@@ -31,22 +8,15 @@ const picocolors_1 = __importDefault(require("picocolors")); + const debug_1 = __importDefault(require("debug")); + const events_1 = require("events"); + const fs_extra_1 = __importDefault(require("fs-extra")); +-const t = __importStar(require("io-ts")); + const semver_1 = __importDefault(require("semver")); + const napi_rs_1 = require("../../../common/napi-rs"); + const constants_1 = require("../../constants"); +-const solc_1 = require("../../core/jsonrpc/types/input/solc"); +-const validation_1 = require("../../core/jsonrpc/types/input/validation"); + const errors_1 = require("../../core/providers/errors"); + const http_1 = require("../../core/providers/http"); + const hardforks_1 = require("../../util/hardforks"); +-const compiler_to_model_1 = require("../stack-traces/compiler-to-model"); + const consoleLogger_1 = require("../stack-traces/consoleLogger"); +-const vm_trace_decoder_1 = require("../stack-traces/vm-trace-decoder"); + const constants_2 = require("../stack-traces/constants"); + const solidity_errors_1 = require("../stack-traces/solidity-errors"); +-const solidityTracer_1 = require("../stack-traces/solidityTracer"); +-const vm_tracer_1 = require("../stack-traces/vm-tracer"); + const packageInfo_1 = require("../../util/packageInfo"); + const convertToEdr_1 = require("./utils/convertToEdr"); + const makeCommon_1 = require("./utils/makeCommon"); +@@ -94,18 +64,14 @@ class EdrProviderEventAdapter extends events_1.EventEmitter { + class EdrProviderWrapper extends events_1.EventEmitter { + constructor(_provider, + // we add this for backwards-compatibility with plugins like solidity-coverage +- _node, _vmTraceDecoder, ++ _node, + // The common configuration for EthereumJS VM is not used by EDR, but tests expect it as part of the provider. +- _common, tracingConfig) { ++ _common) { + super(); + this._provider = _provider; + this._node = _node; +- this._vmTraceDecoder = _vmTraceDecoder; + this._common = _common; + this._failedStackTraces = 0; +- if (tracingConfig !== undefined) { +- (0, vm_trace_decoder_1.initializeVmTraceDecoder)(this._vmTraceDecoder, tracingConfig); +- } + } + static async create(config, loggerConfig, tracingConfig) { + const { Provider } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); +@@ -138,7 +104,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + const eventAdapter = new EdrProviderEventAdapter(); + const printLineFn = loggerConfig.printLineFn ?? logger_1.printLine; + const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? logger_1.replaceLastLine; +- const vmTraceDecoder = new vm_trace_decoder_1.VmTraceDecoder(); + const hardforkName = (0, hardforks_1.getHardforkName)(config.hardfork); + const provider = await Provider.withConfig(getGlobalEdrContext(), { + allowBlocksWithSameTimestamp: config.allowBlocksWithSameTimestamp ?? false, +@@ -185,9 +150,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + }, { + enable: loggerConfig.enabled, + decodeConsoleLogInputsCallback: consoleLogger_1.ConsoleLogger.getDecodedLogs, +- getContractAndFunctionNameCallback: (code, calldata) => { +- return vmTraceDecoder.getContractAndFunctionNamesForCall(code, calldata); +- }, + printLineCallback: (message, replace) => { + if (replace) { + replaceLastLineFn(message); +@@ -196,14 +158,14 @@ class EdrProviderWrapper extends events_1.EventEmitter { + printLineFn(message); + } + }, +- }, (event) => { ++ }, tracingConfig ?? {}, (event) => { + eventAdapter.emit("ethEvent", event); + }); + const minimalEthereumJsNode = { + _vm: (0, minimal_vm_1.getMinimalEthereumJsVm)(provider), + }; + const common = (0, makeCommon_1.makeCommon)(getNodeConfig(config)); +- const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode, vmTraceDecoder, common, tracingConfig); ++ const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode, common); + // Pass through all events from the provider + eventAdapter.addListener("ethEvent", wrapper._ethEventListener.bind(wrapper)); + return wrapper; +@@ -213,11 +175,9 @@ class EdrProviderWrapper extends events_1.EventEmitter { + throw new errors_1.InvalidInputError("Hardhat Network doesn't support JSON-RPC params sent as an object"); + } + const params = args.params ?? []; +- if (args.method === "hardhat_addCompilationResult") { +- return this._addCompilationResultAction(...this._addCompilationResultParams(params)); +- } +- else if (args.method === "hardhat_getStackTraceFailuresCount") { +- return this._getStackTraceFailuresCountAction(...this._getStackTraceFailuresCountParams(params)); ++ if (args.method === "hardhat_getStackTraceFailuresCount") { ++ // stubbed for backwards compatibility ++ return 0; + } + const stringifiedArgs = JSON.stringify({ + method: args.method, +@@ -232,12 +192,10 @@ class EdrProviderWrapper extends events_1.EventEmitter { + response = responseObject.data; + } + const needsTraces = this._node._vm.evm.events.eventNames().length > 0 || +- this._node._vm.events.eventNames().length > 0 || +- this._vmTracer !== undefined; ++ this._node._vm.events.eventNames().length > 0; + if (needsTraces) { + const rawTraces = responseObject.traces; + for (const rawTrace of rawTraces) { +- this._vmTracer?.observe(rawTrace); + // For other consumers in JS we need to marshall the entire trace over FFI + const trace = rawTrace.trace(); + // beforeTx event +@@ -272,12 +230,8 @@ class EdrProviderWrapper extends events_1.EventEmitter { + } + if ((0, http_1.isErrorResponse)(response)) { + let error; +- const solidityTrace = responseObject.solidityTrace; +- let stackTrace; +- if (solidityTrace !== null) { +- stackTrace = await this._rawTraceToSolidityStackTrace(solidityTrace); +- } +- if (stackTrace !== undefined) { ++ const stackTrace = responseObject.stackTrace(); ++ if (stackTrace !== null) { + error = (0, solidity_errors_1.encodeSolidityStackTrace)(response.error.message, stackTrace); + // Pass data and transaction hash from the original error + error.data = response.error.data?.data ?? undefined; +@@ -315,14 +269,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + return response.result; + } + } +- /** +- * Sets a `VMTracer` that observes EVM throughout requests. +- * +- * Used for internal stack traces integration tests. +- */ +- setVmTracer(vmTracer) { +- this._vmTracer = vmTracer; +- } + // temporarily added to make smock work with HH+EDR + _setCallOverrideCallback(callback) { + this._callOverrideCallback = callback; +@@ -357,50 +303,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { + }; + this.emit("message", message); + } +- _addCompilationResultParams(params) { +- return (0, validation_1.validateParams)(params, t.string, solc_1.rpcCompilerInput, solc_1.rpcCompilerOutput); +- } +- async _addCompilationResultAction(solcVersion, compilerInput, compilerOutput) { +- let bytecodes; +- try { +- bytecodes = (0, compiler_to_model_1.createModelsAndDecodeBytecodes)(solcVersion, compilerInput, compilerOutput); +- } +- catch (error) { +- console.warn(picocolors_1.default.yellow("The Hardhat Network tracing engine could not be updated. Run Hardhat with --verbose to learn more.")); +- log("VmTraceDecoder failed to be updated. Please report this to help us improve Hardhat.\n", error); +- return false; +- } +- for (const bytecode of bytecodes) { +- this._vmTraceDecoder.addBytecode(bytecode); +- } +- return true; +- } +- _getStackTraceFailuresCountParams(params) { +- return (0, validation_1.validateParams)(params); +- } +- _getStackTraceFailuresCountAction() { +- return this._failedStackTraces; +- } +- async _rawTraceToSolidityStackTrace(rawTrace) { +- const vmTracer = new vm_tracer_1.VMTracer(); +- vmTracer.observe(rawTrace); +- let vmTrace = vmTracer.getLastTopLevelMessageTrace(); +- const vmTracerError = vmTracer.getLastError(); +- if (vmTrace !== undefined) { +- vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); +- } +- try { +- if (vmTrace === undefined || vmTracerError !== undefined) { +- throw vmTracerError; +- } +- const solidityTracer = new solidityTracer_1.SolidityTracer(); +- return solidityTracer.getStackTrace(vmTrace); +- } +- catch (err) { +- this._failedStackTraces += 1; +- log("Could not generate stack trace. Please report this to help us improve Hardhat.\n", err); +- } +- } + } + exports.EdrProviderWrapper = EdrProviderWrapper; + async function clientVersion(edrClientVersion) { +diff --git a/internal/hardhat-network/provider/provider.js.map b/internal/hardhat-network/provider/provider.js.map +index a3f76bed476f594bb8cce9f42cfae119222b9651..0b1516e1d212d9d5d6c2d4b262db45ae9cb0bad6 100644 +--- a/internal/hardhat-network/provider/provider.js.map ++++ b/internal/hardhat-network/provider/provider.js.map +@@ -1 +1 @@ +-{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/internal/hardhat-network/provider/provider.ts"],"names":[],"mappings":"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"} +\ No newline at end of file ++{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/internal/hardhat-network/provider/provider.ts"],"names":[],"mappings":"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"} +\ No newline at end of file +diff --git a/internal/hardhat-network/stack-traces/debug.d.ts b/internal/hardhat-network/stack-traces/debug.d.ts +index 250c7240aa6846a3423ca4e4a036ac9b5fed9606..7a469dfb6d7f65ec2dcc747f5417670a7535f32c 100644 +--- a/internal/hardhat-network/stack-traces/debug.d.ts ++++ b/internal/hardhat-network/stack-traces/debug.d.ts +@@ -1,3 +1,3 @@ +-declare const printMessageTrace: typeof import("@nomicfoundation/edr").printMessageTrace, printStackTrace: typeof import("@nomicfoundation/edr").printStackTrace; +-export { printMessageTrace, printStackTrace }; ++declare const printStackTrace: typeof import("@nomicfoundation/edr").printStackTrace; ++export { printStackTrace }; + //# sourceMappingURL=debug.d.ts.map +\ No newline at end of file +diff --git a/internal/hardhat-network/stack-traces/debug.d.ts.map b/internal/hardhat-network/stack-traces/debug.d.ts.map +index 8df51b131c552b93aaaa77e1a9b6403ea99af273..157bd4948c7b351b02b99c836a9a983eddcd44fd 100644 +--- a/internal/hardhat-network/stack-traces/debug.d.ts.map ++++ b/internal/hardhat-network/stack-traces/debug.d.ts.map +@@ -1 +1 @@ +-{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../../src/internal/hardhat-network/stack-traces/debug.ts"],"names":[],"mappings":"AAEA,QAAA,MAAQ,iBAAiB,2DAAE,eAAe,uDAEA,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,CAAC"} +\ No newline at end of file ++{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../../src/internal/hardhat-network/stack-traces/debug.ts"],"names":[],"mappings":"AAEA,QAAA,MAAQ,eAAe,uDAEmB,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,CAAC"} +\ No newline at end of file +diff --git a/internal/hardhat-network/stack-traces/debug.js b/internal/hardhat-network/stack-traces/debug.js +index 174709d5296f4e26e80517eb05c241479f3a3e96..6eeb79514cd020c1824dc6cb164b80d90f32d232 100644 +--- a/internal/hardhat-network/stack-traces/debug.js ++++ b/internal/hardhat-network/stack-traces/debug.js +@@ -1,8 +1,7 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); +-exports.printStackTrace = exports.printMessageTrace = void 0; ++exports.printStackTrace = void 0; + const napi_rs_1 = require("../../../common/napi-rs"); +-const { printMessageTrace, printStackTrace } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); +-exports.printMessageTrace = printMessageTrace; ++const { printStackTrace } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); + exports.printStackTrace = printStackTrace; + //# sourceMappingURL=debug.js.map +\ No newline at end of file +diff --git a/internal/hardhat-network/stack-traces/debug.js.map b/internal/hardhat-network/stack-traces/debug.js.map +index 33b6b8cffb8fcd75aeec246eb1b4a1e76590ac35..a56ec7347c6710736698810e23e9b887e453824f 100644 +--- a/internal/hardhat-network/stack-traces/debug.js.map ++++ b/internal/hardhat-network/stack-traces/debug.js.map +@@ -1 +1 @@ +-{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../../src/internal/hardhat-network/stack-traces/debug.ts"],"names":[],"mappings":";;;AAAA,qDAA8D;AAE9D,MAAM,EAAE,iBAAiB,EAAE,eAAe,EAAE,GAAG,IAAA,6BAAmB,EAChE,sBAAsB,CACkB,CAAC;AAElC,8CAAiB;AAAE,0CAAe"} +\ No newline at end of file ++{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../../src/internal/hardhat-network/stack-traces/debug.ts"],"names":[],"mappings":";;;AAAA,qDAA8D;AAE9D,MAAM,EAAE,eAAe,EAAE,GAAG,IAAA,6BAAmB,EAC7C,sBAAsB,CACkB,CAAC;AAElC,0CAAe"} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1524710a..6add7cea2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,9 @@ overrides: hardhat>@nomicfoundation/edr: workspace:* patchedDependencies: - hardhat@2.22.15: - hash: eklheooma4hcnaltirg5pwganq - path: patches/hardhat@2.22.15.patch + hardhat@2.22.17: + hash: 3xrivdutniceknwbgxg5csrkii + path: patches/hardhat@2.22.17.patch importers: @@ -128,8 +128,8 @@ importers: specifier: 5.2.1 version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) hardhat: - specifier: 2.22.15 - version: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + specifier: 2.22.17 + version: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash: specifier: ^4.17.11 version: 4.17.21 @@ -254,8 +254,8 @@ importers: specifier: ^7.0.1 version: 7.0.1 hardhat: - specifier: 2.22.15 - version: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + specifier: 2.22.17 + version: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -285,10 +285,10 @@ importers: devDependencies: '@defi-wonderland/smock': specifier: ^2.4.0 - version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@types/node': specifier: ^20.0.0 version: 20.16.1 @@ -299,8 +299,8 @@ importers: specifier: 5.7.2 version: 5.7.2 hardhat: - specifier: 2.22.15 - version: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + specifier: 2.22.17 + version: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -1729,6 +1729,14 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1737,10 +1745,6 @@ packages: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} - find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1875,8 +1879,8 @@ packages: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} - hardhat@2.22.15: - resolution: {integrity: sha512-BpTGa9PE/sKAaHi4s/S1e9WGv63DR1m7Lzfd60C8gSEchDPfAJssVRSq0MZ2v2k76ig9m0kHAwVLf5teYwu/Mw==} + hardhat@2.22.17: + resolution: {integrity: sha512-tDlI475ccz4d/dajnADUTRc1OJ3H8fpP9sWhXhBPpYsQOg8JHq5xrDimo53UhWPl7KJmAeDCm1bFG74xvpGRpg==} hasBin: true peerDependencies: ts-node: '*' @@ -2189,10 +2193,6 @@ packages: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -2395,10 +2395,6 @@ packages: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2407,10 +2403,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -2427,10 +2419,6 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -2443,10 +2431,6 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2483,10 +2467,17 @@ packages: picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -2886,6 +2877,10 @@ packages: resolution: {integrity: sha512-Kw36UHxJEELq2VUqdaSGR2/8cAsPgMtvX8uGVU6Jk26O66PhXec0A5ZnRYs47btbtwPDpXXF66+Fo3vimCM9aQ==} engines: {node: '>=16'} + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} + engines: {node: '>=12.0.0'} + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -3356,16 +3351,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) diff: 5.0.0 ethers: 5.7.2 - hardhat: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash.isequal: 4.5.0 lodash.isequalwith: 4.4.0 rxjs: 7.8.1 @@ -3881,10 +3876,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: ethers: 5.7.2 - hardhat: 2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) '@pkgr/core@0.1.1': {} @@ -5036,6 +5031,10 @@ snapshots: dependencies: reusify: 1.0.4 + fdir@6.4.2(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -5044,10 +5043,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - find-up@2.1.0: - dependencies: - locate-path: 2.0.0 - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -5198,7 +5193,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.15(patch_hash=eklheooma4hcnaltirg5pwganq)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): + hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -5214,7 +5209,6 @@ snapshots: aggregate-error: 3.1.0 ansi-escapes: 4.3.2 boxen: 5.1.2 - chalk: 2.4.2 chokidar: 4.0.1 ci-info: 2.0.0 debug: 4.3.4(supports-color@8.1.1) @@ -5222,10 +5216,9 @@ snapshots: env-paths: 2.2.1 ethereum-cryptography: 1.2.0 ethereumjs-abi: 0.6.8 - find-up: 2.1.0 + find-up: 5.0.0 fp-ts: 1.19.3 fs-extra: 7.0.1 - glob: 7.2.0 immutable: 4.3.5 io-ts: 1.10.4 json-stream-stringify: 3.1.4 @@ -5234,12 +5227,14 @@ snapshots: mnemonist: 0.38.5 mocha: 10.3.0 p-map: 4.0.0 + picocolors: 1.1.1 raw-body: 2.5.2 resolve: 1.17.0 semver: 6.3.1 solc: 0.8.26(debug@4.3.4) source-map-support: 0.5.21 stacktrace-parser: 0.1.10 + tinyglobby: 0.2.10 tsort: 0.0.1 undici: 5.28.3 uuid: 8.3.2 @@ -5519,11 +5514,6 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - locate-path@2.0.0: - dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -5755,10 +5745,6 @@ snapshots: dependencies: p-map: 2.1.0 - p-limit@1.3.0: - dependencies: - p-try: 1.0.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -5767,10 +5753,6 @@ snapshots: dependencies: yocto-queue: 0.1.0 - p-locate@2.0.0: - dependencies: - p-limit: 1.3.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -5785,8 +5767,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-try@1.0.0: {} - p-try@2.2.0: {} parent-module@1.0.1: @@ -5800,8 +5780,6 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - path-exists@3.0.0: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -5830,8 +5808,12 @@ snapshots: picocolors@1.0.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.2: {} + pify@4.0.1: {} pkg-dir@4.2.0: @@ -6255,6 +6237,11 @@ snapshots: tightrope@0.2.0: {} + tinyglobby@0.2.10: + dependencies: + fdir: 6.4.2(picomatch@4.0.2) + picomatch: 4.0.2 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 From e82fcacf015b56e3e6a164bd0bc7501fc2935347 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Fri, 27 Dec 2024 13:58:41 +0100 Subject: [PATCH 26/30] Export a get_latest_supported_solc_version function This function has to match the latest tested solc version in the stack traces tests. --- crates/edr_napi/index.d.ts | 5 +++++ crates/edr_napi/index.js | 3 ++- crates/edr_napi/src/trace.rs | 7 +++++++ .../test/internal/hardhat-network/stack-traces/test.ts | 8 ++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 9dce1b6f4..37a3cbddc 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -573,6 +573,11 @@ export interface TracingMessageResult { /** Execution result */ readonly executionResult: ExecutionResult } +/** + * Returns the latest version of solc that EDR officially + * supports and is tested against. + */ +export declare function getLatestSupportedSolcVersion(): string export interface Withdrawal { /** The index of withdrawal */ index: bigint diff --git a/crates/edr_napi/index.js b/crates/edr_napi/index.js index 9b62e1be7..566ec1fe3 100644 --- a/crates/edr_napi/index.js +++ b/crates/edr_napi/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, linkHexStringBytecode, printStackTrace, Exit, ExitCode, BytecodeWrapper, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace } = nativeBinding +const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, linkHexStringBytecode, printStackTrace, Exit, ExitCode, BytecodeWrapper, ContractFunctionType, ReturnData, StackTraceEntryType, stackTraceEntryTypeToString, FALLBACK_FUNCTION_NAME, RECEIVE_FUNCTION_NAME, CONSTRUCTOR_FUNCTION_NAME, UNRECOGNIZED_FUNCTION_NAME, UNKNOWN_FUNCTION_NAME, PRECOMPILE_FUNCTION_NAME, UNRECOGNIZED_CONTRACT_NAME, RawTrace, getLatestSupportedSolcVersion } = nativeBinding module.exports.SpecId = SpecId module.exports.EdrContext = EdrContext @@ -336,3 +336,4 @@ module.exports.UNKNOWN_FUNCTION_NAME = UNKNOWN_FUNCTION_NAME module.exports.PRECOMPILE_FUNCTION_NAME = PRECOMPILE_FUNCTION_NAME module.exports.UNRECOGNIZED_CONTRACT_NAME = UNRECOGNIZED_CONTRACT_NAME module.exports.RawTrace = RawTrace +module.exports.getLatestSupportedSolcVersion = getLatestSupportedSolcVersion diff --git a/crates/edr_napi/src/trace.rs b/crates/edr_napi/src/trace.rs index e97a63ff2..1170340fd 100644 --- a/crates/edr_napi/src/trace.rs +++ b/crates/edr_napi/src/trace.rs @@ -196,3 +196,10 @@ impl RawTrace { .collect::>() } } + +#[napi] +/// Returns the latest version of solc that EDR officially +/// supports and is tested against. +pub fn get_latest_supported_solc_version() -> String { + "0.8.28".to_string() +} diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts index e6161b6f8..43eef72fb 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/test.ts @@ -1,4 +1,5 @@ import { + getLatestSupportedSolcVersion, linkHexStringBytecode, stackTraceEntryTypeToString, } from "@nomicfoundation/edr"; @@ -821,6 +822,13 @@ describe("Solidity support", function () { `Expected ${nextMajorVersion} to not be within the ${SUPPORTED_SOLIDITY_VERSION_RANGE} range` ); }); + + it("check that the latest tested version matches the one that EDR exports", async function () { + const latestSupportedVersion = getLatestTestedSolcVersion(); + const edrLatestSupportedVersion = getLatestSupportedSolcVersion(); + + assert.equal(latestSupportedVersion, edrLatestSupportedVersion); + }); }); function defineTestForSolidityMajorVersion( From 6e19b020d59522a3767bcfb82345f1d87ea4919d Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Mon, 30 Dec 2024 16:01:25 +0000 Subject: [PATCH 27/30] Make `stackTrace` throw on error --- crates/edr_napi/index.d.ts | 4 ++-- crates/edr_napi/src/provider.rs | 19 ++++++---------- crates/edr_solidity/src/nested_tracer.rs | 28 +++++------------------- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 37a3cbddc..12a6164e4 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -611,8 +611,8 @@ export declare class Response { /** Returns the response data as a JSON string or a JSON object. */ get data(): string | any get traces(): Array - /**Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback. */ - stackTrace(): SolidityStackTrace | string | null + /**Compute the error stack trace. Return the stack trace if it can be decoded, otherwise returns none. Throws if there was an error computing the stack trace. */ + stackTrace(): SolidityStackTrace | null } export declare class Exit { get kind(): ExitCode diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 2e3a87601..d14301559 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -280,9 +280,9 @@ impl Response { } // Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590 - #[doc = "Compute the error stack trace. Return undefined if there was no error, returns the stack trace if it can be computed or returns the error message if available as a fallback."] + #[doc = "Compute the error stack trace. Return the stack trace if it can be decoded, otherwise returns none. Throws if there was an error computing the stack trace."] #[napi] - pub fn stack_trace(&self) -> napi::Result>> { + pub fn stack_trace(&self) -> napi::Result> { let Some(SolidityTraceData { trace, contract_decoder, @@ -292,24 +292,19 @@ impl Response { }; let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace( trace.as_ref().clone(), - ); + ) + .map_err(|err| napi::Error::from_reason(err.to_string()))?; - if let Some(vm_trace) = nested_trace.result { + if let Some(vm_trace) = nested_trace { let decoded_trace = contract_decoder.try_to_decode_message_trace(vm_trace); let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace) - .map_err(|err| { - napi::Error::from_reason(format!( - "Error converting to solidity stack trace: '{err}'" - )) - })?; + .map_err(|err| napi::Error::from_reason(err.to_string()))?; let stack_trace = stack_trace .into_iter() .map(super::cast::TryCast::try_cast) .collect::, _>>()?; - Ok(Some(Either::A(stack_trace))) - } else if let Some(vm_tracer_error) = nested_trace.error { - Ok(Some(Either::B(vm_tracer_error.to_string()))) + Ok(Some(stack_trace)) } else { Ok(None) } diff --git a/crates/edr_solidity/src/nested_tracer.rs b/crates/edr_solidity/src/nested_tracer.rs index 436a855eb..57d36f94d 100644 --- a/crates/edr_solidity/src/nested_tracer.rs +++ b/crates/edr_solidity/src/nested_tracer.rs @@ -37,33 +37,15 @@ pub enum NestedTracerError { StepDuringPreCompile, } -/// The result of converting a trace to a nested trace. -/// If there was an error, the error is stored in `error` and the `result` is -/// the last successfully decoded trace if any. -pub struct NestedTracerResult { - /// The error that occurred during the conversion. - pub error: Option, - /// The last successfully decoded trace. - pub result: Option, -} - -impl From for Result, NestedTracerError> { - fn from(value: NestedTracerResult) -> Self { - match value.error { - Some(error) => Err(error), - None => Ok(value.result), - } - } -} - /// Observes a trace, collecting information about the execution of the EVM. -pub fn convert_trace_messages_to_nested_trace(trace: edr_evm::trace::Trace) -> NestedTracerResult { +pub fn convert_trace_messages_to_nested_trace( + trace: edr_evm::trace::Trace, +) -> Result, NestedTracerError> { let mut tracer = NestedTracer::new(); - let error = tracer.add_messages(trace.messages).err(); - let result = tracer.get_last_top_level_message_trace(); + tracer.add_messages(trace.messages)?; - NestedTracerResult { error, result } + Ok(tracer.get_last_top_level_message_trace()) } /// Naive Rust port of the `VmTracer` from Hardhat. From 03b8e362982a467dc9f5b47b4a7ab39fe5c6f7e0 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Mon, 30 Dec 2024 18:41:45 +0100 Subject: [PATCH 28/30] Adapt tests and patch to new .stackTrace() semantics --- .../hardhat-network/stack-traces/execution.ts | 7 +----- patches/hardhat@2.22.17.patch | 20 ++++++++++------ pnpm-lock.yaml | 24 +++++++++---------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts index 3198eea38..4d9ca314d 100644 --- a/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts +++ b/hardhat-tests/test/internal/hardhat-network/stack-traces/execution.ts @@ -142,12 +142,7 @@ export async function traceTransaction( params: [response.result ?? response.error.data.transactionHash], }); - const stackTrace: SolidityStackTrace | string | null = - responseObject.stackTrace(); - - if (typeof stackTrace === "string") { - throw new Error("this shouldn't happen"); - } + const stackTrace: SolidityStackTrace | null = responseObject.stackTrace(); const contractAddress = receipt.contractAddress?.slice(2); diff --git a/patches/hardhat@2.22.17.patch b/patches/hardhat@2.22.17.patch index 4b53d4d67..1db2f65fc 100644 --- a/patches/hardhat@2.22.17.patch +++ b/patches/hardhat@2.22.17.patch @@ -148,7 +148,7 @@ index 9df47bce20ca6de26d13a2616bfce48eea655c67..a5e4a60cdb308c82a42bcc22dd045821 +{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/internal/hardhat-network/provider/provider.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EAEf,0BAA0B,EAC1B,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EACV,UAAU,EAKX,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAsBtC,OAAO,EACL,UAAU,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,UAAU,EACV,aAAa,EACd,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAE,YAAY,EAA8B,MAAM,kBAAkB,CAAC;AAO5E,eAAO,MAAM,gBAAgB,+CAA+C,CAAC;AAI7E,wBAAgB,mBAAmB,IAAI,UAAU,CAWhD;AAED,UAAU,4BAA4B;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,oBAAoB,CAAC;IACrC,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,0BAA0B,CAAC;IACnC,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,0BAA0B,EAAE,OAAO,CAAC;IACpC,0BAA0B,EAAE,OAAO,CAAC;IACpC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,4BAA4B,EAAE,OAAO,CAAC;IAEtC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,4BAA4B,EACpC,aAAa,CAAC,EAAE,aAAa,GAC5B,UAAU,CAsBZ;AAWD,qBAAa,kBACX,SAAQ,YACR,YAAW,eAAe;IAQxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAE1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAItB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZ1B,OAAO,CAAC,kBAAkB,CAAK;IAG/B,OAAO,CAAC,qBAAqB,CAAC,CAAuB;IAErD,OAAO;WAYa,MAAM,CACxB,MAAM,EAAE,4BAA4B,EACpC,YAAY,EAAE,YAAY,EAC1B,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAwIjB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAkI9D,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,6BAA6B;CAWtC;AAQD,wBAAsB,4BAA4B,CAChD,4BAA4B,EAAE,4BAA4B,EAC1D,YAAY,EAAE,YAAY,EAC1B,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,eAAe,CAAC,CAY1B"} \ No newline at end of file diff --git a/internal/hardhat-network/provider/provider.js b/internal/hardhat-network/provider/provider.js -index b981b1efa0095eb1b02c4846d83a2fe1c003b7fa..7d3ce8deb446e23a2b3c384bff765618c7bb180e 100644 +index b981b1efa0095eb1b02c4846d83a2fe1c003b7fa..e37766ee9184c9f8bd59446015f0e3070237dd6f 100644 --- a/internal/hardhat-network/provider/provider.js +++ b/internal/hardhat-network/provider/provider.js @@ -1,27 +1,4 @@ @@ -287,7 +287,7 @@ index b981b1efa0095eb1b02c4846d83a2fe1c003b7fa..7d3ce8deb446e23a2b3c384bff765618 // For other consumers in JS we need to marshall the entire trace over FFI const trace = rawTrace.trace(); // beforeTx event -@@ -272,12 +230,8 @@ class EdrProviderWrapper extends events_1.EventEmitter { +@@ -272,12 +230,15 @@ class EdrProviderWrapper extends events_1.EventEmitter { } if ((0, http_1.isErrorResponse)(response)) { let error; @@ -295,14 +295,20 @@ index b981b1efa0095eb1b02c4846d83a2fe1c003b7fa..7d3ce8deb446e23a2b3c384bff765618 - let stackTrace; - if (solidityTrace !== null) { - stackTrace = await this._rawTraceToSolidityStackTrace(solidityTrace); -- } ++ // const stackTrace: SolidityStackTrace | null = responseObject.stackTrace(); ++ let stackTrace = null; ++ try { ++ stackTrace = responseObject.stackTrace(); ++ } ++ catch (e) { ++ log("Failed to get stack trace: %O", e); + } - if (stackTrace !== undefined) { -+ const stackTrace = responseObject.stackTrace(); + if (stackTrace !== null) { error = (0, solidity_errors_1.encodeSolidityStackTrace)(response.error.message, stackTrace); // Pass data and transaction hash from the original error error.data = response.error.data?.data ?? undefined; -@@ -315,14 +269,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { +@@ -315,14 +276,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { return response.result; } } @@ -317,7 +323,7 @@ index b981b1efa0095eb1b02c4846d83a2fe1c003b7fa..7d3ce8deb446e23a2b3c384bff765618 // temporarily added to make smock work with HH+EDR _setCallOverrideCallback(callback) { this._callOverrideCallback = callback; -@@ -357,50 +303,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { +@@ -357,50 +310,6 @@ class EdrProviderWrapper extends events_1.EventEmitter { }; this.emit("message", message); } @@ -414,7 +420,7 @@ index 174709d5296f4e26e80517eb05c241479f3a3e96..6eeb79514cd020c1824dc6cb164b80d9 //# sourceMappingURL=debug.js.map \ No newline at end of file diff --git a/internal/hardhat-network/stack-traces/debug.js.map b/internal/hardhat-network/stack-traces/debug.js.map -index 33b6b8cffb8fcd75aeec246eb1b4a1e76590ac35..a56ec7347c6710736698810e23e9b887e453824f 100644 +index 33b6b8cffb8fcd75aeec246eb1b4a1e76590ac35..949d865347ff533e23b19d6d4f36528b761e6d99 100644 --- a/internal/hardhat-network/stack-traces/debug.js.map +++ b/internal/hardhat-network/stack-traces/debug.js.map @@ -1 +1 @@ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6add7cea2..42652900a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ overrides: patchedDependencies: hardhat@2.22.17: - hash: 3xrivdutniceknwbgxg5csrkii + hash: harzmcugvul6sjoarjvhauoawi path: patches/hardhat@2.22.17.patch importers: @@ -129,7 +129,7 @@ importers: version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) hardhat: specifier: 2.22.17 - version: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash: specifier: ^4.17.11 version: 4.17.21 @@ -255,7 +255,7 @@ importers: version: 7.0.1 hardhat: specifier: 2.22.17 - version: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -285,10 +285,10 @@ importers: devDependencies: '@defi-wonderland/smock': specifier: ^2.4.0 - version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) '@types/node': specifier: ^20.0.0 version: 20.16.1 @@ -300,7 +300,7 @@ importers: version: 5.7.2 hardhat: specifier: 2.22.17 - version: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -3351,16 +3351,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@defi-wonderland/smock@2.4.0(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4)) diff: 5.0.0 ethers: 5.7.2 - hardhat: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) lodash.isequal: 4.5.0 lodash.isequalwith: 4.4.0 rxjs: 7.8.1 @@ -3876,10 +3876,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4))': dependencies: ethers: 5.7.2 - hardhat: 2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4) '@pkgr/core@0.1.1': {} @@ -5193,7 +5193,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.17(patch_hash=3xrivdutniceknwbgxg5csrkii)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): + hardhat@2.22.17(patch_hash=harzmcugvul6sjoarjvhauoawi)(ts-node@10.9.2(@types/node@20.16.1)(typescript@5.0.4))(typescript@5.0.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 From 4adac6b7233d9d14727879882170f375e72f93e5 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 2 Jan 2025 09:19:36 +0000 Subject: [PATCH 29/30] Add TODO comments --- crates/edr_napi/src/provider.rs | 1 + crates/edr_solidity/src/build_model.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index d14301559..c60d9bf1c 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -44,6 +44,7 @@ impl Provider { let config = edr_provider::ProviderConfig::try_from(config)?; + // TODO https://github.com/NomicFoundation/edr/issues/760 let build_info_config: edr_solidity::contract_decoder::BuildInfoConfig = serde_json::from_value(tracing_config)?; let contract_decoder = ContractDecoder::new(&build_info_config) diff --git a/crates/edr_solidity/src/build_model.rs b/crates/edr_solidity/src/build_model.rs index 1649a3d3b..210763a4b 100644 --- a/crates/edr_solidity/src/build_model.rs +++ b/crates/edr_solidity/src/build_model.rs @@ -27,12 +27,14 @@ use crate::artifacts::{ContractAbiEntry, ImmutableReference}; /// A resolved build model from a Solidity compiler standard JSON output. #[derive(Debug, Default)] pub struct BuildModel { + // TODO https://github.com/NomicFoundation/edr/issues/759 /// Maps the contract ID to the contract. pub contract_id_to_contract: IndexMap>>, /// Maps the file ID to the source file. pub file_id_to_source_file: Arc, } +// TODO https://github.com/NomicFoundation/edr/issues/759 /// Type alias for the source file mapping used by [`BuildModel`]. pub type BuildModelSources = HashMap>>; @@ -340,6 +342,7 @@ pub struct ContractMetadata { // This owns the source files transitively used by the source locations // in the Instruction structs. _sources: Arc, + // TODO https://github.com/NomicFoundation/edr/issues/759 /// Contract that the bytecode belongs to. pub contract: Arc>, /// Whether the bytecode is a deployment bytecode. From ee22667a980a4c9efffe79611b9c6b6279cc988f Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 2 Jan 2025 09:23:49 +0000 Subject: [PATCH 30/30] Fix imports --- Cargo.lock | 1 - crates/edr_solidity/Cargo.toml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 952b5b9a0..6059a5ea4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1288,7 +1288,6 @@ dependencies = [ "anyhow", "edr_eth", "edr_evm", - "either", "indexmap 2.2.6", "parking_lot 0.12.3", "semver 1.0.23", diff --git a/crates/edr_solidity/Cargo.toml b/crates/edr_solidity/Cargo.toml index 458ae07da..9b2e11f32 100644 --- a/crates/edr_solidity/Cargo.toml +++ b/crates/edr_solidity/Cargo.toml @@ -15,9 +15,8 @@ parking_lot = { version = "0.12.1", default-features = false } serde = { version = "1.0.158", default-features = false, features = ["std"] } serde_json = { version = "1.0.89", features = ["preserve_order"] } strum = { version = "0.26.0", features = ["derive"] } -thiserror = "1.0.58" semver = "1.0.23" -either = "1.10.0" +thiserror = "1.0.58" [lints] workspace = true