Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port MessageTrace and VMTracer machinery from Hardhat #531

Merged
merged 9 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions crates/edr_napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ 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
}
Expand Down Expand Up @@ -652,6 +653,63 @@ export function getPushLength(opcode: Opcode): number
export function getOpcodeLength(opcode: Opcode): number
export function isCall(opcode: Opcode): boolean
export function isCreate(opcode: Opcode): boolean
/** Represents the exit code of the EVM. */
export const enum ExitCode {
/** Execution was successful. */
SUCCESS = 0,
/** Execution was reverted. */
REVERT = 1,
/** Execution ran out of gas. */
OUT_OF_GAS = 2,
/** Execution encountered an internal error. */
INTERNAL_ERROR = 3,
/** Execution encountered an invalid opcode. */
INVALID_OPCODE = 4,
/** Execution encountered a stack underflow. */
STACK_UNDERFLOW = 5,
/** Create init code size exceeds limit (runtime). */
CODESIZE_EXCEEDS_MAXIMUM = 6,
/** Create collision. */
CREATE_COLLISION = 7
}
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<EvmStep | PrecompileMessageTrace | CreateMessageTrace | CallMessageTrace>
bytecode?: any
numberOfSubtraces: number
deployedContract: Uint8Array | undefined
}
export interface CallMessageTrace {
value: bigint
returnData: Uint8Array
exit: Exit
gasUsed: bigint
depth: number
code: Uint8Array
steps: Array<EvmStep | PrecompileMessageTrace | CreateMessageTrace | CallMessageTrace>
bytecode?: any
numberOfSubtraces: number
calldata: Uint8Array
address: Uint8Array
codeAddress: Uint8Array
}
export interface TracingMessage {
/** Sender address */
readonly caller: Buffer
Expand Down Expand Up @@ -783,6 +841,20 @@ export class Contract {
get receive(): ContractFunction | undefined
getFunctionFromSelector(selector: Uint8Array): ContractFunction | undefined
}
export class Exit {
get kind(): number
isError(): boolean
getReason(): string
}
export type VMTracer = VmTracer
/** N-API bindings for the Rust port of `VMTracer` from Hardhat. */
export class VmTracer {
constructor()
/** Observes a trace, collecting information about the execution of the EVM. */
observe(trace: RawTrace): void
getLastTopLevelMessageTrace(): PrecompileMessageTrace | CreateMessageTrace | CallMessageTrace | undefined
getLastError(): Error | undefined
}
export class RawTrace {
trace(): Array<TracingMessage | TracingStep | TracingMessageResult>
}
5 changes: 4 additions & 1 deletion crates/edr_napi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, createModelsAndDecodeBytecodes, getLibraryAddressPositions, linkHexStringBytecode, SourceFile, SourceLocation, ContractFunctionType, ContractFunctionVisibility, ContractFunction, CustomError, Instruction, JumpType, jumpTypeToString, Bytecode, ContractType, Contract, Opcode, opcodeToString, isPush, isJump, getPushLength, getOpcodeLength, isCall, isCreate, RawTrace } = nativeBinding
const { SpecId, EdrContext, MineOrdering, Provider, Response, SuccessReason, ExceptionalHalt, createModelsAndDecodeBytecodes, getLibraryAddressPositions, linkHexStringBytecode, SourceFile, SourceLocation, ContractFunctionType, ContractFunctionVisibility, ContractFunction, CustomError, Instruction, JumpType, jumpTypeToString, Bytecode, ContractType, Contract, Opcode, opcodeToString, isPush, isJump, getPushLength, getOpcodeLength, isCall, isCreate, Exit, ExitCode, VmTracer, RawTrace } = nativeBinding

