@nomicfoundation/edr 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +7 -0
- package/index.d.ts +277 -1
- package/index.js +23 -1
- package/package.json +44 -35
- package/src/logger.rs +1 -0
- package/src/provider.rs +0 -6
- package/src/trace/compiler.rs +27 -0
- package/src/trace/debug.rs +318 -0
- package/src/trace/error_inferrer.rs +2213 -0
- package/src/trace/exit.rs +86 -0
- package/src/trace/library_utils.rs +6 -0
- package/src/trace/mapped_inlined_internal_functions_heuristics.rs +180 -0
- package/src/trace/message_trace.rs +179 -0
- package/src/trace/model.rs +59 -0
- package/src/trace/return_data.rs +84 -0
- package/src/trace/solidity_stack_trace.rs +694 -0
- package/src/trace/solidity_tracer.rs +315 -0
- package/src/trace/vm_trace_decoder.rs +234 -0
- package/src/trace/vm_tracer.rs +71 -0
- package/src/trace.rs +23 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//! Naive rewrite of `hardhat-network/provider/vm/exit.ts` from Hardhat.
|
|
2
|
+
//! Used together with `VmTracer`.
|
|
3
|
+
|
|
4
|
+
use std::fmt;
|
|
5
|
+
|
|
6
|
+
use edr_evm::HaltReason;
|
|
7
|
+
use napi_derive::napi;
|
|
8
|
+
|
|
9
|
+
#[napi]
|
|
10
|
+
pub struct Exit(pub(crate) ExitCode);
|
|
11
|
+
|
|
12
|
+
#[napi]
|
|
13
|
+
/// Represents the exit code of the EVM.
|
|
14
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
15
|
+
#[allow(clippy::upper_case_acronyms, non_camel_case_types)] // These are exported and mapped 1:1 to existing JS enum
|
|
16
|
+
pub enum ExitCode {
|
|
17
|
+
/// Execution was successful.
|
|
18
|
+
SUCCESS = 0,
|
|
19
|
+
/// Execution was reverted.
|
|
20
|
+
REVERT,
|
|
21
|
+
/// Execution ran out of gas.
|
|
22
|
+
OUT_OF_GAS,
|
|
23
|
+
/// Execution encountered an internal error.
|
|
24
|
+
INTERNAL_ERROR,
|
|
25
|
+
/// Execution encountered an invalid opcode.
|
|
26
|
+
INVALID_OPCODE,
|
|
27
|
+
/// Execution encountered a stack underflow.
|
|
28
|
+
STACK_UNDERFLOW,
|
|
29
|
+
/// Create init code size exceeds limit (runtime).
|
|
30
|
+
CODESIZE_EXCEEDS_MAXIMUM,
|
|
31
|
+
/// Create collision.
|
|
32
|
+
CREATE_COLLISION,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl fmt::Display for ExitCode {
|
|
36
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
37
|
+
match self {
|
|
38
|
+
ExitCode::SUCCESS => write!(f, "Success"),
|
|
39
|
+
ExitCode::REVERT => write!(f, "Reverted"),
|
|
40
|
+
ExitCode::OUT_OF_GAS => write!(f, "Out of gas"),
|
|
41
|
+
ExitCode::INTERNAL_ERROR => write!(f, "Internal error"),
|
|
42
|
+
ExitCode::INVALID_OPCODE => write!(f, "Invalid opcode"),
|
|
43
|
+
ExitCode::STACK_UNDERFLOW => write!(f, "Stack underflow"),
|
|
44
|
+
ExitCode::CODESIZE_EXCEEDS_MAXIMUM => write!(f, "Codesize exceeds maximum"),
|
|
45
|
+
ExitCode::CREATE_COLLISION => write!(f, "Create collision"),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[allow(clippy::fallible_impl_from)] // naively ported for now
|
|
51
|
+
impl From<edr_solidity::message_trace::ExitCode> for ExitCode {
|
|
52
|
+
fn from(code: edr_solidity::message_trace::ExitCode) -> Self {
|
|
53
|
+
use edr_solidity::message_trace::ExitCode;
|
|
54
|
+
|
|
55
|
+
match code {
|
|
56
|
+
ExitCode::Success => Self::SUCCESS,
|
|
57
|
+
ExitCode::Revert => Self::REVERT,
|
|
58
|
+
ExitCode::Halt(HaltReason::OutOfGas(_)) => Self::OUT_OF_GAS,
|
|
59
|
+
ExitCode::Halt(HaltReason::OpcodeNotFound | HaltReason::InvalidFEOpcode
|
|
60
|
+
// Returned when an opcode is not implemented for the hardfork
|
|
61
|
+
| HaltReason::NotActivated) => Self::INVALID_OPCODE,
|
|
62
|
+
ExitCode::Halt(HaltReason::StackUnderflow) => Self::STACK_UNDERFLOW,
|
|
63
|
+
ExitCode::Halt(HaltReason::CreateContractSizeLimit) => Self::CODESIZE_EXCEEDS_MAXIMUM,
|
|
64
|
+
ExitCode::Halt(HaltReason::CreateCollision) => Self::CREATE_COLLISION,
|
|
65
|
+
halt @ ExitCode::Halt(_) => panic!("Unmatched EDR exceptional halt: {halt:?}"),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[napi]
|
|
71
|
+
impl Exit {
|
|
72
|
+
#[napi(getter)]
|
|
73
|
+
pub fn kind(&self) -> ExitCode {
|
|
74
|
+
self.0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[napi]
|
|
78
|
+
pub fn is_error(&self) -> bool {
|
|
79
|
+
!matches!(self.0, ExitCode::SUCCESS)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[napi]
|
|
83
|
+
pub fn get_reason(&self) -> String {
|
|
84
|
+
self.0.to_string()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
//! This file includes Solidity tracing heuristics for solc starting with
|
|
2
|
+
//! version 0.6.9.
|
|
3
|
+
//!
|
|
4
|
+
//! This solc version introduced a significant change to how sourcemaps are
|
|
5
|
+
//! handled for inline yul/internal functions. These were mapped to the
|
|
6
|
+
//! unmapped/-1 file before, which lead to many unmapped reverts. Now, they are
|
|
7
|
+
//! mapped to the part of the Solidity source that lead to their inlining.
|
|
8
|
+
//!
|
|
9
|
+
//! This change is a very positive change, as errors would point to the correct
|
|
10
|
+
//! line by default. The only problem is that we used to rely very heavily on
|
|
11
|
+
//! unmapped reverts to decide when our error detection heuristics were to be
|
|
12
|
+
//! run. In fact, these heuristics were first introduced because of unmapped
|
|
13
|
+
//! reverts.
|
|
14
|
+
//!
|
|
15
|
+
//! Instead of synthetically completing stack traces when unmapped reverts
|
|
16
|
+
//! occur, we now start from complete stack traces and adjust them if we can
|
|
17
|
+
//! provide more meaningful errors.
|
|
18
|
+
|
|
19
|
+
use edr_evm::interpreter::OpCode;
|
|
20
|
+
use napi::{
|
|
21
|
+
bindgen_prelude::{Either24, Either4},
|
|
22
|
+
Either,
|
|
23
|
+
};
|
|
24
|
+
use semver::Version;
|
|
25
|
+
|
|
26
|
+
use super::{
|
|
27
|
+
message_trace::{CallMessageTrace, CreateMessageTrace, EvmStep},
|
|
28
|
+
solidity_stack_trace::{
|
|
29
|
+
InvalidParamsErrorStackTraceEntry, NonContractAccountCalledErrorStackTraceEntry,
|
|
30
|
+
RevertErrorStackTraceEntry, SolidityStackTrace, StackTraceEntryTypeConst,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS: Version = Version::new(0, 6, 9);
|
|
35
|
+
|
|
36
|
+
pub fn stack_trace_may_require_adjustments(
|
|
37
|
+
stacktrace: &SolidityStackTrace,
|
|
38
|
+
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
39
|
+
) -> bool {
|
|
40
|
+
let bytecode = match &decoded_trace {
|
|
41
|
+
Either::A(create) => &create.bytecode,
|
|
42
|
+
Either::B(call) => &call.bytecode,
|
|
43
|
+
};
|
|
44
|
+
let bytecode = bytecode.as_ref().expect("JS code asserts");
|
|
45
|
+
|
|
46
|
+
let Some(last_frame) = stacktrace.last() else {
|
|
47
|
+
return false;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if let Either24::E(last_frame @ RevertErrorStackTraceEntry { .. }) = last_frame {
|
|
51
|
+
return !last_frame.is_invalid_opcode_error
|
|
52
|
+
&& last_frame.return_data.is_empty()
|
|
53
|
+
&& Version::parse(&bytecode.compiler_version)
|
|
54
|
+
.map(|version| version >= FIRST_SOLC_VERSION_WITH_MAPPED_SMALL_INTERNAL_FUNCTIONS)
|
|
55
|
+
.unwrap_or(false);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub fn adjust_stack_trace(
|
|
62
|
+
mut stacktrace: SolidityStackTrace,
|
|
63
|
+
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
64
|
+
) -> napi::Result<SolidityStackTrace> {
|
|
65
|
+
let Some(Either24::E(revert @ RevertErrorStackTraceEntry { .. })) = stacktrace.last() else {
|
|
66
|
+
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");
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Replace the last revert frame with an adjusted frame if needed
|
|
70
|
+
if is_non_contract_account_called_error(decoded_trace)? {
|
|
71
|
+
let last_revert_frame_source_reference = revert.source_reference.clone();
|
|
72
|
+
stacktrace.pop();
|
|
73
|
+
stacktrace.push(
|
|
74
|
+
NonContractAccountCalledErrorStackTraceEntry {
|
|
75
|
+
type_: StackTraceEntryTypeConst,
|
|
76
|
+
source_reference: last_revert_frame_source_reference,
|
|
77
|
+
}
|
|
78
|
+
.into(),
|
|
79
|
+
);
|
|
80
|
+
return Ok(stacktrace);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if is_constructor_invalid_params_error(decoded_trace)? {
|
|
84
|
+
let last_revert_frame_source_reference = revert.source_reference.clone();
|
|
85
|
+
stacktrace.pop();
|
|
86
|
+
stacktrace.push(
|
|
87
|
+
InvalidParamsErrorStackTraceEntry {
|
|
88
|
+
type_: StackTraceEntryTypeConst,
|
|
89
|
+
source_reference: last_revert_frame_source_reference,
|
|
90
|
+
}
|
|
91
|
+
.into(),
|
|
92
|
+
);
|
|
93
|
+
return Ok(stacktrace);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if is_call_invalid_params_error(decoded_trace)? {
|
|
97
|
+
let last_revert_frame_source_reference = revert.source_reference.clone();
|
|
98
|
+
stacktrace.pop();
|
|
99
|
+
stacktrace.push(
|
|
100
|
+
InvalidParamsErrorStackTraceEntry {
|
|
101
|
+
type_: StackTraceEntryTypeConst,
|
|
102
|
+
source_reference: last_revert_frame_source_reference,
|
|
103
|
+
}
|
|
104
|
+
.into(),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return Ok(stacktrace);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Ok(stacktrace)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn is_non_contract_account_called_error(
|
|
114
|
+
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
115
|
+
) -> napi::Result<bool> {
|
|
116
|
+
match_opcodes(
|
|
117
|
+
decoded_trace,
|
|
118
|
+
-9,
|
|
119
|
+
&[
|
|
120
|
+
OpCode::EXTCODESIZE,
|
|
121
|
+
OpCode::ISZERO,
|
|
122
|
+
OpCode::DUP1,
|
|
123
|
+
OpCode::ISZERO,
|
|
124
|
+
],
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fn is_constructor_invalid_params_error(
|
|
129
|
+
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
130
|
+
) -> napi::Result<bool> {
|
|
131
|
+
Ok(match_opcodes(decoded_trace, -20, &[OpCode::CODESIZE])?
|
|
132
|
+
&& match_opcodes(decoded_trace, -15, &[OpCode::CODECOPY])?
|
|
133
|
+
&& match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fn is_call_invalid_params_error(
|
|
137
|
+
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
138
|
+
) -> napi::Result<bool> {
|
|
139
|
+
Ok(match_opcodes(decoded_trace, -11, &[OpCode::CALLDATASIZE])?
|
|
140
|
+
&& match_opcodes(decoded_trace, -7, &[OpCode::LT, OpCode::ISZERO])?)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn match_opcodes(
|
|
144
|
+
decoded_trace: Either<&CallMessageTrace, &CreateMessageTrace>,
|
|
145
|
+
first_step_index: i32,
|
|
146
|
+
opcodes: &[OpCode],
|
|
147
|
+
) -> napi::Result<bool> {
|
|
148
|
+
let (bytecode, steps) = match &decoded_trace {
|
|
149
|
+
Either::A(call) => (&call.bytecode, &call.steps),
|
|
150
|
+
Either::B(create) => (&create.bytecode, &create.steps),
|
|
151
|
+
};
|
|
152
|
+
let bytecode = bytecode.as_ref().expect("JS code asserts");
|
|
153
|
+
|
|
154
|
+
// If the index is negative, we start from the end of the trace,
|
|
155
|
+
// just like in the original JS code
|
|
156
|
+
let mut index = match first_step_index {
|
|
157
|
+
0.. => first_step_index as usize,
|
|
158
|
+
..=-1 if first_step_index.abs() < steps.len() as i32 => {
|
|
159
|
+
(steps.len() as i32 + first_step_index) as usize
|
|
160
|
+
}
|
|
161
|
+
// Out of bounds
|
|
162
|
+
_ => return Ok(false),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
for opcode in opcodes {
|
|
166
|
+
let Some(Either4::A(EvmStep { pc })) = steps.get(index) else {
|
|
167
|
+
return Ok(false);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
let instruction = bytecode.get_instruction(*pc)?;
|
|
171
|
+
|
|
172
|
+
if instruction.opcode != *opcode {
|
|
173
|
+
return Ok(false);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
index += 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
Ok(true)
|
|
180
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
//! Bridging type for the existing `MessageTrace` interface in Hardhat.
|
|
2
|
+
|
|
3
|
+
use napi::{
|
|
4
|
+
bindgen_prelude::{BigInt, ClassInstance, Either3, Either4, Uint8Array, Undefined},
|
|
5
|
+
Either, Env,
|
|
6
|
+
};
|
|
7
|
+
use napi_derive::napi;
|
|
8
|
+
|
|
9
|
+
use super::{exit::Exit, model::BytecodeWrapper};
|
|
10
|
+
|
|
11
|
+
#[napi(object)]
|
|
12
|
+
pub struct EvmStep {
|
|
13
|
+
pub pc: u32,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[napi(object)]
|
|
17
|
+
pub struct PrecompileMessageTrace {
|
|
18
|
+
// `BaseMessageTrace`
|
|
19
|
+
pub value: BigInt,
|
|
20
|
+
pub return_data: Uint8Array,
|
|
21
|
+
pub exit: ClassInstance<Exit>,
|
|
22
|
+
pub gas_used: BigInt,
|
|
23
|
+
pub depth: u32,
|
|
24
|
+
// `PrecompileMessageTrace`
|
|
25
|
+
pub precompile: u32,
|
|
26
|
+
pub calldata: Uint8Array,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// NOTE: Because of the hack below for `deployed_contract`, now the
|
|
30
|
+
// `CallMessageTrace` is a strict superset of `CreateMessageTrace`, so we need
|
|
31
|
+
// to take care to keep the order consistent from most-specific to
|
|
32
|
+
// least-specific in the `Either{3,4}` type when converting to or from N-API.
|
|
33
|
+
#[napi(object)]
|
|
34
|
+
pub struct CreateMessageTrace {
|
|
35
|
+
// `BaseMessageTrace`
|
|
36
|
+
pub value: BigInt,
|
|
37
|
+
pub return_data: Uint8Array,
|
|
38
|
+
pub exit: ClassInstance<Exit>,
|
|
39
|
+
pub gas_used: BigInt,
|
|
40
|
+
pub depth: u32,
|
|
41
|
+
// `BaseEvmMessageTrace`
|
|
42
|
+
pub code: Uint8Array,
|
|
43
|
+
pub steps: Vec<Either4<EvmStep, PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>>,
|
|
44
|
+
/// Reference to the resolved `Bytecode` EDR data.
|
|
45
|
+
/// Only used on the JS side by the `VmTraceDecoder` class.
|
|
46
|
+
pub bytecode: Option<ClassInstance<BytecodeWrapper>>,
|
|
47
|
+
pub number_of_subtraces: u32,
|
|
48
|
+
// `CreateMessageTrace`
|
|
49
|
+
// HACK: It seems that `Either<Uint8Array, Undefined>` means exactly what we
|
|
50
|
+
// want (a required property but can be explicitly `undefined`) but internally
|
|
51
|
+
// the napi-rs treats an encountered `Undefined` like a missing property
|
|
52
|
+
// and it throws a validation error. While not 100% backwards compatible, we
|
|
53
|
+
// work around using an optional type.
|
|
54
|
+
// See https://github.com/napi-rs/napi-rs/issues/1986 for context on the PR
|
|
55
|
+
// that introduced this behavior.
|
|
56
|
+
pub deployed_contract: Option<Either<Uint8Array, Undefined>>,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[napi(object)]
|
|
60
|
+
pub struct CallMessageTrace {
|
|
61
|
+
// `BaseMessageTrace`
|
|
62
|
+
pub value: BigInt,
|
|
63
|
+
pub return_data: Uint8Array,
|
|
64
|
+
pub exit: ClassInstance<Exit>,
|
|
65
|
+
pub gas_used: BigInt,
|
|
66
|
+
pub depth: u32,
|
|
67
|
+
// `BaseEvmMessageTrace`
|
|
68
|
+
pub code: Uint8Array,
|
|
69
|
+
pub steps: Vec<Either4<EvmStep, PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>>,
|
|
70
|
+
/// Reference to the resolved `Bytecode` EDR data.
|
|
71
|
+
/// Only used on the JS side by the `VmTraceDecoder` class.
|
|
72
|
+
pub bytecode: Option<ClassInstance<BytecodeWrapper>>,
|
|
73
|
+
pub number_of_subtraces: u32,
|
|
74
|
+
// `CallMessageTrace`
|
|
75
|
+
pub calldata: Uint8Array,
|
|
76
|
+
pub address: Uint8Array,
|
|
77
|
+
pub code_address: Uint8Array,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Converts [`edr_solidity::message_trace::MessageTraceStep`] to the N-API
|
|
81
|
+
/// representation.
|
|
82
|
+
///
|
|
83
|
+
/// # Panics
|
|
84
|
+
/// This function will panic if the value is mutably borrowed.
|
|
85
|
+
pub fn message_trace_step_to_napi(
|
|
86
|
+
value: edr_solidity::message_trace::MessageTraceStep,
|
|
87
|
+
env: Env,
|
|
88
|
+
) -> napi::Result<Either4<EvmStep, PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>> {
|
|
89
|
+
Ok(match value {
|
|
90
|
+
edr_solidity::message_trace::MessageTraceStep::Evm(step) => {
|
|
91
|
+
Either4::A(EvmStep { pc: step.pc as u32 })
|
|
92
|
+
}
|
|
93
|
+
edr_solidity::message_trace::MessageTraceStep::Message(msg) => {
|
|
94
|
+
// Immediately drop the borrow lock to err on the safe side as we
|
|
95
|
+
// may be recursing.
|
|
96
|
+
let owned = msg.borrow().clone();
|
|
97
|
+
match message_trace_to_napi(owned, env)? {
|
|
98
|
+
Either3::A(precompile) => Either4::B(precompile),
|
|
99
|
+
Either3::B(call) => Either4::C(call),
|
|
100
|
+
Either3::C(create) => Either4::D(create),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Converts the Rust representation of a `MessageTrace` to the N-API
|
|
107
|
+
/// representation.
|
|
108
|
+
pub fn message_trace_to_napi(
|
|
109
|
+
value: edr_solidity::message_trace::MessageTrace,
|
|
110
|
+
env: Env,
|
|
111
|
+
) -> napi::Result<Either3<PrecompileMessageTrace, CallMessageTrace, CreateMessageTrace>> {
|
|
112
|
+
Ok(match value {
|
|
113
|
+
edr_solidity::message_trace::MessageTrace::Precompile(precompile) => {
|
|
114
|
+
Either3::A(PrecompileMessageTrace {
|
|
115
|
+
value: BigInt {
|
|
116
|
+
sign_bit: false,
|
|
117
|
+
words: precompile.base.value.as_limbs().to_vec(),
|
|
118
|
+
},
|
|
119
|
+
return_data: Uint8Array::from(precompile.base.return_data.as_ref()),
|
|
120
|
+
exit: Exit(precompile.base.exit.into()).into_instance(env)?,
|
|
121
|
+
gas_used: BigInt::from(precompile.base.gas_used),
|
|
122
|
+
depth: precompile.base.depth as u32,
|
|
123
|
+
|
|
124
|
+
precompile: precompile.precompile,
|
|
125
|
+
calldata: Uint8Array::from(precompile.calldata.as_ref()),
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
edr_solidity::message_trace::MessageTrace::Call(call) => Either3::B(CallMessageTrace {
|
|
129
|
+
value: BigInt {
|
|
130
|
+
sign_bit: false,
|
|
131
|
+
words: call.base.base.value.as_limbs().to_vec(),
|
|
132
|
+
},
|
|
133
|
+
return_data: Uint8Array::from(call.base.base.return_data.as_ref()),
|
|
134
|
+
exit: Exit(call.base.base.exit.into()).into_instance(env)?,
|
|
135
|
+
gas_used: BigInt::from(call.base.base.gas_used),
|
|
136
|
+
depth: call.base.base.depth as u32,
|
|
137
|
+
code: Uint8Array::from(call.base.code.as_ref()),
|
|
138
|
+
steps: call
|
|
139
|
+
.base
|
|
140
|
+
.steps
|
|
141
|
+
.into_iter()
|
|
142
|
+
.map(|step| message_trace_step_to_napi(step, env))
|
|
143
|
+
.collect::<napi::Result<Vec<_>>>()?,
|
|
144
|
+
// NOTE: We specifically use None as that will be later filled on the JS side
|
|
145
|
+
bytecode: None,
|
|
146
|
+
number_of_subtraces: call.base.number_of_subtraces,
|
|
147
|
+
|
|
148
|
+
address: Uint8Array::from(call.address.as_slice()),
|
|
149
|
+
calldata: Uint8Array::from(call.calldata.as_ref()),
|
|
150
|
+
code_address: Uint8Array::from(call.code_address.as_slice()),
|
|
151
|
+
}),
|
|
152
|
+
edr_solidity::message_trace::MessageTrace::Create(create) => {
|
|
153
|
+
Either3::C(CreateMessageTrace {
|
|
154
|
+
value: BigInt {
|
|
155
|
+
sign_bit: false,
|
|
156
|
+
words: create.base.base.value.as_limbs().to_vec(),
|
|
157
|
+
},
|
|
158
|
+
return_data: Uint8Array::from(create.base.base.return_data.as_ref()),
|
|
159
|
+
exit: Exit(create.base.base.exit.into()).into_instance(env)?,
|
|
160
|
+
gas_used: BigInt::from(create.base.base.gas_used),
|
|
161
|
+
depth: create.base.base.depth as u32,
|
|
162
|
+
code: Uint8Array::from(create.base.code.as_ref()),
|
|
163
|
+
steps: create
|
|
164
|
+
.base
|
|
165
|
+
.steps
|
|
166
|
+
.into_iter()
|
|
167
|
+
.map(|step| message_trace_step_to_napi(step, env))
|
|
168
|
+
.collect::<napi::Result<Vec<_>>>()?,
|
|
169
|
+
// NOTE: We specifically use None as that will be later filled on the JS side
|
|
170
|
+
bytecode: None,
|
|
171
|
+
|
|
172
|
+
number_of_subtraces: create.base.number_of_subtraces,
|
|
173
|
+
deployed_contract: create
|
|
174
|
+
.deployed_contract
|
|
175
|
+
.map(|contract| Either::A(Uint8Array::from(contract.as_ref()))),
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
use std::rc::Rc;
|
|
2
|
+
|
|
3
|
+
use edr_solidity::build_model::Bytecode;
|
|
4
|
+
use napi_derive::napi;
|
|
5
|
+
use serde::Serialize;
|
|
6
|
+
|
|
7
|
+
/// Opaque handle to the `Bytecode` struct.
|
|
8
|
+
/// Only used on the JS side by the `VmTraceDecoder` class.
|
|
9
|
+
// NOTE: Needed, because we store the resolved `Bytecode` in the MessageTrace
|
|
10
|
+
// JS plain objects and those need a dedicated (class) type.
|
|
11
|
+
#[napi]
|
|
12
|
+
pub struct BytecodeWrapper(pub(crate) Rc<Bytecode>);
|
|
13
|
+
|
|
14
|
+
impl BytecodeWrapper {
|
|
15
|
+
pub fn new(bytecode: Rc<Bytecode>) -> Self {
|
|
16
|
+
Self(bytecode)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub fn inner(&self) -> &Rc<Bytecode> {
|
|
20
|
+
&self.0
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl std::ops::Deref for BytecodeWrapper {
|
|
25
|
+
type Target = Bytecode;
|
|
26
|
+
|
|
27
|
+
fn deref(&self) -> &Self::Target {
|
|
28
|
+
&self.0
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[derive(Debug, PartialEq, Eq, Serialize)]
|
|
33
|
+
#[allow(non_camel_case_types)] // intentionally mimicks the original case in TS
|
|
34
|
+
#[allow(clippy::upper_case_acronyms)]
|
|
35
|
+
#[napi]
|
|
36
|
+
// Mimicks [`edr_solidity::build_model::ContractFunctionType`].
|
|
37
|
+
pub enum ContractFunctionType {
|
|
38
|
+
CONSTRUCTOR,
|
|
39
|
+
FUNCTION,
|
|
40
|
+
FALLBACK,
|
|
41
|
+
RECEIVE,
|
|
42
|
+
GETTER,
|
|
43
|
+
MODIFIER,
|
|
44
|
+
FREE_FUNCTION,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl From<edr_solidity::build_model::ContractFunctionType> for ContractFunctionType {
|
|
48
|
+
fn from(value: edr_solidity::build_model::ContractFunctionType) -> Self {
|
|
49
|
+
match value {
|
|
50
|
+
edr_solidity::build_model::ContractFunctionType::Constructor => Self::CONSTRUCTOR,
|
|
51
|
+
edr_solidity::build_model::ContractFunctionType::Function => Self::FUNCTION,
|
|
52
|
+
edr_solidity::build_model::ContractFunctionType::Fallback => Self::FALLBACK,
|
|
53
|
+
edr_solidity::build_model::ContractFunctionType::Receive => Self::RECEIVE,
|
|
54
|
+
edr_solidity::build_model::ContractFunctionType::Getter => Self::GETTER,
|
|
55
|
+
edr_solidity::build_model::ContractFunctionType::Modifier => Self::MODIFIER,
|
|
56
|
+
edr_solidity::build_model::ContractFunctionType::FreeFunction => Self::FREE_FUNCTION,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//! Rewrite of `hardhat-network/provider/return-data.ts` from Hardhat.
|
|
2
|
+
|
|
3
|
+
use alloy_sol_types::SolError;
|
|
4
|
+
use napi::bindgen_prelude::{BigInt, Uint8Array};
|
|
5
|
+
use napi_derive::napi;
|
|
6
|
+
|
|
7
|
+
// Built-in error types
|
|
8
|
+
// See <https://docs.soliditylang.org/en/v0.8.26/control-structures.html#error-handling-assert-require-revert-and-exceptions>
|
|
9
|
+
alloy_sol_types::sol! {
|
|
10
|
+
error Error(string);
|
|
11
|
+
error Panic(uint256);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#[napi]
|
|
15
|
+
pub struct ReturnData {
|
|
16
|
+
#[napi(readonly)]
|
|
17
|
+
pub value: Uint8Array,
|
|
18
|
+
selector: Option<[u8; 4]>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[napi]
|
|
22
|
+
impl ReturnData {
|
|
23
|
+
#[napi(constructor)]
|
|
24
|
+
pub fn new(value: Uint8Array) -> Self {
|
|
25
|
+
let selector = if value.len() >= 4 {
|
|
26
|
+
Some(value[0..4].try_into().unwrap())
|
|
27
|
+
} else {
|
|
28
|
+
None
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Self { value, selector }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[napi]
|
|
35
|
+
pub fn is_empty(&self) -> bool {
|
|
36
|
+
self.value.is_empty()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn matches_selector(&self, selector: impl AsRef<[u8]>) -> bool {
|
|
40
|
+
self.selector
|
|
41
|
+
.map_or(false, |value| value == selector.as_ref())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[napi]
|
|
45
|
+
pub fn is_error_return_data(&self) -> bool {
|
|
46
|
+
self.selector == Some(Error::SELECTOR)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[napi]
|
|
50
|
+
pub fn is_panic_return_data(&self) -> bool {
|
|
51
|
+
self.selector == Some(Panic::SELECTOR)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[napi]
|
|
55
|
+
pub fn decode_error(&self) -> napi::Result<String> {
|
|
56
|
+
if self.is_empty() {
|
|
57
|
+
return Ok(String::new());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let result = Error::abi_decode(&self.value[..], false).map_err(|_err| {
|
|
61
|
+
napi::Error::new(
|
|
62
|
+
napi::Status::InvalidArg,
|
|
63
|
+
"Expected return data to be a Error(string) and contain a valid string",
|
|
64
|
+
)
|
|
65
|
+
})?;
|
|
66
|
+
|
|
67
|
+
Ok(result._0)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[napi]
|
|
71
|
+
pub fn decode_panic(&self) -> napi::Result<BigInt> {
|
|
72
|
+
let result = Panic::abi_decode(&self.value[..], false).map_err(|_err| {
|
|
73
|
+
napi::Error::new(
|
|
74
|
+
napi::Status::InvalidArg,
|
|
75
|
+
"Expected return data to be a Error(string) and contain a valid string",
|
|
76
|
+
)
|
|
77
|
+
})?;
|
|
78
|
+
|
|
79
|
+
Ok(BigInt {
|
|
80
|
+
sign_bit: false,
|
|
81
|
+
words: result._0.as_limbs().to_vec(),
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|