module.exports.SpecId = SpecId
module.exports.EdrContext = EdrContext
Expand Down Expand Up @@ -342,4 +342,7 @@ module.exports.getPushLength = getPushLength
module.exports.getOpcodeLength = getOpcodeLength
module.exports.isCall = isCall
module.exports.isCreate = isCreate
module.exports.Exit = Exit
module.exports.ExitCode = ExitCode
module.exports.VmTracer = VmTracer
module.exports.RawTrace = RawTrace
1 change: 1 addition & 0 deletions crates/edr_napi/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct LoggerConfig {
#[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,
Expand Down
5 changes: 4 additions & 1 deletion crates/edr_napi/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ mod library_utils;
mod model;
mod source_map;

mod exit;
mod message_trace;
mod opcodes;
mod vm_tracer;

#[napi(object)]
pub struct TracingMessage {
Expand Down Expand Up @@ -165,7 +168,7 @@ pub struct TracingMessageResult {

#[napi]
pub struct RawTrace {
inner: Arc<edr_evm::trace::Trace>,
pub(crate) inner: Arc<edr_evm::trace::Trace>,
}

impl RawTrace {
Expand Down
86 changes: 86 additions & 0 deletions crates/edr_napi/src/trace/exit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! Naive rewrite of `hardhat-network/provider/vm/exit.ts` from Hardhat.
//! Used together with `VmTracer`.

use std::fmt;

use edr_evm::HaltReason;
use napi_derive::napi;

#[napi]
pub struct Exit(pub(crate) ExitCode);

#[napi]
/// Represents the exit code of the EVM.
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms, non_camel_case_types)] // These are exported and mapped 1:1 to existing JS enum
pub enum ExitCode {
/// Execution was successful.
SUCCESS = 0,
/// Execution was reverted.
REVERT,
/// Execution ran out of gas.
OUT_OF_GAS,
/// Execution encountered an internal error.
INTERNAL_ERROR,
/// Execution encountered an invalid opcode.
INVALID_OPCODE,
/// Execution encountered a stack underflow.
STACK_UNDERFLOW,
/// Create init code size exceeds limit (runtime).
CODESIZE_EXCEEDS_MAXIMUM,
/// Create collision.
CREATE_COLLISION,
}

impl fmt::Display for ExitCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExitCode::SUCCESS => write!(f, "Success"),
ExitCode::REVERT => write!(f, "Reverted"),
ExitCode::OUT_OF_GAS => write!(f, "Out of gas"),
ExitCode::INTERNAL_ERROR => write!(f, "Internal error"),
ExitCode::INVALID_OPCODE => write!(f, "Invalid opcode"),
ExitCode::STACK_UNDERFLOW => write!(f, "Stack underflow"),
ExitCode::CODESIZE_EXCEEDS_MAXIMUM => write!(f, "Codesize exceeds maximum"),
ExitCode::CREATE_COLLISION => write!(f, "Create collision"),
}
}
}

#[allow(clippy::fallible_impl_from)] // naively ported for now
impl From<edr_solidity::message_trace::ExitCode> for ExitCode {
fn from(code: edr_solidity::message_trace::ExitCode) -> Self {
use edr_solidity::message_trace::ExitCode;

match code {
ExitCode::Success => Self::SUCCESS,
ExitCode::Revert => Self::REVERT,
ExitCode::Halt(HaltReason::OutOfGas(_)) => Self::OUT_OF_GAS,
ExitCode::Halt(HaltReason::OpcodeNotFound | HaltReason::InvalidFEOpcode
// Returned when an opcode is not implemented for the hardfork
| HaltReason::NotActivated) => Self::INVALID_OPCODE,
ExitCode::Halt(HaltReason::StackUnderflow) => Self::STACK_UNDERFLOW,
ExitCode::Halt(HaltReason::CreateContractSizeLimit) => Self::CODESIZE_EXCEEDS_MAXIMUM,
ExitCode::Halt(HaltReason::CreateCollision) => Self::CREATE_COLLISION,
halt @ ExitCode::Halt(_) => panic!("Unmatched EDR exceptional halt: {halt:?}"),
}
}
}

#[napi]
impl Exit {
#[napi(getter)]
pub fn kind(&self) -> u8 {
Wodann marked this conversation as resolved.
Show resolved Hide resolved
self.0 as u8
}

#[napi]
pub fn is_error(&self) -> bool {
!matches!(self.0, ExitCode::SUCCESS)
}

#[napi]
pub fn get_reason(&self) -> String {
self.0.to_string()
}
}
Loading
Loading