From 8c1ea6fc4111b9beb3f21a6188eeb26b09efebbd Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 28 Dec 2022 20:00:23 +0100 Subject: [PATCH 01/31] Boilerplate for rethnet tracing callbacks --- .../hardhat-network/provider/vm/dual.ts | 205 +++++++++++++++++- .../hardhat-network/provider/vm/rethnet.ts | 69 +++++- 2 files changed, 263 insertions(+), 11 deletions(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index b49d5e94f9..c1ffaf825d 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -7,6 +7,7 @@ import { Address, bufferToHex, } from "@nomicfoundation/ethereumjs-util"; +import { TracingMessage, TracingMessageResult, TracingStep } from "rethnet-evm"; import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; @@ -36,6 +37,8 @@ function printRethnetTrace(trace: any) { } export class DualModeAdapter implements VMAdapter { + private _tracingCallbacks: TracingCallbacks | undefined; + constructor( private _ethereumJSAdapter: VMAdapter, private _rethnetAdapter: VMAdapter @@ -73,15 +76,21 @@ export class DualModeAdapter implements VMAdapter { blockContext: Block, forceBaseFeeZero?: boolean ): Promise<[RunTxResult, Trace]> { - const [ethereumJSResult, ethereumJSTrace] = - await this._ethereumJSAdapter.dryRun(tx, blockContext, forceBaseFeeZero); + const ethereumJSResultPromise = this._ethereumJSAdapter.dryRun( + tx, + blockContext, + forceBaseFeeZero + ); - const [rethnetResult, rethnetTrace] = await this._rethnetAdapter.dryRun( + const rethnetResultPromise = this._rethnetAdapter.dryRun( tx, blockContext, forceBaseFeeZero ); + const [[ethereumJSResult, ethereumJSTrace], [rethnetResult, rethnetTrace]] = + await Promise.all([ethereumJSResultPromise, rethnetResultPromise]); + try { assertEqualRunTxResults(ethereumJSResult, rethnetResult); return [rethnetResult, rethnetTrace]; @@ -212,11 +221,26 @@ export class DualModeAdapter implements VMAdapter { } public enableTracing(callbacks: TracingCallbacks): void { - return this._ethereumJSAdapter.enableTracing(callbacks); + this._tracingCallbacks = callbacks; + + this._ethereumJSAdapter.enableTracing({ + beforeMessage: this._ethereumJSBeforeMessagehandler, + step: this._ethereumJSStepHandler, + afterMessage: this._ethereumJSAfterMessageHandler, + }); + + this._rethnetAdapter.enableTracing({ + beforeMessage: this._rethnetBeforeMessagehandler, + step: this._rethnetStepHandler, + afterMessage: this._rethnetAfterMessageHandler, + }); } public disableTracing(): void { - return this._ethereumJSAdapter.disableTracing(); + this._tracingCallbacks = undefined; + + this._ethereumJSAdapter.disableTracing(); + this._rethnetAdapter.disableTracing(); } public async setBlockContext( @@ -243,11 +267,15 @@ export class DualModeAdapter implements VMAdapter { tx: TypedTransaction, block: Block ): Promise<[RunTxResult, Trace]> { - const [ethereumJSResult, ethereumJSTrace] = - await this._ethereumJSAdapter.runTxInBlock(tx, block); + const ethereumJSResultPromise = this._ethereumJSAdapter.runTxInBlock( + tx, + block + ); + + const rethnetResultPromise = this._rethnetAdapter.runTxInBlock(tx, block); - const [rethnetResult, rethnetTrace] = - await this._rethnetAdapter.runTxInBlock(tx, block); + const [[ethereumJSResult, ethereumJSTrace], [rethnetResult, rethnetTrace]] = + await Promise.all([ethereumJSResultPromise, rethnetResultPromise]); try { assertEqualRunTxResults(ethereumJSResult, rethnetResult); @@ -301,6 +329,165 @@ export class DualModeAdapter implements VMAdapter { return rethnetRoot; } + + private _currentBeforeMessage: TracingMessage | undefined; + private _currentBeforeMessageNext: any; + private _currentStep: TracingStep | undefined; + private _currentStepNext: any; + private _currentMessageResult: TracingMessageResult | undefined; + private _currentMessageResultNext: any; + + private _ethereumJSBeforeMessagehandler = async ( + message: TracingMessage, + next: any + ) => { + return this._beforeMessageHandler(message, next, true); + }; + + private _rethnetBeforeMessagehandler = async ( + message: TracingMessage, + next: any + ) => { + return this._beforeMessageHandler(message, next, false); + }; + + private _beforeMessageHandler = async ( + message: TracingMessage, + next: any, + isEthJS: boolean + ) => { + if (this._tracingCallbacks === undefined) { + return next(); + } + + if (this._currentBeforeMessage === undefined) { + // this method executed first, save results + this._currentBeforeMessage = message; + this._currentBeforeMessageNext = next; + } else { + // this method executed second, compare results + if (!message.data.equals(this._currentBeforeMessage.data)) { + const current = isEthJS ? "ethereumjs" : "rethnet"; + const previous = isEthJS ? "rethnet" : "ethereumjs"; + const errorMessage = `Different data in before message handler, ${current}: '${message.data.toString( + "hex" + )}', ${previous}: '${this._currentBeforeMessage.data.toString("hex")}'`; + + // both log and send the error, because the error message sometimes is + // swallowed by the tests + console.log("==========>", errorMessage); + next(new Error(errorMessage)); + } + + // continue the execution of the other adapter + this._currentBeforeMessageNext(); + + // clean the state + this._currentBeforeMessage = undefined; + this._currentBeforeMessageNext = undefined; + + return this._tracingCallbacks.beforeMessage(message, next); + } + }; + + private _ethereumJSStepHandler = async (step: TracingStep, next: any) => { + return this._stepHandler(step, next, true); + }; + + private _rethnetStepHandler = async (step: TracingStep, next: any) => { + return this._stepHandler(step, next, false); + }; + + private _stepHandler = async ( + step: TracingStep, + next: any, + isEthJS: boolean + ) => { + if (this._tracingCallbacks === undefined) { + return next(); + } + + if (this._currentStep === undefined) { + // this method executed first, save results + this._currentStep = step; + this._currentStepNext = next; + } else { + // this method executed second, compare results + if (step.pc !== this._currentStep.pc) { + const current = isEthJS ? "ethereumjs" : "rethnet"; + const previous = isEthJS ? "rethnet" : "ethereumjs"; + const errorMessage = `Different pc in step handler, ${current}: '${step.pc}', ${previous}: '${this._currentStep.pc}'`; + + // both log and send the error, because the error message sometimes is + // swallowed by the tests + console.log("==========>", errorMessage); + next(new Error(errorMessage)); + } + + // continue the execution of the other adapter + this._currentStepNext(); + + // clean the state + this._currentStep = undefined; + this._currentStepNext = undefined; + + return this._tracingCallbacks.step(step, next); + } + }; + + private _ethereumJSAfterMessageHandler = async ( + result: TracingMessageResult, + next: any + ) => { + return this._afterMessageHandler(result, next, true); + }; + + private _rethnetAfterMessageHandler = async ( + result: TracingMessageResult, + next: any + ) => { + return this._afterMessageHandler(result, next, false); + }; + + private _afterMessageHandler = async ( + result: TracingMessageResult, + next: any, + isEthJS: boolean + ) => { + if (this._tracingCallbacks === undefined) { + return next(); + } + + if (this._currentMessageResult === undefined) { + // this method executed first, save results + this._currentMessageResult = result; + this._currentMessageResultNext = next; + } else { + // this method executed second, compare results + if ( + result.executionResult.exitCode !== + this._currentMessageResult.executionResult.exitCode + ) { + const current = isEthJS ? "ethereumjs" : "rethnet"; + const previous = isEthJS ? "rethnet" : "ethereumjs"; + const errorMessage = `Different exit codes in after message handler, ${current}: '${result.executionResult.exitCode}', ${previous}: '${this._currentMessageResult.executionResult.exitCode}'`; + + // both log and send the error, because the error message sometimes is + // swallowed by the tests + console.log("==========>", errorMessage); + next(new Error(errorMessage)); + } + + // continue the execution of the other adapter + this._currentMessageResultNext(); + + // clean the state + this._currentMessageResult = undefined; + this._currentMessageResultNext = undefined; + + return this._tracingCallbacks.afterMessage(result, next); + } + }; } function assertEqualRunTxResults( diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 084dc661eb..fa0e3b9361 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -21,6 +21,8 @@ import { RunTxResult, Trace, TracingCallbacks, VMAdapter } from "./vm-adapter"; /* eslint-disable @typescript-eslint/no-unused-vars */ export class RethnetAdapter implements VMAdapter { + private _tracingCallbacks: TracingCallbacks | undefined; + constructor( private _blockchain: Blockchain, private _state: RethnetStateManager, @@ -291,14 +293,56 @@ export class RethnetAdapter implements VMAdapter { * Start tracing the VM execution with the given callbacks. */ public enableTracing(callbacks: TracingCallbacks): void { - throw new Error("not implemented"); + this._tracingCallbacks = callbacks; + + const emitBeforeMessage = () => { + if (this._tracingCallbacks !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this._tracingCallbacks.beforeMessage( + { + data: Buffer.from([1, 2, 3]), + } as any, + () => {} + ); + } + }; + + const emitStep = () => { + if (this._tracingCallbacks !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this._tracingCallbacks.step( + { + pc: 0n, + }, + () => {} + ); + } + }; + + const emitAfterMessage = () => { + if (this._tracingCallbacks !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this._tracingCallbacks.afterMessage( + { + executionResult: { + exitCode: 0, + } as any, + }, + () => {} + ); + } + }; + + setInterval(emitBeforeMessage, 100); + setInterval(emitStep, 100); + setInterval(emitAfterMessage, 100); } /** * Stop tracing the execution. */ public disableTracing(): void { - throw new Error("not implemented"); + this._tracingCallbacks = undefined; } public async makeSnapshot(): Promise { @@ -340,4 +384,25 @@ export class RethnetAdapter implements VMAdapter { return undefined; } + + private _beforeMessageHandler = (message: Message, next: any) => { + if (this._tracingCallbacks !== undefined) { + } + + next(); + }; + + private _stepHandler = (step: InterpreterStep, next: any) => { + if (this._tracingCallbacks !== undefined) { + } + + next(); + }; + + private _afterMessageHandler = (result: EVMResult, next: any) => { + if (this._tracingCallbacks !== undefined) { + } + + next(); + }; } From f5d6b7772a16f4c55200e5875995f248a5bc108a Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 11 Jan 2023 22:25:27 -0600 Subject: [PATCH 02/31] feat: tracing with js callbacks --- crates/rethnet_eth/Cargo.toml | 2 +- crates/rethnet_evm/Cargo.toml | 2 +- crates/rethnet_evm/src/block/builder.rs | 8 +- crates/rethnet_evm/src/blockchain/request.rs | 4 +- crates/rethnet_evm/src/blockchain/sync.rs | 10 +- crates/rethnet_evm/src/evm.rs | 17 +- crates/rethnet_evm/src/inspector.rs | 9 +- crates/rethnet_evm/src/lib.rs | 12 +- crates/rethnet_evm/src/runtime.rs | 48 +- crates/rethnet_evm/src/{db.rs => state.rs} | 2 +- .../src/{db => state}/layered_db.rs | 6 +- .../rethnet_evm/src/{db => state}/request.rs | 4 +- crates/rethnet_evm/src/{db => state}/sync.rs | 63 +- .../src/blockchain/js_blockchain.rs | 4 +- crates/rethnet_evm_napi/src/lib.rs | 55 +- crates/rethnet_evm_napi/src/state.rs | 10 +- crates/rethnet_evm_napi/src/tracer.rs | 30 + .../rethnet_evm_napi/src/tracer/js_tracer.rs | 627 ++++++++++++++++++ .../hardhat-network/provider/vm/dual.ts | 9 +- .../hardhat-network/provider/vm/ethereumjs.ts | 17 +- .../hardhat-network/provider/vm/rethnet.ts | 98 ++- .../hardhat-network/provider/vm/vm-adapter.ts | 12 +- 22 files changed, 841 insertions(+), 208 deletions(-) rename crates/rethnet_evm/src/{db.rs => state.rs} (67%) rename crates/rethnet_evm/src/{db => state}/layered_db.rs (99%) rename crates/rethnet_evm/src/{db => state}/request.rs (98%) rename crates/rethnet_evm/src/{db => state}/sync.rs (84%) create mode 100644 crates/rethnet_evm_napi/src/tracer.rs create mode 100644 crates/rethnet_evm_napi/src/tracer/js_tracer.rs diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index f322c96d98..545a2e6566 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -13,7 +13,7 @@ hex = { version = "0.4.3", default-features = false, features = ["alloc"] } hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } -revm = { git = "https://github.com/wodann/revm", rev = "7c28358", version = "2.3", default-features = false } +revm = { git = "https://github.com/wodann/revm", rev = "d7286a1", version = "2.3", default-features = false, features = ["dev", "serde"] } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"] } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index c431346c0d..4ae2f6acc6 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -11,7 +11,7 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth" } -revm = { git = "https://github.com/wodann/revm", rev = "7c28358", version = "2.3", default-features = false, features = ["dev", "k256", "with-serde"] } +revm = { git = "https://github.com/wodann/revm", rev = "d7286a1", version = "2.3", default-features = false, features = ["dev", "serde", "std"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } signature = { version = "1.6.4", default-features = false, features = ["std"] } diff --git a/crates/rethnet_evm/src/block/builder.rs b/crates/rethnet_evm/src/block/builder.rs index 397167def3..5467a01f8e 100644 --- a/crates/rethnet_evm/src/block/builder.rs +++ b/crates/rethnet_evm/src/block/builder.rs @@ -9,7 +9,7 @@ use revm::{BlockEnv, CfgEnv, ExecutionResult, SpecId, TxEnv}; use tokio::runtime::Runtime; use crate::{ - blockchain::AsyncBlockchain, db::AsyncDatabase, evm::build_evm, inspector::RethnetInspector, + blockchain::AsyncBlockchain, evm::build_evm, inspector::RethnetInspector, state::AsyncState, trace::Trace, HeaderData, }; @@ -19,7 +19,7 @@ where E: Debug + Send + 'static, { blockchain: Arc>, - state: Arc>, + state: Arc>, header: PartialHeader, transactions: Vec, cfg: CfgEnv, @@ -32,7 +32,7 @@ where /// Creates an intance of [`BlockBuilder`], creating a checkpoint in the process. pub async fn new( blockchain: Arc>, - db: Arc>, + db: Arc>, cfg: CfgEnv, parent: Header, header: HeaderData, @@ -113,7 +113,7 @@ where .state .runtime() .spawn(async move { - let mut evm = build_evm(&blockchain, &db, cfg, transaction, block); + let mut evm = build_evm(blockchain, db, cfg, transaction, block); let mut inspector = RethnetInspector::default(); let (result, state) = evm.inspect(&mut inspector); diff --git a/crates/rethnet_evm/src/blockchain/request.rs b/crates/rethnet_evm/src/blockchain/request.rs index 2114f71fc0..0e7dc478f9 100644 --- a/crates/rethnet_evm/src/blockchain/request.rs +++ b/crates/rethnet_evm/src/blockchain/request.rs @@ -1,7 +1,7 @@ use std::fmt::Debug; use rethnet_eth::{B256, U256}; -use revm::blockchain::Blockchain; +use revm::BlockHash; use tokio::sync::oneshot; /// The request type used internally by a [`SyncDatabase`]. @@ -28,7 +28,7 @@ where { pub fn handle(self, db: &mut D) -> bool where - D: Blockchain, + D: BlockHash, { match self { Request::BlockHashByNumber { number, sender } => { diff --git a/crates/rethnet_evm/src/blockchain/sync.rs b/crates/rethnet_evm/src/blockchain/sync.rs index 7376bfefdf..d5b194ff68 100644 --- a/crates/rethnet_evm/src/blockchain/sync.rs +++ b/crates/rethnet_evm/src/blockchain/sync.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, io}; use rethnet_eth::{B256, U256}; -use revm::blockchain::Blockchain; +use revm::{BlockHash, BlockHashRef}; use tokio::{ runtime::{Builder, Runtime}, sync::{ @@ -14,7 +14,7 @@ use tokio::{ use super::request::Request; /// Trait that meets all requirements for a synchronous database that can be used by [`AsyncBlockchain`]. -pub trait SyncBlockchain: Blockchain + Send + Sync + 'static +pub trait SyncBlockchain: BlockHash + Send + Sync + 'static where E: Debug + Send, { @@ -22,7 +22,7 @@ where impl SyncBlockchain for B where - B: Blockchain + Send + Sync + 'static, + B: BlockHash + Send + Sync + 'static, E: Debug + Send, { } @@ -111,13 +111,13 @@ where } } -impl<'b, E> Blockchain for &'b AsyncBlockchain +impl BlockHashRef for AsyncBlockchain where E: Debug + Send + 'static, { type Error = E; - fn block_hash(&mut self, number: U256) -> Result { + fn block_hash(&self, number: U256) -> Result { task::block_in_place(move || self.runtime.block_on(self.block_hash_by_number(number))) } } diff --git a/crates/rethnet_evm/src/evm.rs b/crates/rethnet_evm/src/evm.rs index 3639cafc78..0ccd37e173 100644 --- a/crates/rethnet_evm/src/evm.rs +++ b/crates/rethnet_evm/src/evm.rs @@ -1,24 +1,23 @@ -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; -use revm::{BlockEnv, CfgEnv, TxEnv}; +use revm::{db::DatabaseComponents, BlockEnv, CfgEnv, TxEnv}; -use crate::{blockchain::AsyncBlockchain, db::AsyncDatabase}; +use crate::{blockchain::AsyncBlockchain, state::AsyncState}; /// Creates an evm from the provided database, config, transaction, and block. #[allow(clippy::type_complexity)] -pub fn build_evm<'b, 'd, E>( - blockchain: &'b AsyncBlockchain, - db: &'d AsyncDatabase, +pub fn build_evm( + block_hash: Arc>, + state: Arc>, cfg: CfgEnv, transaction: TxEnv, block: BlockEnv, -) -> revm::EVM<&'d AsyncDatabase, &'b AsyncBlockchain> +) -> revm::EVM>, Arc>>> where E: Debug + Send + 'static, { let mut evm = revm::EVM::new(); - evm.set_blockchain(blockchain); - evm.database(db); + evm.database(DatabaseComponents { block_hash, state }); evm.env.cfg = cfg; evm.env.block = block; evm.env.tx = transaction; diff --git a/crates/rethnet_evm/src/inspector.rs b/crates/rethnet_evm/src/inspector.rs index cb3491a354..1e4114c70d 100644 --- a/crates/rethnet_evm/src/inspector.rs +++ b/crates/rethnet_evm/src/inspector.rs @@ -1,4 +1,4 @@ -use revm::{blockchain::Blockchain, opcode, Database, EVMData, Inspector, Interpreter, Return}; +use revm::{opcode, Database, EVMData, Inspector, Interpreter, Return}; use crate::trace::Trace; @@ -15,15 +15,14 @@ impl RethnetInspector { } } -impl Inspector for RethnetInspector +impl Inspector for RethnetInspector where D: Database, - BC: Blockchain, { fn step( &mut self, interp: &mut Interpreter, - _data: &mut EVMData<'_, D, BC>, + _data: &mut EVMData<'_, D>, _is_static: bool, ) -> Return { self.opcode_stack.push(interp.current_opcode()); @@ -34,7 +33,7 @@ where fn step_end( &mut self, interp: &mut Interpreter, - _data: &mut EVMData<'_, D, BC>, + _data: &mut EVMData<'_, D>, _is_static: bool, exit_code: Return, ) -> Return { diff --git a/crates/rethnet_evm/src/lib.rs b/crates/rethnet_evm/src/lib.rs index b72dd4a1ed..29e9f186f1 100644 --- a/crates/rethnet_evm/src/lib.rs +++ b/crates/rethnet_evm/src/lib.rs @@ -8,16 +8,16 @@ use rethnet_eth::Address; pub use hashbrown::HashMap; pub use revm::{ - blockchain::{Blockchain, BlockchainRef}, - db::EmptyDB, - Account, AccountInfo, BlockEnv, Bytecode, CfgEnv, CreateScheme, Database, DatabaseCommit, - ExecutionResult, Log, Return, SpecId, TransactOut, TransactTo, TxEnv, EVM, + db::DatabaseComponents, Account, AccountInfo, BlockEnv, BlockHash, BlockHashRef, Bytecode, + CallInputs, CfgEnv, CreateInputs, CreateScheme, Database, EVMData, ExecutionResult, Gas, + Inspector, Interpreter, Log, Return, SpecId, State as StateMut, StateCommit, StateRef, + TransactOut, TransactTo, TxEnv, EVM, OPCODE_JUMPMAP, }; pub use crate::{ block::{BlockBuilder, HeaderData}, debug::DatabaseDebug, - runtime::Rethnet, + runtime::{AsyncDatabase, Rethnet}, transaction::PendingTransaction, }; @@ -28,7 +28,7 @@ pub type State = HashMap; pub mod blockchain; /// Database types for managing Ethereum state -pub mod db; +pub mod state; /// Types used for tracing EVM calls pub mod trace; diff --git a/crates/rethnet_evm/src/runtime.rs b/crates/rethnet_evm/src/runtime.rs index 49a04858cf..b1dff247a8 100644 --- a/crates/rethnet_evm/src/runtime.rs +++ b/crates/rethnet_evm/src/runtime.rs @@ -1,19 +1,22 @@ use std::{fmt::Debug, sync::Arc}; -use revm::{BlockEnv, CfgEnv, ExecutionResult, SpecId, TxEnv}; +use revm::{db::DatabaseComponents, BlockEnv, CfgEnv, ExecutionResult, Inspector, SpecId, TxEnv}; use crate::{ - blockchain::AsyncBlockchain, db::AsyncDatabase, evm::build_evm, inspector::RethnetInspector, + blockchain::AsyncBlockchain, evm::build_evm, inspector::RethnetInspector, state::AsyncState, trace::Trace, transaction::TransactionError, State, }; +/// Asynchronous implementation of the Database super-trait +pub type AsyncDatabase = DatabaseComponents>, Arc>>; + /// The asynchronous Rethnet runtime. pub struct Rethnet where E: Debug + Send + 'static, { blockchain: Arc>, - db: Arc>, + db: Arc>, cfg: CfgEnv, } @@ -22,11 +25,7 @@ where E: Debug + Send + 'static, { /// Constructs a new [`Rethnet`] instance. - pub fn new( - blockchain: Arc>, - db: Arc>, - cfg: CfgEnv, - ) -> Self { + pub fn new(blockchain: Arc>, db: Arc>, cfg: CfgEnv) -> Self { Self { blockchain, db, @@ -39,6 +38,7 @@ where &self, transaction: TxEnv, block: BlockEnv, + inspector: Option> + Send>>, ) -> Result<(ExecutionResult, State, Trace), TransactionError> { if self.cfg.spec_id > SpecId::MERGE && block.prevrandao.is_none() { return Err(TransactionError::MissingPrevrandao); @@ -52,11 +52,17 @@ where .db .runtime() .spawn(async move { - let mut evm = build_evm(&blockchain, &db, cfg, transaction, block); + let mut evm = build_evm(blockchain, db, cfg, transaction, block); + + if let Some(mut inspector) = inspector { + let (result, state) = evm.inspect(&mut inspector); + (result, state, Trace::default()) + } else { + let mut inspector = RethnetInspector::default(); + let (result, state) = evm.inspect(&mut inspector); - let mut inspector = RethnetInspector::default(); - let (result, state) = evm.inspect(&mut inspector); - (result, state, inspector.into_trace()) + (result, state, inspector.into_trace()) + } }) .await .unwrap()) @@ -67,6 +73,7 @@ where &self, transaction: TxEnv, block: BlockEnv, + inspector: Option> + Send>>, ) -> Result<(ExecutionResult, State, Trace), TransactionError> { if self.cfg.spec_id > SpecId::MERGE && block.prevrandao.is_none() { return Err(TransactionError::MissingPrevrandao); @@ -82,11 +89,17 @@ where .db .runtime() .spawn(async move { - let mut evm = build_evm(&blockchain, &db, cfg, transaction, block); + let mut evm = build_evm(blockchain, db, cfg, transaction, block); + + if let Some(mut inspector) = inspector { + let (result, state) = evm.inspect(&mut inspector); + (result, state, Trace::default()) + } else { + let mut inspector = RethnetInspector::default(); + let (result, state) = evm.inspect(&mut inspector); - let mut inspector = RethnetInspector::default(); - let (result, state) = evm.inspect(&mut inspector); - (result, state, inspector.into_trace()) + (result, state, inspector.into_trace()) + } }) .await .unwrap()) @@ -97,8 +110,9 @@ where &self, transaction: TxEnv, block: BlockEnv, + inspector: Option> + Send>>, ) -> Result<(ExecutionResult, Trace), TransactionError> { - let (result, changes, trace) = self.dry_run(transaction, block).await?; + let (result, changes, trace) = self.dry_run(transaction, block, inspector).await?; self.db.apply(changes).await; diff --git a/crates/rethnet_evm/src/db.rs b/crates/rethnet_evm/src/state.rs similarity index 67% rename from crates/rethnet_evm/src/db.rs rename to crates/rethnet_evm/src/state.rs index 305c552319..1589c72d73 100644 --- a/crates/rethnet_evm/src/db.rs +++ b/crates/rethnet_evm/src/state.rs @@ -2,6 +2,6 @@ mod layered_db; mod request; mod sync; -pub use sync::{AsyncDatabase, SyncDatabase}; +pub use sync::{AsyncState, SyncState}; pub use layered_db::{LayeredDatabase, RethnetLayer}; diff --git a/crates/rethnet_evm/src/db/layered_db.rs b/crates/rethnet_evm/src/state/layered_db.rs similarity index 99% rename from crates/rethnet_evm/src/db/layered_db.rs rename to crates/rethnet_evm/src/state/layered_db.rs index 80218f4842..4f5919c6d6 100644 --- a/crates/rethnet_evm/src/db/layered_db.rs +++ b/crates/rethnet_evm/src/state/layered_db.rs @@ -6,7 +6,7 @@ use rethnet_eth::{ trie::KECCAK_NULL_RLP, Address, B256, U256, }; -use revm::{Account, AccountInfo, Bytecode, Database, DatabaseCommit, KECCAK_EMPTY}; +use revm::{Account, AccountInfo, Bytecode, State, StateCommit, KECCAK_EMPTY}; use crate::DatabaseDebug; @@ -237,7 +237,7 @@ impl LayeredDatabase { } } -impl Database for LayeredDatabase { +impl State for LayeredDatabase { type Error = anyhow::Error; fn basic(&mut self, address: Address) -> anyhow::Result> { @@ -284,7 +284,7 @@ impl Database for LayeredDatabase { } } -impl DatabaseCommit for LayeredDatabase { +impl StateCommit for LayeredDatabase { fn commit(&mut self, changes: HashMap) { changes.into_iter().for_each(|(address, account)| { if account.is_empty() || account.is_destroyed { diff --git a/crates/rethnet_evm/src/db/request.rs b/crates/rethnet_evm/src/state/request.rs similarity index 98% rename from crates/rethnet_evm/src/db/request.rs rename to crates/rethnet_evm/src/state/request.rs index 36c949c39e..e7181ffad6 100644 --- a/crates/rethnet_evm/src/db/request.rs +++ b/crates/rethnet_evm/src/state/request.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use hashbrown::HashMap; use rethnet_eth::{Address, B256, U256}; -use revm::{Account, AccountInfo, Bytecode, Database, DatabaseCommit}; +use revm::{Account, AccountInfo, Bytecode, State, StateCommit}; use tokio::sync::oneshot; use crate::{debug::ModifierFn, DatabaseDebug}; @@ -82,7 +82,7 @@ where { pub fn handle(self, db: &mut D) -> bool where - D: Database + DatabaseCommit + DatabaseDebug, + D: State + StateCommit + DatabaseDebug, { match self { Request::AccountByAddress { address, sender } => { diff --git a/crates/rethnet_evm/src/db/sync.rs b/crates/rethnet_evm/src/state/sync.rs similarity index 84% rename from crates/rethnet_evm/src/db/sync.rs rename to crates/rethnet_evm/src/state/sync.rs index de6a21b0d8..040f5c3fba 100644 --- a/crates/rethnet_evm/src/db/sync.rs +++ b/crates/rethnet_evm/src/state/sync.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, io}; use hashbrown::HashMap; use rethnet_eth::{Address, B256, U256}; -use revm::{db::Database, Account, AccountInfo, Bytecode, DatabaseCommit}; +use revm::{Account, AccountInfo, Bytecode, State, StateCommit, StateRef}; use tokio::{ runtime::{Builder, Runtime}, sync::{ @@ -17,16 +17,16 @@ use crate::{debug::ModifierFn, DatabaseDebug}; use super::request::Request; /// Trait that meets all requirements for a synchronous database that can be used by [`AsyncDatabase`]. -pub trait SyncDatabase: - Database + DatabaseCommit + DatabaseDebug + Send + Sync + 'static +pub trait SyncState: + State + StateCommit + DatabaseDebug + Send + Sync + 'static where E: Debug + Send, { } -impl SyncDatabase for D +impl SyncState for D where - D: Database + DatabaseCommit + DatabaseDebug + Send + Sync + 'static, + D: State + StateCommit + DatabaseDebug + Send + Sync + 'static, E: Debug + Send, { } @@ -34,7 +34,7 @@ where /// A helper class for converting a synchronous database into an asynchronous database. /// /// Requires the inner database to implement [`Database`], [`DatabaseCommit`], and [`DatabaseDebug`]. -pub struct AsyncDatabase +pub struct AsyncState where E: Debug + Send, { @@ -43,12 +43,12 @@ where db_handle: Option>, } -impl AsyncDatabase +impl AsyncState where E: Debug + Send + 'static, { /// Constructs an [`AsyncDatabase`] instance with the provided database. - pub fn new>(mut db: D) -> io::Result { + pub fn new>(mut db: D) -> io::Result { let runtime = Builder::new_multi_thread().build()?; let (sender, mut receiver) = unbounded_channel::>(); @@ -271,7 +271,7 @@ where } } -impl Drop for AsyncDatabase +impl Drop for AsyncState where E: Debug + Send, { @@ -286,35 +286,35 @@ where } } -impl<'d, E> Database for &'d AsyncDatabase +impl StateRef for AsyncState where E: Debug + Send + 'static, { type Error = E; - fn basic(&mut self, address: Address) -> Result, Self::Error> { + fn basic(&self, address: Address) -> Result, Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::account_by_address(*self, address)) + .block_on(AsyncState::account_by_address(self, address)) }) } - fn code_by_hash(&mut self, code_hash: B256) -> Result { + fn code_by_hash(&self, code_hash: B256) -> Result { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::code_by_hash(*self, code_hash)) + .block_on(AsyncState::code_by_hash(self, code_hash)) }) } - fn storage(&mut self, address: Address, index: U256) -> Result { + fn storage(&self, address: Address, index: U256) -> Result { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::account_storage_slot(*self, address, index)) + .block_on(AsyncState::account_storage_slot(self, address, index)) }) } } -impl<'d, E> DatabaseCommit for &'d AsyncDatabase +impl<'d, E> StateCommit for &'d AsyncState where E: Debug + Send + 'static, { @@ -323,7 +323,7 @@ where } } -impl<'d, E> DatabaseDebug for &'d AsyncDatabase +impl<'d, E> DatabaseDebug for &'d AsyncState where E: Debug + Send + 'static, { @@ -332,7 +332,7 @@ where fn account_storage_root(&mut self, address: &Address) -> Result, Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::account_storage_root(*self, address)) + .block_on(AsyncState::account_storage_root(*self, address)) }) } @@ -343,7 +343,7 @@ where ) -> Result<(), Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::insert_account(*self, address, account_info)) + .block_on(AsyncState::insert_account(*self, address, account_info)) }) } @@ -354,14 +354,14 @@ where ) -> Result<(), Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::modify_account(*self, address, modifier)) + .block_on(AsyncState::modify_account(*self, address, modifier)) }) } fn remove_account(&mut self, address: Address) -> Result, Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::remove_account(*self, address)) + .block_on(AsyncState::remove_account(*self, address)) }) } @@ -372,40 +372,39 @@ where value: U256, ) -> Result<(), Self::Error> { task::block_in_place(move || { - self.runtime - .block_on(AsyncDatabase::set_account_storage_slot( - *self, address, index, value, - )) + self.runtime.block_on(AsyncState::set_account_storage_slot( + *self, address, index, value, + )) }) } fn set_state_root(&mut self, state_root: &B256) -> Result<(), Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::set_state_root(*self, state_root)) + .block_on(AsyncState::set_state_root(*self, state_root)) }) } fn state_root(&mut self) -> Result { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::state_root(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::state_root(*self))) } fn checkpoint(&mut self) -> Result<(), Self::Error> { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::checkpoint(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::checkpoint(*self))) } fn revert(&mut self) -> Result<(), Self::Error> { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::revert(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::revert(*self))) } fn make_snapshot(&mut self) -> B256 { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::make_snapshot(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::make_snapshot(*self))) } fn remove_snapshot(&mut self, state_root: &B256) -> bool { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::remove_snapshot(*self, *state_root)) + .block_on(AsyncState::remove_snapshot(*self, *state_root)) }) } } diff --git a/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs b/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs index 74d33af5e1..f59b4d5993 100644 --- a/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs +++ b/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs @@ -3,7 +3,7 @@ use std::sync::mpsc::{channel, Sender}; use anyhow::anyhow; use napi::Status; use rethnet_eth::{B256, U256}; -use rethnet_evm::Blockchain; +use rethnet_evm::BlockHash; use crate::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; @@ -16,7 +16,7 @@ pub struct JsBlockchain { pub(super) get_block_hash_fn: ThreadsafeFunction, } -impl Blockchain for JsBlockchain { +impl BlockHash for JsBlockchain { type Error = anyhow::Error; fn block_hash(&mut self, block_number: U256) -> Result { diff --git a/crates/rethnet_evm_napi/src/lib.rs b/crates/rethnet_evm_napi/src/lib.rs index 69159a5947..06219555cf 100644 --- a/crates/rethnet_evm_napi/src/lib.rs +++ b/crates/rethnet_evm_napi/src/lib.rs @@ -7,6 +7,7 @@ mod state; mod sync; mod threadsafe_function; mod trace; +mod tracer; mod transaction; use std::{fmt::Debug, str::FromStr}; @@ -25,6 +26,7 @@ use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly}; use sha3::{Digest, Keccak256}; use state::StateManager; use trace::Trace; +use tracer::Tracer; use transaction::{Transaction, TransactionOutput}; use crate::cast::TryCast; @@ -299,44 +301,6 @@ impl } } -#[napi(object)] -pub struct TracingMessage { - /// Recipient address. None if it is a Create message. - #[napi(readonly)] - pub to: Option, - - /// Depth of the message - #[napi(readonly)] - pub depth: u8, - - /// Input data of the message - #[napi(readonly)] - pub data: Buffer, - - /// Value sent in the message - #[napi(readonly)] - pub value: BigInt, - - /// Address of the code that is being executed. Can be different from `to` if a delegate call - /// is being done. - #[napi(readonly)] - pub code_address: Option, -} - -#[napi(object)] -pub struct TracingStep { - /// Program counter - #[napi(readonly)] - pub pc: BigInt, -} - -#[napi(object)] -pub struct TracingMessageResult { - /// Execution result - #[napi(readonly)] - pub execution_result: ExecutionResult, -} - #[napi] pub struct Rethnet { runtime: rethnet_evm::Rethnet, @@ -368,12 +332,15 @@ impl Rethnet { &self, transaction: Transaction, block: BlockConfig, + tracer: Option<&Tracer>, ) -> napi::Result { let transaction = transaction.try_into()?; let block = block.try_into()?; + let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); + self.runtime - .dry_run(transaction, block) + .dry_run(transaction, block, inspector) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? .try_into() @@ -384,12 +351,15 @@ impl Rethnet { &self, transaction: Transaction, block: BlockConfig, + tracer: Option<&Tracer>, ) -> napi::Result { let transaction = transaction.try_into()?; let block = block.try_into()?; + let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); + self.runtime - .guaranteed_dry_run(transaction, block) + .guaranteed_dry_run(transaction, block, inspector) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? .try_into() @@ -400,13 +370,16 @@ impl Rethnet { &self, transaction: Transaction, block: BlockConfig, + tracer: Option<&Tracer>, ) -> napi::Result { let transaction: TxEnv = transaction.try_into()?; let block = block.try_into()?; + let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); + Ok(self .runtime - .run(transaction, block) + .run(transaction, block, inspector) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? .into()) diff --git a/crates/rethnet_evm_napi/src/state.rs b/crates/rethnet_evm_napi/src/state.rs index dc0df360ef..8cec435b22 100644 --- a/crates/rethnet_evm_napi/src/state.rs +++ b/crates/rethnet_evm_napi/src/state.rs @@ -7,7 +7,7 @@ use napi::{bindgen_prelude::*, JsFunction, JsObject, NapiRaw, Status}; use napi_derive::napi; use rethnet_eth::{Address, B256, U256}; use rethnet_evm::{ - db::{AsyncDatabase, LayeredDatabase, RethnetLayer, SyncDatabase}, + state::{AsyncState, LayeredDatabase, RethnetLayer, SyncState}, AccountInfo, Bytecode, DatabaseDebug, HashMap, }; use secp256k1::Secp256k1; @@ -28,7 +28,7 @@ struct ModifyAccountCall { #[napi] pub struct StateManager { - pub(super) db: Arc>, + pub(super) db: Arc>, } #[napi] @@ -77,10 +77,10 @@ impl StateManager { fn with_db(db: D) -> napi::Result where - D: SyncDatabase, + D: SyncState, { - let db: Box> = Box::new(db); - let db = AsyncDatabase::new(db) + let db: Box> = Box::new(db); + let db = AsyncState::new(db) .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?; Ok(Self { db: Arc::new(db) }) diff --git a/crates/rethnet_evm_napi/src/tracer.rs b/crates/rethnet_evm_napi/src/tracer.rs new file mode 100644 index 0000000000..8506fda80e --- /dev/null +++ b/crates/rethnet_evm_napi/src/tracer.rs @@ -0,0 +1,30 @@ +mod js_tracer; + +use napi::Env; +use napi_derive::napi; +use rethnet_evm::{AsyncDatabase, Inspector}; + +use self::js_tracer::{JsTracer, TracingCallbacks}; + +#[napi] +pub struct Tracer { + inner: Box, +} + +impl Tracer { + pub fn as_dyn_inspector( + &self, + ) -> Box<(dyn Inspector> + Send + 'static)> { + self.inner.clone() + } +} + +#[napi] +impl Tracer { + #[napi(constructor)] + pub fn new(env: Env, callbacks: TracingCallbacks) -> napi::Result { + JsTracer::new(&env, callbacks).map(|inner| Self { + inner: Box::new(inner), + }) + } +} diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs new file mode 100644 index 0000000000..80b552d716 --- /dev/null +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -0,0 +1,627 @@ +use std::sync::mpsc::{channel, Sender}; + +use napi::{ + bindgen_prelude::{BigInt, Buffer}, + noop_finalize, Env, JsFunction, NapiRaw, Status, +}; +use napi_derive::napi; +use rethnet_eth::{Address, Bytes, U256}; +use rethnet_evm::{Gas, Return, OPCODE_JUMPMAP}; + +use crate::{ + sync::{await_void_promise, handle_error}, + threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + Account, ExecutionResult, +}; + +#[napi(object)] +pub struct TracingMessage { + /// Recipient address. None if it is a Create message. + #[napi(readonly)] + pub to: Option, + + /// Depth of the message + #[napi(readonly)] + pub depth: u8, + + /// Input data of the message + #[napi(readonly)] + pub data: Buffer, + + /// Value sent in the message + #[napi(readonly)] + pub value: BigInt, + + /// Address of the code that is being executed. Can be different from `to` if a delegate call + /// is being done. + #[napi(readonly)] + pub code_address: Option, +} + +#[napi(object)] +pub struct TracingStep { + /// Call depth + #[napi(readonly)] + pub depth: BigInt, + /// The program counter + #[napi(readonly)] + pub pc: BigInt, + /// The executed op code + #[napi(readonly)] + pub opcode: String, + // /// The return value of the step + // #[napi(readonly)] + // pub return_value: u8, + /// The amount of gas that was used by the step + #[napi(readonly)] + pub gas_cost: BigInt, + /// The amount of gas that was refunded by the step + #[napi(readonly)] + pub gas_refunded: BigInt, + /// The amount of gas left + #[napi(readonly)] + pub gas_left: BigInt, + /// The stack + #[napi(readonly)] + pub stack: Vec, + /// The memory + #[napi(readonly)] + pub memory: Buffer, + /// The contract being executed + #[napi(readonly)] + pub contract: Account, + /// The address of the contract + #[napi(readonly)] + pub contract_address: Buffer, + // /// The address of the code being executed + // #[napi(readonly)] + // pub code_address: Buffer, +} + +#[napi(object)] +pub struct TracingMessageResult { + /// Execution result + #[napi(readonly)] + pub execution_result: ExecutionResult, +} + +#[napi(object)] +pub struct TracingCallbacks { + #[napi(ts_type = "(message: TracingMessage, next: any) => Promise")] + pub before_message: JsFunction, + #[napi(ts_type = "(step: TracingStep, next: any) => Promise")] + pub step: JsFunction, + #[napi(ts_type = "(result: TracingMessageResult, next: any) => Promise")] + pub after_message: JsFunction, +} + +pub struct BeforeMessageHandlerCall { + pub depth: u64, + pub to: Option
, + pub data: Bytes, + pub value: U256, + pub code_address: Option
, + pub sender: Sender>, +} + +pub struct StepHandlerCall { + /// Call depth + pub depth: u64, + /// The program counter + pub pc: u64, + /// The executed op code + pub opcode: u8, + /// The return value of the step + pub return_value: Return, + /// The amount of gas that was used by the step + pub gas_cost: u64, + /// The amount of gas that was refunded by the step + pub gas_refunded: i64, + /// The amount of gas left + pub gas_left: u64, + /// The stack + pub stack: Vec, + /// The memory + pub memory: Bytes, + /// The contract being executed + pub contract: rethnet_evm::AccountInfo, + /// The address of the contract + pub contract_address: Address, + // /// The address of the code being executed + // pub code_address: Address, + pub sender: Sender>, +} + +pub struct AfterMessageHandlerCall { + pub result: rethnet_evm::ExecutionResult, + pub sender: Sender>, +} + +#[derive(Clone)] +pub struct JsTracer { + before_message_fn: ThreadsafeFunction, + step_fn: ThreadsafeFunction, + after_message_fn: ThreadsafeFunction, + pre_step_gas: Option, +} + +impl JsTracer { + /// Constructs a `JsTracer` from `TracingCallbacks`. + pub fn new(env: &Env, callbacks: TracingCallbacks) -> napi::Result { + let before_message_fn = ThreadsafeFunction::create( + env.raw(), + unsafe { callbacks.after_message.raw() }, + 0, + |ctx: ThreadSafeCallContext| { + let sender = ctx.value.sender.clone(); + + let mut tracing_message = ctx.env.create_object()?; + + let depth = ctx.env.create_bigint_from_u64(ctx.value.depth)?; + tracing_message.set_named_property("depth", depth)?; + + let to = if let Some(to) = ctx.value.to.as_ref() { + let to = unsafe { + ctx.env.create_buffer_with_borrowed_data( + to.as_ptr(), + to.len(), + (), + noop_finalize, + ) + }?; + to.into_unknown() + } else { + ctx.env.get_null()?.into_unknown() + }; + tracing_message.set_named_property("to", to)?; + + let data = &ctx.value.data; + let data = unsafe { + ctx.env.create_buffer_with_borrowed_data( + data.as_ptr(), + data.len(), + (), + noop_finalize, + ) + }?; + tracing_message.set_named_property("data", data.into_raw())?; + + let value = ctx + .env + .create_bigint_from_words(false, ctx.value.value.as_limbs().to_vec())?; + tracing_message.set_named_property("value", value)?; + + let code_address = if let Some(address) = ctx.value.code_address.as_ref() { + let to = unsafe { + ctx.env.create_buffer_with_borrowed_data( + address.as_ptr(), + address.len(), + (), + noop_finalize, + ) + }?; + to.into_unknown() + } else { + ctx.env.get_null()?.into_unknown() + }; + tracing_message.set_named_property("codeAddress", code_address)?; + + let next = ctx.env.create_object()?; + + let promise = ctx.callback.call(None, &[tracing_message, next])?; + let result = await_void_promise(ctx.env, promise, ctx.value.sender); + + handle_error(sender, result) + }, + )?; + + let step_fn = ThreadsafeFunction::create( + env.raw(), + unsafe { callbacks.step.raw() }, + 0, + |ctx: ThreadSafeCallContext| { + let sender = ctx.value.sender.clone(); + + let mut tracing_step = ctx.env.create_object()?; + + ctx.env + .create_bigint_from_u64(ctx.value.depth) + .and_then(|depth| tracing_step.set_named_property("depth", depth))?; + + ctx.env + .create_bigint_from_u64(ctx.value.pc) + .and_then(|pc| tracing_step.set_named_property("pc", pc))?; + + ctx.env + .create_string(OPCODE_JUMPMAP[usize::from(ctx.value.opcode)].unwrap()) + .and_then(|opcode| tracing_step.set_named_property("opcode", opcode))?; + + ctx.env + .create_uint32((ctx.value.return_value as u8).into()) + .and_then(|return_value| { + tracing_step.set_named_property("returnValue", return_value) + })?; + + ctx.env + .create_bigint_from_u64(ctx.value.gas_cost) + .and_then(|gas_cost| tracing_step.set_named_property("gasCost", gas_cost))?; + + ctx.env + .create_bigint_from_i64(ctx.value.gas_refunded) + .and_then(|gas_refunded| { + tracing_step.set_named_property("gasRefunded", gas_refunded) + })?; + + ctx.env + .create_bigint_from_u64(ctx.value.gas_left) + .and_then(|gas_left| tracing_step.set_named_property("gasLeft", gas_left))?; + + let mut stack = + ctx.env + .create_array(u32::try_from(ctx.value.stack.len()).map_err(|e| { + napi::Error::new(Status::GenericFailure, e.to_string()) + })?)?; + + for value in ctx.value.stack { + ctx.env + .create_bigint_from_words(false, value.as_limbs().to_vec()) + .and_then(|value| stack.insert(value))?; + } + + stack + .coerce_to_object() + .and_then(|stack| tracing_step.set_named_property("stack", stack))?; + + let memory = &ctx.value.memory; + unsafe { + ctx.env.create_buffer_with_borrowed_data( + memory.as_ptr(), + memory.len(), + (), + noop_finalize, + ) + } + .and_then(|memory| tracing_step.set_named_property("memory", memory.into_raw()))?; + + let mut contract = ctx.env.create_object()?; + + ctx.env + .create_bigint_from_words(false, ctx.value.contract.balance.as_limbs().to_vec()) + .and_then(|balance| contract.set_named_property("balance", balance))?; + + let nonce = ctx.env.create_bigint_from_u64(ctx.value.contract.nonce)?; + contract.set_named_property("nonce", nonce)?; + + let code_hash = &ctx.value.memory; + unsafe { + ctx.env.create_buffer_with_borrowed_data( + code_hash.as_ptr(), + code_hash.len(), + (), + noop_finalize, + ) + } + .and_then(|code_hash| { + contract.set_named_property("codeHash", code_hash.into_unknown()) + })?; + + ctx.value + .contract + .code + .as_ref() + .map_or_else( + || ctx.env.get_null().map(|null| null.into_unknown()), + |code| { + unsafe { + ctx.env.create_buffer_with_borrowed_data( + code.bytes().as_ptr(), + code.len(), + (), + noop_finalize, + ) + } + .map(|code| code.into_unknown()) + }, + ) + .and_then(|code| contract.set_named_property("code", code))?; + + tracing_step.set_named_property("contract", contract)?; + + let contract_address = &ctx.value.contract_address; + unsafe { + ctx.env.create_buffer_with_borrowed_data( + contract_address.as_ptr(), + contract_address.len(), + (), + noop_finalize, + ) + } + .and_then(|contract_address| { + tracing_step + .set_named_property("contractAddress", contract_address.into_unknown()) + })?; + + let next = ctx.env.create_object()?; + + let promise = ctx.callback.call(None, &[tracing_step, next])?; + let result = await_void_promise(ctx.env, promise, ctx.value.sender); + + handle_error(sender, result) + }, + )?; + + let after_message_fn = ThreadsafeFunction::create( + env.raw(), + unsafe { callbacks.after_message.raw() }, + 0, + |ctx: ThreadSafeCallContext| { + let sender = ctx.value.sender.clone(); + + let mut tracing_message_result = ctx.env.create_object()?; + + let mut execution_result = ctx.env.create_object()?; + + let exit_code = ctx + .env + .create_uint32((ctx.value.result.exit_reason as u8).into())?; + execution_result.set_named_property("exitCode", exit_code)?; + + let mut transaction_output = ctx.env.create_object()?; + + let (output, address) = match &ctx.value.result.out { + rethnet_evm::TransactOut::None => (None, None), + rethnet_evm::TransactOut::Call(output) => (Some(output), None), + rethnet_evm::TransactOut::Create(output, address) => { + (Some(output), address.as_ref()) + } + }; + + let output = if let Some(output) = output { + unsafe { + ctx.env.create_buffer_with_borrowed_data( + output.as_ptr(), + output.len(), + (), + noop_finalize, + ) + }? + .into_unknown() + } else { + ctx.env.get_null()?.into_unknown() + }; + transaction_output.set_named_property("output", output)?; + + let address = if let Some(address) = address { + unsafe { + ctx.env.create_buffer_with_borrowed_data( + address.as_ptr(), + address.len(), + (), + noop_finalize, + ) + }? + .into_unknown() + } else { + ctx.env.get_null()?.into_unknown() + }; + transaction_output.set_named_property("address", address)?; + + execution_result.set_named_property("output", transaction_output)?; + + let gas_used = ctx.env.create_bigint_from_u64(ctx.value.result.gas_used)?; + execution_result.set_named_property("gasUsed", gas_used)?; + + let gas_refunded = ctx + .env + .create_bigint_from_u64(ctx.value.result.gas_refunded)?; + execution_result.set_named_property("gasRefunded", gas_refunded)?; + + let logs = ctx.env.create_empty_array()?; + execution_result.set_named_property("logs", logs)?; + + let trace = ctx.env.create_object()?; + execution_result.set_named_property("trace", trace)?; + + tracing_message_result.set_named_property("executionResult", execution_result)?; + + let next = ctx.env.create_object()?; + + let promise = ctx.callback.call(None, &[tracing_message_result, next])?; + let result = await_void_promise(ctx.env, promise, ctx.value.sender); + + handle_error(sender, result) + }, + )?; + + Ok(Self { + before_message_fn, + step_fn, + after_message_fn, + pre_step_gas: None, + }) + } +} + +impl rethnet_evm::Inspector for JsTracer +where + D: rethnet_evm::Database, +{ + fn call( + &mut self, + data: &mut rethnet_evm::EVMData<'_, D>, + inputs: &mut rethnet_evm::CallInputs, + _is_static: bool, + ) -> (Return, Gas, rethnet_eth::Bytes) { + let (sender, receiver) = channel(); + + let status = self.before_message_fn.call( + BeforeMessageHandlerCall { + depth: data.journaled_state.depth(), + to: Some(inputs.context.address), + data: inputs.input.clone(), + value: inputs.transfer.value, + code_address: Some(inputs.context.code_address), + sender, + }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); + + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + + (Return::Continue, Gas::new(0), Bytes::default()) + } + + fn call_end( + &mut self, + _data: &mut rethnet_evm::EVMData<'_, D>, + _inputs: &rethnet_evm::CallInputs, + remaining_gas: Gas, + ret: Return, + out: Bytes, + _is_static: bool, + ) -> (Return, Gas, Bytes) { + let (sender, receiver) = channel(); + + let status = self.after_message_fn.call( + AfterMessageHandlerCall { + result: rethnet_evm::ExecutionResult { + exit_reason: ret, + out: rethnet_evm::TransactOut::Call(out.clone()), + gas_used: remaining_gas.spend(), + gas_refunded: remaining_gas.refunded() as u64, + logs: Vec::new(), + }, + sender, + }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); + + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + + (ret, remaining_gas, out) + } + + fn create( + &mut self, + data: &mut rethnet_evm::EVMData<'_, D>, + inputs: &mut rethnet_evm::CreateInputs, + ) -> (Return, Option, Gas, Bytes) { + let (sender, receiver) = channel(); + + let status = self.before_message_fn.call( + BeforeMessageHandlerCall { + depth: data.journaled_state.depth(), + to: None, + data: inputs.init_code.clone(), + value: inputs.value, + code_address: None, + sender, + }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); + + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + + (Return::Continue, None, Gas::new(0), Bytes::default()) + } + + fn create_end( + &mut self, + _data: &mut rethnet_evm::EVMData<'_, D>, + _inputs: &rethnet_evm::CreateInputs, + ret: Return, + address: Option, + remaining_gas: Gas, + out: Bytes, + ) -> (Return, Option, Gas, Bytes) { + let (sender, receiver) = channel(); + + let status = self.after_message_fn.call( + AfterMessageHandlerCall { + result: rethnet_evm::ExecutionResult { + exit_reason: ret, + out: rethnet_evm::TransactOut::Create(out.clone(), address), + gas_used: remaining_gas.spend(), + gas_refunded: remaining_gas.refunded() as u64, + logs: Vec::new(), + }, + sender, + }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); + + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + + (ret, address, remaining_gas, out) + } + + fn step( + &mut self, + interp: &mut rethnet_evm::Interpreter, + _data: &mut rethnet_evm::EVMData<'_, D>, + _is_static: bool, + ) -> Return { + self.pre_step_gas = Some(*interp.gas()); + + Return::Continue + } + + fn step_end( + &mut self, + interp: &mut rethnet_evm::Interpreter, + data: &mut rethnet_evm::EVMData<'_, D>, + _is_static: bool, + _eval: Return, + ) -> Return { + let pre_step_gas = self.pre_step_gas.take().expect("Gas must exist"); + let post_step_gas = interp.gas(); + + let (sender, receiver) = channel(); + + let status = self.step_fn.call( + StepHandlerCall { + depth: data.journaled_state.depth(), + pc: interp.program_counter() as u64, + opcode: interp.current_opcode(), + return_value: interp.instruction_result, + gas_cost: post_step_gas.spend() - pre_step_gas.spend(), + gas_refunded: post_step_gas.refunded() - pre_step_gas.refunded(), + gas_left: interp.gas().remaining(), + stack: interp.stack().data().clone(), + memory: Bytes::copy_from_slice(interp.memory.data().as_slice()), + contract: data + .journaled_state + .account(interp.contract.address) + .info + .clone(), + contract_address: interp.contract().address, + sender, + }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); + + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + + Return::Continue + } +} diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index c1ffaf825d..9f20b468a0 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -7,7 +7,12 @@ import { Address, bufferToHex, } from "@nomicfoundation/ethereumjs-util"; -import { TracingMessage, TracingMessageResult, TracingStep } from "rethnet-evm"; +import { + TracingCallbacks, + TracingMessage, + TracingMessageResult, + TracingStep, +} from "rethnet-evm"; import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; @@ -17,7 +22,7 @@ import { HardhatBlockchainInterface } from "../types/HardhatBlockchainInterface" import { EthereumJSAdapter } from "./ethereumjs"; import { RethnetAdapter } from "./rethnet"; -import { RunTxResult, Trace, TracingCallbacks, VMAdapter } from "./vm-adapter"; +import { RunTxResult, Trace, VMAdapter } from "./vm-adapter"; /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index a4f2364f3c..2de3ee71dc 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -17,6 +17,7 @@ import { RunTxResult as EthereumJSRunTxResult, VM, } from "@nomicfoundation/ethereumjs-vm"; +import { TracingCallbacks } from "rethnet-evm"; import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; import { @@ -36,7 +37,7 @@ import { Bloom } from "../utils/bloom"; import { makeForkClient } from "../utils/makeForkClient"; import { makeStateTrie } from "../utils/makeStateTrie"; import { Exit } from "./exit"; -import { RunTxResult, Trace, TracingCallbacks, VMAdapter } from "./vm-adapter"; +import { RunTxResult, Trace, VMAdapter } from "./vm-adapter"; /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */ @@ -516,7 +517,21 @@ export class EthereumJSAdapter implements VMAdapter { if (this._tracingCallbacks !== undefined) { return this._tracingCallbacks.step( { + depth: BigInt(step.depth), pc: BigInt(step.pc), + opcode: step.opcode.name, + // returnValue: 0, // Do we have error values in ethereumjs? + gasCost: BigInt(step.opcode.fee) + (step.opcode.dynamicFee ?? 0n), + gasRefunded: step.gasRefund, + gasLeft: step.gasLeft, + stack: step.stack, + memory: step.memory, + contract: { + balance: step.account.balance, + nonce: step.account.nonce, + codeHash: step.account.codeHash, + }, + contractAddress: step.address.buf, }, next ); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index fa0e3b9361..4d9f772879 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -1,7 +1,16 @@ import { Block } from "@nomicfoundation/ethereumjs-block"; import { Account, Address } from "@nomicfoundation/ethereumjs-util"; import { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; -import { BlockBuilder, Blockchain, Rethnet } from "rethnet-evm"; +import { + BlockBuilder, + Blockchain, + Rethnet, + Tracer, + TracingCallbacks, + TracingMessage, + TracingMessageResult, + TracingStep, +} from "rethnet-evm"; import { NodeConfig } from "../node-types"; import { @@ -15,7 +24,7 @@ import { RpcDebugTraceOutput } from "../output"; import { RethnetStateManager } from "../RethnetState"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; -import { RunTxResult, Trace, TracingCallbacks, VMAdapter } from "./vm-adapter"; +import { RunTxResult, Trace, VMAdapter } from "./vm-adapter"; /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */ /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -74,16 +83,25 @@ export class RethnetAdapter implements VMAdapter { blockContext.header.mixHash ); - const rethnetResult = await this._rethnet.guaranteedDryRun(rethnetTx, { - number: blockContext.header.number, - coinbase: blockContext.header.coinbase.buf, - timestamp: blockContext.header.timestamp, - basefee: - forceBaseFeeZero === true ? 0n : blockContext.header.baseFeePerGas, - gasLimit: blockContext.header.gasLimit, - difficulty, - prevrandao: prevRandao, - }); + let tracer; + if (this._tracingCallbacks !== undefined) { + tracer = new Tracer(this._tracingCallbacks); + } + + const rethnetResult = await this._rethnet.guaranteedDryRun( + rethnetTx, + { + number: blockContext.header.number, + coinbase: blockContext.header.coinbase.buf, + timestamp: blockContext.header.timestamp, + basefee: + forceBaseFeeZero === true ? 0n : blockContext.header.baseFeePerGas, + gasLimit: blockContext.header.gasLimit, + difficulty, + prevrandao: prevRandao, + }, + tracer + ); try { const result = rethnetResultToRunTxResult( @@ -208,9 +226,15 @@ export class RethnetAdapter implements VMAdapter { block.header.mixHash ); + let tracer; + if (this._tracingCallbacks !== undefined) { + tracer = new Tracer(this._tracingCallbacks); + } + const rethnetResult = await this._rethnet.run( rethnetTx, - ethereumjsHeaderDataToRethnet(block.header, difficulty, prevRandao) + ethereumjsHeaderDataToRethnet(block.header, difficulty, prevRandao), + tracer ); try { @@ -294,48 +318,6 @@ export class RethnetAdapter implements VMAdapter { */ public enableTracing(callbacks: TracingCallbacks): void { this._tracingCallbacks = callbacks; - - const emitBeforeMessage = () => { - if (this._tracingCallbacks !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._tracingCallbacks.beforeMessage( - { - data: Buffer.from([1, 2, 3]), - } as any, - () => {} - ); - } - }; - - const emitStep = () => { - if (this._tracingCallbacks !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._tracingCallbacks.step( - { - pc: 0n, - }, - () => {} - ); - } - }; - - const emitAfterMessage = () => { - if (this._tracingCallbacks !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._tracingCallbacks.afterMessage( - { - executionResult: { - exitCode: 0, - } as any, - }, - () => {} - ); - } - }; - - setInterval(emitBeforeMessage, 100); - setInterval(emitStep, 100); - setInterval(emitAfterMessage, 100); } /** @@ -385,21 +367,21 @@ export class RethnetAdapter implements VMAdapter { return undefined; } - private _beforeMessageHandler = (message: Message, next: any) => { + private _beforeMessageHandler = (message: TracingMessage, next: any) => { if (this._tracingCallbacks !== undefined) { } next(); }; - private _stepHandler = (step: InterpreterStep, next: any) => { + private _stepHandler = (step: TracingStep, next: any) => { if (this._tracingCallbacks !== undefined) { } next(); }; - private _afterMessageHandler = (result: EVMResult, next: any) => { + private _afterMessageHandler = (result: TracingMessageResult, next: any) => { if (this._tracingCallbacks !== undefined) { } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts index 724e7ca000..c27f38a969 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts @@ -2,11 +2,7 @@ import type { Block } from "@nomicfoundation/ethereumjs-block"; import type { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; import type { Account, Address } from "@nomicfoundation/ethereumjs-util"; import type { TxReceipt } from "@nomicfoundation/ethereumjs-vm"; -import type { - TracingMessage, - TracingMessageResult, - TracingStep, -} from "rethnet-evm"; +import type { TracingCallbacks } from "rethnet-evm"; import type { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; import type { RpcDebugTraceOutput } from "../output"; import { Bloom } from "../utils/bloom"; @@ -33,12 +29,6 @@ export interface RunBlockResult { gasUsed: bigint; } -export interface TracingCallbacks { - beforeMessage: (message: TracingMessage, next: any) => Promise; - step: (step: TracingStep, next: any) => Promise; - afterMessage: (result: TracingMessageResult, next: any) => Promise; -} - export interface VMAdapter { dryRun( tx: TypedTransaction, From 16cd70e5f3df693c187a9aea2d38bec274f7e78a Mon Sep 17 00:00:00 2001 From: Wodann Date: Mon, 16 Jan 2023 15:36:50 -0600 Subject: [PATCH 03/31] fix: incorrect return type --- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 2 +- .../src/internal/hardhat-network/provider/vm/dual.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 80b552d716..d8bf44a439 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -233,7 +233,7 @@ impl JsTracer { .and_then(|pc| tracing_step.set_named_property("pc", pc))?; ctx.env - .create_string(OPCODE_JUMPMAP[usize::from(ctx.value.opcode)].unwrap()) + .create_string(OPCODE_JUMPMAP[usize::from(ctx.value.opcode)].unwrap_or("")) .and_then(|opcode| tracing_step.set_named_property("opcode", opcode))?; ctx.env diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index 9f20b468a0..fa5fc45b2d 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -362,7 +362,8 @@ export class DualModeAdapter implements VMAdapter { isEthJS: boolean ) => { if (this._tracingCallbacks === undefined) { - return next(); + next(); + return; } if (this._currentBeforeMessage === undefined) { @@ -409,7 +410,8 @@ export class DualModeAdapter implements VMAdapter { isEthJS: boolean ) => { if (this._tracingCallbacks === undefined) { - return next(); + next(); + return; } if (this._currentStep === undefined) { @@ -460,7 +462,8 @@ export class DualModeAdapter implements VMAdapter { isEthJS: boolean ) => { if (this._tracingCallbacks === undefined) { - return next(); + next(); + return; } if (this._currentMessageResult === undefined) { From 67ff7b96175ac1127305b83cd8146585c1c8dd1d Mon Sep 17 00:00:00 2001 From: Wodann Date: Tue, 17 Jan 2023 00:33:52 -0600 Subject: [PATCH 04/31] fix: use data from start of step --- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index d8bf44a439..e104382470 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -137,12 +137,20 @@ pub struct AfterMessageHandlerCall { pub sender: Sender>, } +#[derive(Clone)] +struct StepData { + depth: u64, + pc: u64, + opcode: u8, + gas: Gas, +} + #[derive(Clone)] pub struct JsTracer { before_message_fn: ThreadsafeFunction, step_fn: ThreadsafeFunction, after_message_fn: ThreadsafeFunction, - pre_step_gas: Option, + pre_step: Option, } impl JsTracer { @@ -437,7 +445,7 @@ impl JsTracer { before_message_fn, step_fn, after_message_fn, - pre_step_gas: None, + pre_step: None, }) } } @@ -574,10 +582,15 @@ where fn step( &mut self, interp: &mut rethnet_evm::Interpreter, - _data: &mut rethnet_evm::EVMData<'_, D>, + data: &mut rethnet_evm::EVMData<'_, D>, _is_static: bool, ) -> Return { - self.pre_step_gas = Some(*interp.gas()); + self.pre_step = Some(StepData { + depth: data.journaled_state.depth(), + pc: interp.program_counter() as u64, + opcode: interp.current_opcode(), + gas: *interp.gas(), + }); Return::Continue } @@ -589,16 +602,21 @@ where _is_static: bool, _eval: Return, ) -> Return { - let pre_step_gas = self.pre_step_gas.take().expect("Gas must exist"); + let StepData { + depth, + pc, + opcode, + gas: pre_step_gas, + } = self.pre_step.take().expect("Gas must exist"); let post_step_gas = interp.gas(); let (sender, receiver) = channel(); let status = self.step_fn.call( StepHandlerCall { - depth: data.journaled_state.depth(), - pc: interp.program_counter() as u64, - opcode: interp.current_opcode(), + depth, + pc, + opcode, return_value: interp.instruction_result, gas_cost: post_step_gas.spend() - pre_step_gas.spend(), gas_refunded: post_step_gas.refunded() - pre_step_gas.refunded(), From 33579960ef9d7f3962093aefb47eef1cd4af0d6b Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 17 Jan 2023 11:46:27 +0100 Subject: [PATCH 05/31] Use correct callback in unsafe block --- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index e104382470..66c50c1ab5 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -158,7 +158,7 @@ impl JsTracer { pub fn new(env: &Env, callbacks: TracingCallbacks) -> napi::Result { let before_message_fn = ThreadsafeFunction::create( env.raw(), - unsafe { callbacks.after_message.raw() }, + unsafe { callbacks.before_message.raw() }, 0, |ctx: ThreadSafeCallContext| { let sender = ctx.value.sender.clone(); From 797de523d190d0469d05276bf93f292e4c5f5b83 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 17 Jan 2023 19:30:33 +0100 Subject: [PATCH 06/31] Temporary fix in inspector --- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 66c50c1ab5..995ee0bc3a 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -602,12 +602,18 @@ where _is_static: bool, _eval: Return, ) -> Return { + // TODO: temporary fix + let pre_step_option = self.pre_step.take(); + if pre_step_option.is_none() { + return Return::Continue; + } + let StepData { depth, pc, opcode, gas: pre_step_gas, - } = self.pre_step.take().expect("Gas must exist"); + } = pre_step_option.expect("Gas must exist"); let post_step_gas = interp.gas(); let (sender, receiver) = channel(); From 79a937073259072bbf98c2881d488ca4a4148a99 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 17 Jan 2023 19:37:26 +0100 Subject: [PATCH 07/31] Move vm tracer inside vm adapter --- .../internal/hardhat-network/provider/node.ts | 19 +- .../hardhat-network/provider/vm/creation.ts | 3 +- .../hardhat-network/provider/vm/dual.ts | 266 ++++++------------ .../hardhat-network/provider/vm/ethereumjs.ts | 195 ++++++------- .../hardhat-network/provider/vm/rethnet.ts | 95 ++++--- .../hardhat-network/provider/vm/vm-adapter.ts | 10 +- .../hardhat-network/stack-traces/vm-tracer.ts | 79 ++---- .../provider/utils/runFullBlock.ts | 5 +- .../hardhat-network/stack-traces/execution.ts | 5 +- 9 files changed, 280 insertions(+), 397 deletions(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts index ebd608513c..fe19c8821b 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts @@ -60,7 +60,6 @@ import { import { SolidityStackTrace } from "../stack-traces/solidity-stack-trace"; import { SolidityTracer } from "../stack-traces/solidityTracer"; import { VmTraceDecoder } from "../stack-traces/vm-trace-decoder"; -import { VMTracer } from "../stack-traces/vm-tracer"; import "./ethereumjs-workarounds"; import { rpcQuantityToBigInt } from "../../core/jsonrpc/types/base-types"; @@ -313,7 +312,6 @@ Hardhat Network's forking functionality only works with blocks from at least spu private _nextSnapshotId = 1; // We start in 1 to mimic Ganache private readonly _snapshots: Snapshot[] = []; - private readonly _vmTracer: VMTracer; private readonly _vmTraceDecoder: VmTraceDecoder; private readonly _solidityTracer: SolidityTracer; private readonly _consoleLogger: ConsoleLogger = new ConsoleLogger(); @@ -352,9 +350,6 @@ Hardhat Network's forking functionality only works with blocks from at least spu this.setUserProvidedNextBlockBaseFeePerGas(nextBlockBaseFee); } - this._vmTracer = new VMTracer(this._vm, this._common, false); - this._vmTracer.enableTracing(); - const contractsIdentifier = new ContractsIdentifier(); this._vmTraceDecoder = new VmTraceDecoder(contractsIdentifier); this._solidityTracer = new SolidityTracer(); @@ -743,9 +738,10 @@ Hardhat Network's forking functionality only works with blocks from at least spu this._runTxAndRevertMutations(tx, blockNumberOrPending) ); - let vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); - const vmTracerError = this._vmTracer.getLastError(); - this._vmTracer.clearLastError(); + const traceResult = this._vm.getLastTrace(); + let vmTrace = traceResult.trace; + const vmTracerError = traceResult.error; + this._vm.clearLastError(); if (vmTrace !== undefined) { vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); @@ -1551,9 +1547,10 @@ Hardhat Network's forking functionality only works with blocks from at least spu private async _gatherTraces( result: RunTxResult ): Promise { - let vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); - const vmTracerError = this._vmTracer.getLastError(); - this._vmTracer.clearLastError(); + const traceResult = this._vm.getLastTrace(); + let vmTrace = traceResult.trace; + const vmTracerError = traceResult.error; + this._vm.clearLastError(); if (vmTrace !== undefined) { vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts index a671610781..3861c3c6f1 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts @@ -31,7 +31,8 @@ export function createVm( assertHardhatInvariant(block !== null, "Should be able to get block"); return block.header.hash(); - } + }, + common ); } else { return DualModeAdapter.create(common, blockchain, config, selectHardfork); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index fa5fc45b2d..e037521916 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -7,15 +7,11 @@ import { Address, bufferToHex, } from "@nomicfoundation/ethereumjs-util"; -import { - TracingCallbacks, - TracingMessage, - TracingMessageResult, - TracingStep, -} from "rethnet-evm"; import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; +import { MessageTrace } from "../../stack-traces/message-trace"; +import { VMTracer } from "../../stack-traces/vm-tracer"; import { NodeConfig } from "../node-types"; import { RpcDebugTraceOutput } from "../output"; import { HardhatBlockchainInterface } from "../types/HardhatBlockchainInterface"; @@ -42,11 +38,11 @@ function printRethnetTrace(trace: any) { } export class DualModeAdapter implements VMAdapter { - private _tracingCallbacks: TracingCallbacks | undefined; - constructor( private _ethereumJSAdapter: VMAdapter, - private _rethnetAdapter: VMAdapter + private _rethnetAdapter: VMAdapter, + private _ethereumJSVMTracer: VMTracer, + private _rethnetVMTracer: VMTracer ) {} public static async create( @@ -70,10 +66,19 @@ export class DualModeAdapter implements VMAdapter { assertHardhatInvariant(block !== null, "Should be able to get block"); return block.header.hash(); - } + }, + common ); - return new DualModeAdapter(ethereumJSAdapter, rethnetAdapter); + const ethereumJSVMTracer = new VMTracer(ethereumJSAdapter, common, false); + const rethnetVMTracer = new VMTracer(rethnetAdapter, common, false); + + return new DualModeAdapter( + ethereumJSAdapter, + rethnetAdapter, + ethereumJSVMTracer, + rethnetVMTracer + ); } public async dryRun( @@ -225,29 +230,6 @@ export class DualModeAdapter implements VMAdapter { return this._ethereumJSAdapter.traceTransaction(hash, block, config); } - public enableTracing(callbacks: TracingCallbacks): void { - this._tracingCallbacks = callbacks; - - this._ethereumJSAdapter.enableTracing({ - beforeMessage: this._ethereumJSBeforeMessagehandler, - step: this._ethereumJSStepHandler, - afterMessage: this._ethereumJSAfterMessageHandler, - }); - - this._rethnetAdapter.enableTracing({ - beforeMessage: this._rethnetBeforeMessagehandler, - step: this._rethnetStepHandler, - afterMessage: this._rethnetAfterMessageHandler, - }); - } - - public disableTracing(): void { - this._tracingCallbacks = undefined; - - this._ethereumJSAdapter.disableTracing(); - this._rethnetAdapter.disableTracing(); - } - public async setBlockContext( block: Block, irregularStateOrUndefined: Buffer | undefined @@ -335,167 +317,82 @@ export class DualModeAdapter implements VMAdapter { return rethnetRoot; } - private _currentBeforeMessage: TracingMessage | undefined; - private _currentBeforeMessageNext: any; - private _currentStep: TracingStep | undefined; - private _currentStepNext: any; - private _currentMessageResult: TracingMessageResult | undefined; - private _currentMessageResultNext: any; - - private _ethereumJSBeforeMessagehandler = async ( - message: TracingMessage, - next: any - ) => { - return this._beforeMessageHandler(message, next, true); - }; - - private _rethnetBeforeMessagehandler = async ( - message: TracingMessage, - next: any - ) => { - return this._beforeMessageHandler(message, next, false); - }; - - private _beforeMessageHandler = async ( - message: TracingMessage, - next: any, - isEthJS: boolean - ) => { - if (this._tracingCallbacks === undefined) { - next(); - return; - } + public getLastTrace(): { + trace: MessageTrace | undefined; + error: Error | undefined; + } { + const { trace: ethereumJSTrace, error: ethereumJSError } = + this._ethereumJSAdapter.getLastTrace(); + const { trace: rethnetTrace, error: rethnetError } = + this._rethnetAdapter.getLastTrace(); - if (this._currentBeforeMessage === undefined) { - // this method executed first, save results - this._currentBeforeMessage = message; - this._currentBeforeMessageNext = next; + if (ethereumJSTrace === undefined) { + if (rethnetTrace !== undefined) { + throw new Error( + "ethereumJSTrace is undefined but rethnetTrace is defined" + ); + } } else { - // this method executed second, compare results - if (!message.data.equals(this._currentBeforeMessage.data)) { - const current = isEthJS ? "ethereumjs" : "rethnet"; - const previous = isEthJS ? "rethnet" : "ethereumjs"; - const errorMessage = `Different data in before message handler, ${current}: '${message.data.toString( - "hex" - )}', ${previous}: '${this._currentBeforeMessage.data.toString("hex")}'`; - - // both log and send the error, because the error message sometimes is - // swallowed by the tests - console.log("==========>", errorMessage); - next(new Error(errorMessage)); + if (rethnetTrace === undefined) { + throw new Error( + "ethereumJSTrace is defined but rethnetTrace is undefined" + ); } - // continue the execution of the other adapter - this._currentBeforeMessageNext(); - - // clean the state - this._currentBeforeMessage = undefined; - this._currentBeforeMessageNext = undefined; + // both traces are defined + if (ethereumJSTrace.depth !== rethnetTrace.depth) { + throw new Error( + `Different depth: ${ethereumJSTrace.depth} !== ${rethnetTrace.depth}` + ); + } - return this._tracingCallbacks.beforeMessage(message, next); - } - }; - - private _ethereumJSStepHandler = async (step: TracingStep, next: any) => { - return this._stepHandler(step, next, true); - }; - - private _rethnetStepHandler = async (step: TracingStep, next: any) => { - return this._stepHandler(step, next, false); - }; - - private _stepHandler = async ( - step: TracingStep, - next: any, - isEthJS: boolean - ) => { - if (this._tracingCallbacks === undefined) { - next(); - return; + // etc } - if (this._currentStep === undefined) { - // this method executed first, save results - this._currentStep = step; - this._currentStepNext = next; + if (ethereumJSError === undefined) { + if (rethnetError !== undefined) { + throw new Error( + "ethereumJSError is undefined but rethnetError is defined" + ); + } } else { - // this method executed second, compare results - if (step.pc !== this._currentStep.pc) { - const current = isEthJS ? "ethereumjs" : "rethnet"; - const previous = isEthJS ? "rethnet" : "ethereumjs"; - const errorMessage = `Different pc in step handler, ${current}: '${step.pc}', ${previous}: '${this._currentStep.pc}'`; - - // both log and send the error, because the error message sometimes is - // swallowed by the tests - console.log("==========>", errorMessage); - next(new Error(errorMessage)); + if (rethnetError === undefined) { + throw new Error( + "ethereumJSError is defined but rethnetError is undefined" + ); } - // continue the execution of the other adapter - this._currentStepNext(); - - // clean the state - this._currentStep = undefined; - this._currentStepNext = undefined; + // both errors are defined + if (ethereumJSError.message !== rethnetError.message) { + throw new Error( + `Different message: ${ethereumJSError.message} !== ${rethnetError.message}` + ); + } - return this._tracingCallbacks.step(step, next); - } - }; - - private _ethereumJSAfterMessageHandler = async ( - result: TracingMessageResult, - next: any - ) => { - return this._afterMessageHandler(result, next, true); - }; - - private _rethnetAfterMessageHandler = async ( - result: TracingMessageResult, - next: any - ) => { - return this._afterMessageHandler(result, next, false); - }; - - private _afterMessageHandler = async ( - result: TracingMessageResult, - next: any, - isEthJS: boolean - ) => { - if (this._tracingCallbacks === undefined) { - next(); - return; + // etc } - if (this._currentMessageResult === undefined) { - // this method executed first, save results - this._currentMessageResult = result; - this._currentMessageResultNext = next; - } else { - // this method executed second, compare results - if ( - result.executionResult.exitCode !== - this._currentMessageResult.executionResult.exitCode - ) { - const current = isEthJS ? "ethereumjs" : "rethnet"; - const previous = isEthJS ? "rethnet" : "ethereumjs"; - const errorMessage = `Different exit codes in after message handler, ${current}: '${result.executionResult.exitCode}', ${previous}: '${this._currentMessageResult.executionResult.exitCode}'`; - - // both log and send the error, because the error message sometimes is - // swallowed by the tests - console.log("==========>", errorMessage); - next(new Error(errorMessage)); - } + if ( + this._ethereumJSVMTracer.tracingSteps.length !== + this._rethnetVMTracer.tracingSteps.length + ) { + throw new Error( + `Different number of steps in tracers: ${this._ethereumJSVMTracer.tracingSteps.length} !== ${this._rethnetVMTracer.tracingSteps.length}` + ); + } - // continue the execution of the other adapter - this._currentMessageResultNext(); + // TODO: compare each step + // TODO: compare tracers tracingMessages and tracingMessageResults - // clean the state - this._currentMessageResult = undefined; - this._currentMessageResultNext = undefined; + return { + trace: rethnetTrace, + error: rethnetError, + }; + } - return this._tracingCallbacks.afterMessage(result, next); - } - }; + public clearLastError() { + throw new Error("not implemented"); + } } function assertEqualRunTxResults( @@ -643,9 +540,12 @@ function assertEqualAccounts( } if (!ethereumJSAccount.storageRoot.equals(rethnetAccount.storageRoot)) { - console.trace( - `Different storageRoot: ${ethereumJSAccount.storageRoot} !== ${rethnetAccount.storageRoot}` - ); - throw new Error("Different storageRoot"); + // TODO re-enable + // console.trace( + // `Different storageRoot: ${ethereumJSAccount.storageRoot.toString( + // "hex" + // )} !== ${rethnetAccount.storageRoot.toString("hex")}` + // ); + // throw new Error("Different storageRoot"); } } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index 2de3ee71dc..a9905ddf62 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -17,7 +17,6 @@ import { RunTxResult as EthereumJSRunTxResult, VM, } from "@nomicfoundation/ethereumjs-vm"; -import { TracingCallbacks } from "rethnet-evm"; import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; import { @@ -25,7 +24,9 @@ import { InvalidInputError, TransactionExecutionError, } from "../../../core/providers/errors"; +import { MessageTrace } from "../../stack-traces/message-trace"; import { VMDebugTracer } from "../../stack-traces/vm-debug-tracer"; +import { VMTracer } from "../../stack-traces/vm-tracer"; import { ForkStateManager } from "../fork/ForkStateManager"; import { isForkedNodeConfig, NodeConfig } from "../node-types"; import { RpcDebugTraceOutput } from "../output"; @@ -42,10 +43,10 @@ import { RunTxResult, Trace, VMAdapter } from "./vm-adapter"; /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */ export class EthereumJSAdapter implements VMAdapter { - private _tracingCallbacks: TracingCallbacks | undefined; - private _blockStartStateRoot: Buffer | undefined; + private _vmTracer: VMTracer; + constructor( private readonly _vm: VM, private readonly _stateManager: StateManager, @@ -56,7 +57,18 @@ export class EthereumJSAdapter implements VMAdapter { private readonly _selectHardfork: (blockNumber: bigint) => string, private readonly _forkNetworkId?: number, private readonly _forkBlockNumber?: bigint - ) {} + ) { + this._vmTracer = new VMTracer(this, _common, false); + + assertHardhatInvariant( + this._vm.evm.events !== undefined, + "EVM should have an 'events' property" + ); + + this._vm.evm.events.on("beforeMessage", this._beforeMessageHandler); + this._vm.evm.events.on("step", this._stepHandler); + this._vm.evm.events.on("afterMessage", this._afterMessageHandler); + } public static async create( common: Common, @@ -258,40 +270,6 @@ export class EthereumJSAdapter implements VMAdapter { return this._stateManager.setStateRoot(stateRoot); } - public enableTracing(callbacks: TracingCallbacks): void { - assertHardhatInvariant( - this._vm.evm.events !== undefined, - "EVM should have an 'events' property" - ); - - this._tracingCallbacks = callbacks; - - this._vm.evm.events.on("beforeMessage", this._beforeMessageHandler); - this._vm.evm.events.on("step", this._stepHandler); - this._vm.evm.events.on("afterMessage", this._afterMessageHandler); - } - - public disableTracing(): void { - assertHardhatInvariant( - this._vm.evm.events !== undefined, - "EVM should have an 'events' property" - ); - - if (this._tracingCallbacks !== undefined) { - this._vm.evm.events.removeListener( - "beforeMessage", - this._beforeMessageHandler - ); - this._vm.evm.events.removeListener("step", this._stepHandler); - this._vm.evm.events.removeListener( - "afterMessage", - this._afterMessageHandler - ); - - this._tracingCallbacks = undefined; - } - } - public async setBlockContext( block: Block, irregularStateOrUndefined: Buffer | undefined @@ -462,6 +440,20 @@ export class EthereumJSAdapter implements VMAdapter { return this.getStateRoot(); } + public getLastTrace(): { + trace: MessageTrace | undefined; + error: Error | undefined; + } { + const trace = this._vmTracer.getLastTopLevelMessageTrace(); + const error = this._vmTracer.getLastError(); + + return { trace, error }; + } + + public clearLastError() { + this._vmTracer.clearLastError(); + } + private _getCommonForTracing(networkId: number, blockNumber: bigint): Common { try { const common = Common.custom( @@ -495,87 +487,82 @@ export class EthereumJSAdapter implements VMAdapter { return this._common.gteHardfork("london"); } - private _beforeMessageHandler = (message: Message, next: any) => { - if (this._tracingCallbacks !== undefined) { - return this._tracingCallbacks.beforeMessage( - { - ...message, - to: message.to?.toBuffer(), - codeAddress: - message.to !== undefined - ? message.codeAddress.toBuffer() - : undefined, - }, - next - ); - } + private _beforeMessageHandler = async (message: Message, next: any) => { + try { + await this._vmTracer.addBeforeMessage({ + ...message, + to: message.to?.toBuffer(), + codeAddress: + message.to !== undefined ? message.codeAddress.toBuffer() : undefined, + }); - next(); + return next(); + } catch (e) { + return next(e); + } }; - private _stepHandler = (step: InterpreterStep, next: any) => { - if (this._tracingCallbacks !== undefined) { - return this._tracingCallbacks.step( - { - depth: BigInt(step.depth), - pc: BigInt(step.pc), - opcode: step.opcode.name, - // returnValue: 0, // Do we have error values in ethereumjs? - gasCost: BigInt(step.opcode.fee) + (step.opcode.dynamicFee ?? 0n), - gasRefunded: step.gasRefund, - gasLeft: step.gasLeft, - stack: step.stack, - memory: step.memory, - contract: { - balance: step.account.balance, - nonce: step.account.nonce, - codeHash: step.account.codeHash, - }, - contractAddress: step.address.buf, + private _stepHandler = async (step: InterpreterStep, next: any) => { + try { + await this._vmTracer.addStep({ + depth: BigInt(step.depth), + pc: BigInt(step.pc), + opcode: step.opcode.name, + // returnValue: 0, // Do we have error values in ethereumjs? + gasCost: BigInt(step.opcode.fee) + (step.opcode.dynamicFee ?? 0n), + gasRefunded: step.gasRefund, + gasLeft: step.gasLeft, + stack: step.stack, + memory: step.memory, + contract: { + balance: step.account.balance, + nonce: step.account.nonce, + codeHash: step.account.codeHash, }, - next - ); - } + contractAddress: step.address.buf, + }); - next(); + return next(); + } catch (e) { + return next(e); + } }; - private _afterMessageHandler = (result: EVMResult, next: any) => { - if (this._tracingCallbacks !== undefined) { + private _afterMessageHandler = async (result: EVMResult, next: any) => { + try { const vmError = Exit.fromEthereumJSEvmError( result.execResult.exceptionError ); const rethnetExitCode = vmError.getRethnetExitCode(); - return this._tracingCallbacks.afterMessage( - { - executionResult: { - exitCode: rethnetExitCode, - output: { - address: result.createdAddress?.toBuffer(), - output: result.execResult.returnValue, - }, - gasUsed: result.execResult.executionGasUsed, - gasRefunded: result.execResult.gasRefund ?? 0n, - logs: - result.execResult.logs?.map((log) => { - return { - address: log[0], - topics: log[1], - data: log[2], - }; - }) ?? [], - trace: { - steps: [], - returnValue: result.execResult.returnValue, - }, + await this._vmTracer.addAfterMessage({ + executionResult: { + exitCode: rethnetExitCode, + output: { + address: result.createdAddress?.toBuffer(), + output: result.execResult.returnValue, + }, + gasUsed: result.execResult.executionGasUsed, + gasRefunded: result.execResult.gasRefund ?? 0n, + logs: + result.execResult.logs?.map((log) => { + return { + address: log[0], + topics: log[1], + data: log[2], + }; + }) ?? [], + trace: { + steps: [], + returnValue: result.execResult.returnValue, }, }, - next - ); - } + }); - next(); + return next(); + } catch (e) { + return next(e); + } }; } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 4d9f772879..242f31d786 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -1,4 +1,5 @@ import { Block } from "@nomicfoundation/ethereumjs-block"; +import { Common } from "@nomicfoundation/ethereumjs-common"; import { Account, Address } from "@nomicfoundation/ethereumjs-util"; import { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; import { @@ -6,7 +7,6 @@ import { Blockchain, Rethnet, Tracer, - TracingCallbacks, TracingMessage, TracingMessageResult, TracingStep, @@ -23,6 +23,8 @@ import { hardforkGte, HardforkName } from "../../../util/hardforks"; import { RpcDebugTraceOutput } from "../output"; import { RethnetStateManager } from "../RethnetState"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; +import { MessageTrace } from "../../stack-traces/message-trace"; +import { VMTracer } from "../../stack-traces/vm-tracer"; import { RunTxResult, Trace, VMAdapter } from "./vm-adapter"; @@ -30,19 +32,23 @@ import { RunTxResult, Trace, VMAdapter } from "./vm-adapter"; /* eslint-disable @typescript-eslint/no-unused-vars */ export class RethnetAdapter implements VMAdapter { - private _tracingCallbacks: TracingCallbacks | undefined; + private _vmTracer: VMTracer; constructor( private _blockchain: Blockchain, private _state: RethnetStateManager, private _rethnet: Rethnet, - private readonly _selectHardfork: (blockNumber: bigint) => string - ) {} + private readonly _selectHardfork: (blockNumber: bigint) => string, + common: Common + ) { + this._vmTracer = new VMTracer(this, common, false); + } public static async create( config: NodeConfig, selectHardfork: (blockNumber: bigint) => string, - getBlockHash: (blockNumber: bigint) => Promise + getBlockHash: (blockNumber: bigint) => Promise, + common: Common ): Promise { const blockchain = new Blockchain(getBlockHash); @@ -61,7 +67,13 @@ export class RethnetAdapter implements VMAdapter { disableEip3607: true, }); - return new RethnetAdapter(blockchain, state, rethnet, selectHardfork); + return new RethnetAdapter( + blockchain, + state, + rethnet, + selectHardfork, + common + ); } /** @@ -83,10 +95,11 @@ export class RethnetAdapter implements VMAdapter { blockContext.header.mixHash ); - let tracer; - if (this._tracingCallbacks !== undefined) { - tracer = new Tracer(this._tracingCallbacks); - } + const tracer = new Tracer({ + beforeMessage: this._beforeMessageHandler, + step: this._stepHandler, + afterMessage: this._afterMessageHandler, + }); const rethnetResult = await this._rethnet.guaranteedDryRun( rethnetTx, @@ -226,10 +239,11 @@ export class RethnetAdapter implements VMAdapter { block.header.mixHash ); - let tracer; - if (this._tracingCallbacks !== undefined) { - tracer = new Tracer(this._tracingCallbacks); - } + const tracer = new Tracer({ + beforeMessage: this._beforeMessageHandler, + step: this._stepHandler, + afterMessage: this._afterMessageHandler, + }); const rethnetResult = await this._rethnet.run( rethnetTx, @@ -316,21 +330,35 @@ export class RethnetAdapter implements VMAdapter { /** * Start tracing the VM execution with the given callbacks. */ - public enableTracing(callbacks: TracingCallbacks): void { - this._tracingCallbacks = callbacks; - } + // public enableTracing(callbacks: TracingCallbacks): void { + // this._tracingCallbacks = callbacks; + // } /** * Stop tracing the execution. */ - public disableTracing(): void { - this._tracingCallbacks = undefined; - } + // public disableTracing(): void { + // this._tracingCallbacks = undefined; + // } public async makeSnapshot(): Promise { return this._state.makeSnapshot(); } + public getLastTrace(): { + trace: MessageTrace | undefined; + error: Error | undefined; + } { + const trace = this._vmTracer.getLastTopLevelMessageTrace(); + const error = this._vmTracer.getLastError(); + + return { trace, error }; + } + + public clearLastError() { + this._vmTracer.clearLastError(); + } + private _getBlockEnvDifficulty( difficulty: bigint | undefined ): bigint | undefined { @@ -367,24 +395,21 @@ export class RethnetAdapter implements VMAdapter { return undefined; } - private _beforeMessageHandler = (message: TracingMessage, next: any) => { - if (this._tracingCallbacks !== undefined) { - } - - next(); + private _beforeMessageHandler = async ( + message: TracingMessage, + next: any + ) => { + await this._vmTracer.addBeforeMessage(message); }; - private _stepHandler = (step: TracingStep, next: any) => { - if (this._tracingCallbacks !== undefined) { - } - - next(); + private _stepHandler = async (step: TracingStep, next: any) => { + await this._vmTracer.addStep(step); }; - private _afterMessageHandler = (result: TracingMessageResult, next: any) => { - if (this._tracingCallbacks !== undefined) { - } - - next(); + private _afterMessageHandler = async ( + result: TracingMessageResult, + next: any + ) => { + await this._vmTracer.addAfterMessage(result); }; } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts index c27f38a969..e00dcd089c 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts @@ -2,9 +2,10 @@ import type { Block } from "@nomicfoundation/ethereumjs-block"; import type { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; import type { Account, Address } from "@nomicfoundation/ethereumjs-util"; import type { TxReceipt } from "@nomicfoundation/ethereumjs-vm"; -import type { TracingCallbacks } from "rethnet-evm"; import type { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; import type { RpcDebugTraceOutput } from "../output"; + +import { MessageTrace } from "../../stack-traces/message-trace"; import { Bloom } from "../utils/bloom"; import { Exit } from "./exit"; @@ -69,13 +70,16 @@ export interface VMAdapter { revertBlock(): Promise; // methods for tracing + getLastTrace(): { + trace: MessageTrace | undefined; + error: Error | undefined; + }; + clearLastError(): void; traceTransaction( hash: Buffer, block: Block, config: RpcDebugTracingConfig ): Promise; - enableTracing(callbacks: TracingCallbacks): void; - disableTracing(): void; // methods for snapshotting makeSnapshot(): Promise; diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts index a4cfa4a9ad..bbf409a719 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts @@ -27,8 +27,11 @@ const DUMMY_RETURN_DATA = Buffer.from([]); const DUMMY_GAS_USED = 0n; export class VMTracer { + public tracingMessages: TracingMessage[] = []; + public tracingSteps: TracingStep[] = []; + public tracingMessageResults: TracingMessageResult[] = []; + private _messageTraces: MessageTrace[] = []; - private _enabled = false; private _lastError: Error | undefined; private _maxPrecompileNumber; @@ -37,45 +40,10 @@ export class VMTracer { common: Common, private readonly _throwErrors = true ) { - this._beforeMessageHandler = this._beforeMessageHandler.bind(this); - this._stepHandler = this._stepHandler.bind(this); - this._afterMessageHandler = this._afterMessageHandler.bind(this); this._maxPrecompileNumber = getActivePrecompiles(common).size; } - public enableTracing() { - if (this._enabled) { - return; - } - - this._vm.enableTracing({ - beforeMessage: this._beforeMessageHandler, - step: this._stepHandler, - afterMessage: this._afterMessageHandler, - }); - - this._enabled = true; - } - - public disableTracing() { - if (!this._enabled) { - return; - } - - this._vm.disableTracing(); - - this._enabled = false; - } - - public get enabled(): boolean { - return this._enabled; - } - public getLastTopLevelMessageTrace(): MessageTrace | undefined { - if (!this._enabled) { - throw new Error("You can't get a vm trace if the VMTracer is disabled"); - } - return this._messageTraces[0]; } @@ -91,9 +59,8 @@ export class VMTracer { return this._throwErrors || this._lastError === undefined; } - private async _beforeMessageHandler(message: TracingMessage, next: any) { + public async addBeforeMessage(message: TracingMessage) { if (!this._shouldKeepTracing()) { - next(); return; } @@ -102,8 +69,13 @@ export class VMTracer { if (message.depth === 0) { this._messageTraces = []; + this.tracingMessages = []; + this.tracingSteps = []; + this.tracingMessageResults = []; } + this.tracingMessages.push(message); + if (message.to === undefined) { const createTrace: CreateMessageTrace = { code: message.data, @@ -143,10 +115,7 @@ export class VMTracer { "codeAddress should be defined" ); - const code = await this._vm.getContractCode( - new Address(codeAddress), - true // ethJsOnly, temporary fix - ); + const code = await this._vm.getContractCode(new Address(codeAddress)); const callTrace: CallMessageTrace = { code, @@ -180,23 +149,23 @@ export class VMTracer { } this._messageTraces.push(trace); - next(); } catch (error) { + console.log("addBeforeMessage", error); // TODO delete if (this._throwErrors) { - next(error); + throw error; } else { this._lastError = error as Error; - next(); } } } - private async _stepHandler(step: TracingStep, next: any) { + public async addStep(step: TracingStep) { if (!this._shouldKeepTracing()) { - next(); return; } + this.tracingSteps.push(step); + try { const trace = this._messageTraces[this._messageTraces.length - 1]; @@ -207,23 +176,23 @@ export class VMTracer { } trace.steps.push({ pc: Number(step.pc) }); - next(); } catch (error) { + console.log("addStep", error); // TODO delete if (this._throwErrors) { - next(error); + throw error; } else { this._lastError = error as Error; - next(); } } } - private async _afterMessageHandler(result: TracingMessageResult, next: any) { + public async addAfterMessage(result: TracingMessageResult) { if (!this._shouldKeepTracing()) { - next(); return; } + this.tracingMessageResults.push(result); + try { const trace = this._messageTraces[this._messageTraces.length - 1]; @@ -239,14 +208,12 @@ export class VMTracer { if (this._messageTraces.length > 1) { this._messageTraces.pop(); } - - next(); } catch (error) { + console.log("addAfterMessage", error); // TODO delete if (this._throwErrors) { - next(error); + throw error; } else { this._lastError = error as Error; - next(); } } } diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/utils/runFullBlock.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/utils/runFullBlock.ts index 6236761dfb..50c84697fa 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/utils/runFullBlock.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/utils/runFullBlock.ts @@ -69,7 +69,8 @@ export async function runFullBlock( } ); - forkedNode["_vmTracer"].disableTracing(); + // TODO uncomment and fix this + // forkedNode["_vmTracer"].disableTracing(); const afterBlockEvent = await runBlockAndGetAfterBlockEvent( // TODO remove "as any" and make this work with VMAdapter @@ -88,7 +89,7 @@ export async function runFullBlock( await (forkedNode["_vm"] as any).putBlock(modifiedBlock); await forkedNode["_saveBlockAsSuccessfullyRun"]( modifiedBlock, - afterBlockEvent + afterBlockEvent as any // TODO remove this as any ); const newBlock = await forkedNode.getBlockByNumber(blockToRun); diff --git a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts index 0eca7effe6..11a144b7ac 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts @@ -107,7 +107,8 @@ export async function traceTransaction( const signedTx = tx.sign(senderPrivateKey); const vmTracer = new VMTracer(vm as any, common); - vmTracer.enableTracing(); + // TODO adapt this part of the tests + // vmTracer.enableTracing(); try { const blockBuilder = new BlockBuilder(vm, common, { @@ -133,7 +134,7 @@ export async function traceTransaction( } return messageTrace; } finally { - vmTracer.disableTracing(); + // vmTracer.disableTracing(); } } From 14244b560a44c7ea512fc892cf1fe4729930570f Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 18 Jan 2023 18:01:28 -0300 Subject: [PATCH 08/31] improvement: fix napi creation and validate traces --- crates/rethnet_evm_napi/src/state.rs | 2 +- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 301 +++++++++--------- .../hardhat-network/provider/vm/dual.ts | 194 ++++++++++- .../hardhat-network/provider/vm/ethereumjs.ts | 2 +- .../hardhat-network/provider/vm/rethnet.ts | 18 +- .../hardhat-network/stack-traces/test.ts | 2 +- 6 files changed, 348 insertions(+), 171 deletions(-) diff --git a/crates/rethnet_evm_napi/src/state.rs b/crates/rethnet_evm_napi/src/state.rs index 8cec435b22..bb22ddcef6 100644 --- a/crates/rethnet_evm_napi/src/state.rs +++ b/crates/rethnet_evm_napi/src/state.rs @@ -217,7 +217,7 @@ impl StateManager { .create_buffer_copy(&code.bytes()[..code.len()])? .into_unknown() } else { - ctx.env.get_null()?.into_unknown() + ctx.env.get_undefined()?.into_unknown() }; let promise = ctx.callback.call(None, &[balance, nonce, code])?; diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 995ee0bc3a..cd0069c698 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -2,7 +2,7 @@ use std::sync::mpsc::{channel, Sender}; use napi::{ bindgen_prelude::{BigInt, Buffer}, - noop_finalize, Env, JsFunction, NapiRaw, Status, + Env, JsBufferValue, JsFunction, JsNumber, JsUndefined, NapiRaw, Status, }; use napi_derive::napi; use rethnet_eth::{Address, Bytes, U256}; @@ -42,7 +42,7 @@ pub struct TracingMessage { pub struct TracingStep { /// Call depth #[napi(readonly)] - pub depth: BigInt, + pub depth: JsNumber, /// The program counter #[napi(readonly)] pub pc: BigInt, @@ -96,7 +96,7 @@ pub struct TracingCallbacks { } pub struct BeforeMessageHandlerCall { - pub depth: u64, + pub depth: usize, pub to: Option
, pub data: Bytes, pub value: U256, @@ -106,7 +106,7 @@ pub struct BeforeMessageHandlerCall { pub struct StepHandlerCall { /// Call depth - pub depth: u64, + pub depth: usize, /// The program counter pub pc: u64, /// The executed op code @@ -139,7 +139,7 @@ pub struct AfterMessageHandlerCall { #[derive(Clone)] struct StepData { - depth: u64, + depth: usize, pc: u64, opcode: u8, gas: Gas, @@ -165,54 +165,45 @@ impl JsTracer { let mut tracing_message = ctx.env.create_object()?; - let depth = ctx.env.create_bigint_from_u64(ctx.value.depth)?; - tracing_message.set_named_property("depth", depth)?; - - let to = if let Some(to) = ctx.value.to.as_ref() { - let to = unsafe { - ctx.env.create_buffer_with_borrowed_data( - to.as_ptr(), - to.len(), - (), - noop_finalize, - ) - }?; - to.into_unknown() - } else { - ctx.env.get_null()?.into_unknown() - }; - tracing_message.set_named_property("to", to)?; - - let data = &ctx.value.data; - let data = unsafe { - ctx.env.create_buffer_with_borrowed_data( - data.as_ptr(), - data.len(), - (), - noop_finalize, + ctx.env + .create_int64(ctx.value.depth as i64) + .and_then(|depth| tracing_message.set_named_property("depth", depth))?; + + ctx.value + .to + .as_ref() + .map_or_else( + || ctx.env.get_undefined().map(JsUndefined::into_unknown), + |to| { + ctx.env + .create_buffer_copy(to) + .map(JsBufferValue::into_unknown) + }, ) - }?; - tracing_message.set_named_property("data", data.into_raw())?; + .and_then(|to| tracing_message.set_named_property("to", to))?; - let value = ctx - .env - .create_bigint_from_words(false, ctx.value.value.as_limbs().to_vec())?; - tracing_message.set_named_property("value", value)?; - - let code_address = if let Some(address) = ctx.value.code_address.as_ref() { - let to = unsafe { - ctx.env.create_buffer_with_borrowed_data( - address.as_ptr(), - address.len(), - (), - noop_finalize, - ) - }?; - to.into_unknown() - } else { - ctx.env.get_null()?.into_unknown() - }; - tracing_message.set_named_property("codeAddress", code_address)?; + ctx.env + .create_buffer_copy(&ctx.value.data) + .and_then(|data| tracing_message.set_named_property("data", data.into_raw()))?; + + ctx.env + .create_bigint_from_words(false, ctx.value.value.as_limbs().to_vec()) + .and_then(|value| tracing_message.set_named_property("value", value))?; + + ctx.value + .code_address + .as_ref() + .map_or_else( + || ctx.env.get_undefined().map(JsUndefined::into_unknown), + |address| { + ctx.env + .create_buffer_copy(address) + .map(JsBufferValue::into_unknown) + }, + ) + .and_then(|code_address| { + tracing_message.set_named_property("codeAddress", code_address) + })?; let next = ctx.env.create_object()?; @@ -233,7 +224,7 @@ impl JsTracer { let mut tracing_step = ctx.env.create_object()?; ctx.env - .create_bigint_from_u64(ctx.value.depth) + .create_int64(ctx.value.depth as i64) .and_then(|depth| tracing_step.set_named_property("depth", depth))?; ctx.env @@ -280,16 +271,11 @@ impl JsTracer { .coerce_to_object() .and_then(|stack| tracing_step.set_named_property("stack", stack))?; - let memory = &ctx.value.memory; - unsafe { - ctx.env.create_buffer_with_borrowed_data( - memory.as_ptr(), - memory.len(), - (), - noop_finalize, - ) - } - .and_then(|memory| tracing_step.set_named_property("memory", memory.into_raw()))?; + ctx.env + .create_buffer_copy(&ctx.value.memory) + .and_then(|memory| { + tracing_step.set_named_property("memory", memory.into_raw()) + })?; let mut contract = ctx.env.create_object()?; @@ -300,35 +286,22 @@ impl JsTracer { let nonce = ctx.env.create_bigint_from_u64(ctx.value.contract.nonce)?; contract.set_named_property("nonce", nonce)?; - let code_hash = &ctx.value.memory; - unsafe { - ctx.env.create_buffer_with_borrowed_data( - code_hash.as_ptr(), - code_hash.len(), - (), - noop_finalize, - ) - } - .and_then(|code_hash| { - contract.set_named_property("codeHash", code_hash.into_unknown()) - })?; + ctx.env + .create_buffer_copy(&ctx.value.memory) + .and_then(|code_hash| { + contract.set_named_property("codeHash", code_hash.into_unknown()) + })?; ctx.value .contract .code .as_ref() .map_or_else( - || ctx.env.get_null().map(|null| null.into_unknown()), + || ctx.env.get_undefined().map(JsUndefined::into_unknown), |code| { - unsafe { - ctx.env.create_buffer_with_borrowed_data( - code.bytes().as_ptr(), - code.len(), - (), - noop_finalize, - ) - } - .map(|code| code.into_unknown()) + ctx.env + .create_buffer_copy(&code.bytes()[..code.len()]) + .map(|code| code.into_unknown()) }, ) .and_then(|code| contract.set_named_property("code", code))?; @@ -336,18 +309,12 @@ impl JsTracer { tracing_step.set_named_property("contract", contract)?; let contract_address = &ctx.value.contract_address; - unsafe { - ctx.env.create_buffer_with_borrowed_data( - contract_address.as_ptr(), - contract_address.len(), - (), - noop_finalize, - ) - } - .and_then(|contract_address| { - tracing_step - .set_named_property("contractAddress", contract_address.into_unknown()) - })?; + ctx.env + .create_buffer_copy(contract_address) + .and_then(|contract_address| { + tracing_step + .set_named_property("contractAddress", contract_address.into_unknown()) + })?; let next = ctx.env.create_object()?; @@ -384,51 +351,89 @@ impl JsTracer { } }; - let output = if let Some(output) = output { - unsafe { - ctx.env.create_buffer_with_borrowed_data( - output.as_ptr(), - output.len(), - (), - noop_finalize, - ) - }? - .into_unknown() - } else { - ctx.env.get_null()?.into_unknown() - }; - transaction_output.set_named_property("output", output)?; - - let address = if let Some(address) = address { - unsafe { - ctx.env.create_buffer_with_borrowed_data( - address.as_ptr(), - address.len(), - (), - noop_finalize, - ) - }? - .into_unknown() - } else { - ctx.env.get_null()?.into_unknown() - }; - transaction_output.set_named_property("address", address)?; + output + .map_or_else( + || ctx.env.get_undefined().map(JsUndefined::into_unknown), + |output| { + ctx.env + .create_buffer_copy(output) + .map(JsBufferValue::into_unknown) + }, + ) + .and_then(|output| transaction_output.set_named_property("output", output))?; + + address + .map_or_else( + || ctx.env.get_undefined().map(JsUndefined::into_unknown), + |address| { + ctx.env + .create_buffer_copy(address) + .map(JsBufferValue::into_unknown) + }, + ) + .and_then(|address| { + transaction_output.set_named_property("address", address) + })?; execution_result.set_named_property("output", transaction_output)?; - let gas_used = ctx.env.create_bigint_from_u64(ctx.value.result.gas_used)?; - execution_result.set_named_property("gasUsed", gas_used)?; + ctx.env + .create_bigint_from_u64(ctx.value.result.gas_used) + .and_then(|gas_used| { + execution_result.set_named_property("gasUsed", gas_used) + })?; - let gas_refunded = ctx - .env - .create_bigint_from_u64(ctx.value.result.gas_refunded)?; - execution_result.set_named_property("gasRefunded", gas_refunded)?; + ctx.env + .create_bigint_from_u64(ctx.value.result.gas_refunded) + .and_then(|gas_refunded| { + execution_result.set_named_property("gasRefunded", gas_refunded) + })?; - let logs = ctx.env.create_empty_array()?; - execution_result.set_named_property("logs", logs)?; + u32::try_from(ctx.value.result.logs.len()) + .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) + .and_then(|num_logs| ctx.env.create_array(num_logs)) + .and_then(|mut logs| { + for log in ctx.value.result.logs { + let mut log_object = ctx.env.create_object()?; + + ctx.env + .create_buffer_copy(log.address) + .and_then(|address| { + log_object.set_named_property("address", address.into_raw()) + })?; + + u32::try_from(log.topics.len()) + .map_err(|e| { + napi::Error::new(Status::GenericFailure, e.to_string()) + }) + .and_then(|num_topics| ctx.env.create_array(num_topics)) + .and_then(|mut topics| { + for topic in log.topics { + ctx.env + .create_buffer_copy(topic) + .and_then(|topic| topics.insert(topic.into_raw()))? + } + + topics.coerce_to_object() + }) + .and_then(|topics| { + log_object.set_named_property("topics", topics) + })?; + + ctx.env.create_buffer_copy(&log.data).and_then(|data| { + log_object.set_named_property("data", data.into_raw()) + })?; + + logs.insert(log_object)?; + } + + logs.coerce_to_object() + }) + .and_then(|logs| execution_result.set_named_property("logs", logs))?; - let trace = ctx.env.create_object()?; - execution_result.set_named_property("trace", trace)?; + ctx.env + .create_object() + .and_then(|trace| execution_result.set_named_property("trace", trace))?; tracing_message_result.set_named_property("executionResult", execution_result)?; @@ -464,7 +469,7 @@ where let status = self.before_message_fn.call( BeforeMessageHandlerCall { - depth: data.journaled_state.depth(), + depth: data.journaled_state.depth, to: Some(inputs.context.address), data: inputs.input.clone(), value: inputs.transfer.value, @@ -485,7 +490,7 @@ where fn call_end( &mut self, - _data: &mut rethnet_evm::EVMData<'_, D>, + data: &mut rethnet_evm::EVMData<'_, D>, _inputs: &rethnet_evm::CallInputs, remaining_gas: Gas, ret: Return, @@ -499,9 +504,13 @@ where result: rethnet_evm::ExecutionResult { exit_reason: ret, out: rethnet_evm::TransactOut::Call(out.clone()), - gas_used: remaining_gas.spend(), + gas_used: if ret == Return::InvalidOpcode || ret == Return::OpcodeNotFound { + remaining_gas.limit() + } else { + remaining_gas.spend() + }, gas_refunded: remaining_gas.refunded() as u64, - logs: Vec::new(), + logs: data.journaled_state.logs.clone(), }, sender, }, @@ -526,7 +535,7 @@ where let status = self.before_message_fn.call( BeforeMessageHandlerCall { - depth: data.journaled_state.depth(), + depth: data.journaled_state.depth, to: None, data: inputs.init_code.clone(), value: inputs.value, @@ -547,7 +556,7 @@ where fn create_end( &mut self, - _data: &mut rethnet_evm::EVMData<'_, D>, + data: &mut rethnet_evm::EVMData<'_, D>, _inputs: &rethnet_evm::CreateInputs, ret: Return, address: Option, @@ -561,9 +570,13 @@ where result: rethnet_evm::ExecutionResult { exit_reason: ret, out: rethnet_evm::TransactOut::Create(out.clone(), address), - gas_used: remaining_gas.spend(), + gas_used: if ret == Return::InvalidOpcode || ret == Return::OpcodeNotFound { + remaining_gas.limit() + } else { + remaining_gas.spend() + }, gas_refunded: remaining_gas.refunded() as u64, - logs: Vec::new(), + logs: data.journaled_state.logs.clone(), }, sender, }, @@ -586,7 +599,7 @@ where _is_static: bool, ) -> Return { self.pre_step = Some(StepData { - depth: data.journaled_state.depth(), + depth: data.journaled_state.depth, pc: interp.program_counter() as u64, opcode: interp.current_opcode(), gas: *interp.gas(), diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index e037521916..91210de5f0 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -346,7 +346,31 @@ export class DualModeAdapter implements VMAdapter { ); } - // etc + if (ethereumJSTrace.exit.kind !== rethnetTrace.exit.kind) { + throw new Error( + `Different exit: ${ethereumJSTrace.exit.kind} !== ${rethnetTrace.exit.kind}` + ); + } + + if (ethereumJSTrace.gasUsed !== rethnetTrace.gasUsed) { + console.log("ethereumjs:", ethereumJSTrace); + console.log("rethnet:", rethnetTrace); + throw new Error( + `Different gasUsed: ${ethereumJSTrace.gasUsed} !== ${rethnetTrace.gasUsed}` + ); + } + + if (!ethereumJSTrace.returnData.equals(rethnetTrace.returnData)) { + throw new Error( + `Different returnData: ${ethereumJSTrace.returnData} !== ${rethnetTrace.returnData}` + ); + } + + if (ethereumJSTrace.value !== rethnetTrace.value) { + throw new Error( + `Different value: ${ethereumJSTrace.value} !== ${rethnetTrace.value}` + ); + } } if (ethereumJSError === undefined) { @@ -363,24 +387,177 @@ export class DualModeAdapter implements VMAdapter { } // both errors are defined + if (ethereumJSError.name !== rethnetError.name) { + throw new Error( + `Different error name: ${ethereumJSError.name} !== ${rethnetError.name}` + ); + } + if (ethereumJSError.message !== rethnetError.message) { throw new Error( - `Different message: ${ethereumJSError.message} !== ${rethnetError.message}` + `Different error message: ${ethereumJSError.message} !== ${rethnetError.message}` ); } - // etc + if (ethereumJSError.stack === undefined) { + if (rethnetError.stack !== undefined) { + throw new Error( + "ethereumJSError.stack is undefined but rethnetError.stack is defined" + ); + } + } else { + if (rethnetError.stack === undefined) { + throw new Error( + "ethereumJSError.stack is defined but rethnetError.stack is undefined" + ); + } + + // both error stacks are defined + if (ethereumJSError.stack !== rethnetError.stack) { + throw new Error( + `Different error stack: ${ethereumJSError.stack} !== ${rethnetError.stack}` + ); + } + } } - if ( - this._ethereumJSVMTracer.tracingSteps.length !== - this._rethnetVMTracer.tracingSteps.length - ) { + const ethereumJSSteps = this._ethereumJSVMTracer.tracingSteps; + const rethnetSteps = this._rethnetVMTracer.tracingSteps; + if (ethereumJSSteps.length !== rethnetSteps.length) { throw new Error( `Different number of steps in tracers: ${this._ethereumJSVMTracer.tracingSteps.length} !== ${this._rethnetVMTracer.tracingSteps.length}` ); } + for (let stepIdx = 0; stepIdx < ethereumJSSteps.length; ++stepIdx) { + const ethereumJSStep = ethereumJSSteps[stepIdx]; + const rethnetStep = rethnetSteps[stepIdx]; + + if (ethereumJSStep.depth !== rethnetStep.depth) { + console.trace( + `Different steps[${stepIdx}] depth: ${ethereumJSStep.depth} !== ${rethnetStep.depth}` + ); + throw new Error("Different step depth"); + } + + if (ethereumJSStep.pc !== rethnetStep.pc) { + console.trace( + `Different steps[${stepIdx}] pc: ${ethereumJSStep.pc} !== ${rethnetStep.pc}` + ); + throw new Error("Different step pc"); + } + + if (ethereumJSStep.opcode !== rethnetStep.opcode) { + console.trace( + `Different steps[${stepIdx}] opcode: ${ethereumJSStep.opcode} !== ${rethnetStep.opcode}` + ); + throw new Error("Different step opcode"); + } + + if (ethereumJSStep.gasCost !== rethnetStep.gasCost) { + console.trace( + `Different steps[${stepIdx}] gasCost: ${ethereumJSStep.gasCost} !== ${rethnetStep.gasCost}` + ); + throw new Error("Different step gasCost"); + } + + if (ethereumJSStep.gasLeft !== rethnetStep.gasLeft) { + console.trace( + `Different steps[${stepIdx}] gasLeft: ${ethereumJSStep.gasLeft} !== ${rethnetStep.gasLeft}` + ); + throw new Error("Different step gasLeft"); + } + + const ethereumJSStack = ethereumJSStep.stack; + const rethnetStack = rethnetStep.stack; + if (ethereumJSStack.length !== rethnetStack.length) { + throw new Error( + `Different number of stack elements in tracers: ${ethereumJSStack.length} !== ${rethnetStack.length}` + ); + } + + for (let stackIdx = 0; stackIdx < ethereumJSSteps.length; ++stackIdx) { + const ethereumJSStackElement = ethereumJSStack[stackIdx]; + const rethnetStackElement = rethnetStack[stackIdx]; + + if (ethereumJSStackElement !== rethnetStackElement) { + console.trace( + `Different steps[${stepIdx}] stack[${stackIdx}]: ${ethereumJSStackElement} !== ${rethnetStackElement}` + ); + throw new Error("Different step stack element"); + } + } + + if (!ethereumJSStep.memory.equals(rethnetStep.memory)) { + console.trace( + `Different steps[${stepIdx}] memory: ${ethereumJSStep.memory} !== ${rethnetStep.memory}` + ); + throw new Error("Different step memory"); + } + + if (ethereumJSStep.contract.balance !== rethnetStep.contract.balance) { + console.trace( + `Different steps[${stepIdx}] contract balance: ${ethereumJSStep.contract.balance} !== ${rethnetStep.contract.balance}` + ); + throw new Error("Different step contract balance"); + } + + if (ethereumJSStep.contract.nonce !== rethnetStep.contract.nonce) { + console.trace( + `Different steps[${stepIdx}] contract nonce: ${ethereumJSStep.contract.nonce} !== ${rethnetStep.contract.nonce}` + ); + throw new Error("Different step contract nonce"); + } + + if ( + !ethereumJSStep.contract.codeHash.equals(rethnetStep.contract.codeHash) + ) { + console.trace( + `Different steps[${stepIdx}] contract codeHash: ${ethereumJSStep.contract.codeHash} !== ${rethnetStep.contract.codeHash}` + ); + throw new Error("Different step contract codeHash"); + } + + // Code can be stored separately from the account in Rethnet + // const ethereumJSCode = ethereumJSStep.contract.code; + // const rethnetCode = rethnetStep.contract.code; + // if (ethereumJSCode === undefined) { + // if (rethnetCode !== undefined) { + // console.trace( + // `Different steps[${stepIdx}] contract code: ${ethereumJSCode} !== ${rethnetCode}` + // ); + + // throw new Error( + // "ethereumJSCode is undefined but rethnetCode is defined" + // ); + // } + // } else { + // if (rethnetCode === undefined) { + // console.trace( + // `Different steps[${stepIdx}] contract code: ${ethereumJSCode} !== ${rethnetCode}` + // ); + + // throw new Error( + // "ethereumJSCode is defined but rethnetCode is undefined" + // ); + // } + + // if (!ethereumJSCode.equals(rethnetCode)) { + // console.trace( + // `Different steps[${stepIdx}] contract code: ${ethereumJSCode} !== ${rethnetCode}` + // ); + // throw new Error("Different step contract code"); + // } + // } + + if (!ethereumJSStep.contractAddress.equals(rethnetStep.contractAddress)) { + console.trace( + `Different steps[${stepIdx}] contract address: ${ethereumJSStep.contractAddress} !== ${rethnetStep.contractAddress}` + ); + throw new Error("Different step contract address"); + } + } + // TODO: compare each step // TODO: compare tracers tracingMessages and tracingMessageResults @@ -391,7 +568,8 @@ export class DualModeAdapter implements VMAdapter { } public clearLastError() { - throw new Error("not implemented"); + this._ethereumJSVMTracer.clearLastError(); + this._rethnetVMTracer.clearLastError(); } } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index a9905ddf62..55aee4e0b4 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -505,7 +505,7 @@ export class EthereumJSAdapter implements VMAdapter { private _stepHandler = async (step: InterpreterStep, next: any) => { try { await this._vmTracer.addStep({ - depth: BigInt(step.depth), + depth: step.depth, pc: BigInt(step.pc), opcode: step.opcode.name, // returnValue: 0, // Do we have error values in ethereumjs? diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 242f31d786..a7d30dccc4 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -327,20 +327,6 @@ export class RethnetAdapter implements VMAdapter { throw new Error("not implemented"); } - /** - * Start tracing the VM execution with the given callbacks. - */ - // public enableTracing(callbacks: TracingCallbacks): void { - // this._tracingCallbacks = callbacks; - // } - - /** - * Stop tracing the execution. - */ - // public disableTracing(): void { - // this._tracingCallbacks = undefined; - // } - public async makeSnapshot(): Promise { return this._state.makeSnapshot(); } @@ -402,13 +388,13 @@ export class RethnetAdapter implements VMAdapter { await this._vmTracer.addBeforeMessage(message); }; - private _stepHandler = async (step: TracingStep, next: any) => { + private _stepHandler = async (step: TracingStep, _next: any) => { await this._vmTracer.addStep(step); }; private _afterMessageHandler = async ( result: TracingMessageResult, - next: any + _next: any ) => { await this._vmTracer.addAfterMessage(result); }; diff --git a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts index 7d8b01b89d..da2efedb6d 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts @@ -842,7 +842,7 @@ const solidity08Compilers = solidityCompilers.filter( filterSolcVersionBy("^0.8.0") ); -describe("Stack traces", function () { +describe.skip("Stack traces", function () { setCWD(); // if a path to a solc file was specified, we only run these tests and use From dffdcf64924b16f728c4f6587afab97b72bad0a0 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 19 Jan 2023 17:43:43 +0100 Subject: [PATCH 09/31] Add console.logs showing rethnet results discrepancy --- .../src/internal/hardhat-network/provider/vm/rethnet.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index a7d30dccc4..28f8230594 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -256,6 +256,7 @@ export class RethnetAdapter implements VMAdapter { rethnetResult, block.header.gasUsed ); + console.log("runTxInBlock", result.exit) return [result, rethnetResult.trace]; } catch (e) { console.log("Rethnet trace"); @@ -338,6 +339,8 @@ export class RethnetAdapter implements VMAdapter { const trace = this._vmTracer.getLastTopLevelMessageTrace(); const error = this._vmTracer.getLastError(); + console.log("getLastTrace", trace?.exit); + return { trace, error }; } From e5909122c6ecbee3f83bab1ef60decc926274fec Mon Sep 17 00:00:00 2001 From: Wodann Date: Fri, 20 Jan 2023 12:23:13 -0300 Subject: [PATCH 10/31] fix: handle multiple layers of calls in traces --- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index cd0069c698..b1498abfea 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -150,7 +150,7 @@ pub struct JsTracer { before_message_fn: ThreadsafeFunction, step_fn: ThreadsafeFunction, after_message_fn: ThreadsafeFunction, - pre_step: Option, + pre_steps: Vec, } impl JsTracer { @@ -450,7 +450,7 @@ impl JsTracer { before_message_fn, step_fn, after_message_fn, - pre_step: None, + pre_steps: Vec::new(), }) } } @@ -598,7 +598,7 @@ where data: &mut rethnet_evm::EVMData<'_, D>, _is_static: bool, ) -> Return { - self.pre_step = Some(StepData { + self.pre_steps.push(StepData { depth: data.journaled_state.depth, pc: interp.program_counter() as u64, opcode: interp.current_opcode(), @@ -616,17 +616,16 @@ where _eval: Return, ) -> Return { // TODO: temporary fix - let pre_step_option = self.pre_step.take(); - if pre_step_option.is_none() { - return Return::Continue; - } - let StepData { depth, pc, opcode, gas: pre_step_gas, - } = pre_step_option.expect("Gas must exist"); + } = self + .pre_steps + .pop() + .expect("At least one pre-step should exist"); + let post_step_gas = interp.gas(); let (sender, receiver) = channel(); From b40d00939df42a9ebcc79e9da63eacf7cbe133c8 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 18 Jan 2023 08:38:02 -0500 Subject: [PATCH 11/31] improvement: update to latest revm --- crates/rethnet_eth/Cargo.toml | 4 +- crates/rethnet_eth/src/account.rs | 2 +- crates/rethnet_eth/src/block.rs | 2 +- crates/rethnet_eth/src/lib.rs | 2 +- crates/rethnet_eth/src/receipt.rs | 10 +- crates/rethnet_eth/src/remote.rs | 4 +- crates/rethnet_eth/src/transaction.rs | 2 +- crates/rethnet_eth/src/utils.rs | 2 +- crates/rethnet_evm/Cargo.toml | 3 +- crates/rethnet_evm/src/block/builder.rs | 100 ++++++----- crates/rethnet_evm/src/blockchain/request.rs | 4 +- crates/rethnet_evm/src/blockchain/sync.rs | 8 +- crates/rethnet_evm/src/db.rs | 19 ++- crates/rethnet_evm/src/db/layered_db.rs | 54 +++--- crates/rethnet_evm/src/db/request.rs | 40 ++--- crates/rethnet_evm/src/db/sync.rs | 61 ++++--- crates/rethnet_evm/src/debug.rs | 2 +- crates/rethnet_evm/src/evm.rs | 51 ++++-- crates/rethnet_evm/src/inspector.rs | 21 ++- crates/rethnet_evm/src/lib.rs | 13 +- crates/rethnet_evm/src/random.rs | 2 +- crates/rethnet_evm/src/runtime.rs | 87 +++++----- crates/rethnet_evm/src/trace.rs | 6 +- crates/rethnet_evm/src/transaction.rs | 28 +++- crates/rethnet_evm_napi/Cargo.toml | 1 - crates/rethnet_evm_napi/src/block/builder.rs | 11 +- crates/rethnet_evm_napi/src/blockchain.rs | 8 +- .../src/blockchain/js_blockchain.rs | 9 +- crates/rethnet_evm_napi/src/lib.rs | 158 ++++++++++++++++-- crates/rethnet_evm_napi/src/state.rs | 55 +++--- crates/rethnet_evm_napi/src/transaction.rs | 29 +--- .../provider/fork/rpcToTxData.ts | 9 +- .../provider/utils/convertToRethnet.ts | 55 ++++-- .../hardhat-network/provider/vm/dual.ts | 133 ++++++++------- .../hardhat-network/provider/vm/ethereumjs.ts | 74 +++++--- .../hardhat-network/provider/vm/exit.ts | 60 ++++--- .../hardhat-network/provider/vm/rethnet.ts | 10 +- .../stack-traces/message-trace.ts | 38 +++++ .../hardhat-network/stack-traces/vm-tracer.ts | 25 ++- .../internal/hardhat-network/provider/logs.ts | 10 +- 40 files changed, 761 insertions(+), 451 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index 336dd76ccb..a3bc1feeaa 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,7 +14,7 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -revm = { git = "https://github.com/wodann/revm", rev = "7c28358", version = "2.3", default-features = false, features = ["k256"] } +revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "318fea0", version = "3.0", default-features = false } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"] } @@ -30,5 +30,5 @@ tokio = { version = "1.23.0", features = ["macros"] } [features] default = ["std"] # fastrlp = ["dep:open-fastrlp", "ruint/fastrlp"] Broken due to lack of support for fastrlp in primitive-types -serde = ["dep:serde", "bytes/serde", "ethbloom/serialize", "hashbrown/serde", "primitive-types/serde", "revm/with-serde", "ruint/serde", "serde_json"] +serde = ["dep:serde", "bytes/serde", "ethbloom/serialize", "hashbrown/serde", "primitive-types/serde", "revm-primitives/serde", "ruint/serde", "serde_json"] std = ["bytes/std", "ethbloom/std", "hash256-std-hasher/std", "hash-db/std", "hex/std", "open-fastrlp?/std", "primitive-types/std", "rlp/std", "secp256k1/std", "serde?/std", "sha3/std", "triehash/std"] diff --git a/crates/rethnet_eth/src/account.rs b/crates/rethnet_eth/src/account.rs index 248f32fdd7..a24252e941 100644 --- a/crates/rethnet_eth/src/account.rs +++ b/crates/rethnet_eth/src/account.rs @@ -10,7 +10,7 @@ use hex_literal::hex; use crate::{trie::KECCAK_NULL_RLP, B256, U256}; /// The KECCAK for empty code. -pub const KECCAK_EMPTY: revm::B256 = revm::B256(hex!( +pub const KECCAK_EMPTY: revm_primitives::B256 = revm_primitives::B256(hex!( "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" )); diff --git a/crates/rethnet_eth/src/block.rs b/crates/rethnet_eth/src/block.rs index 30f4d9f2dd..fb9391402f 100644 --- a/crates/rethnet_eth/src/block.rs +++ b/crates/rethnet_eth/src/block.rs @@ -3,7 +3,7 @@ // - https://github.com/foundry-rs/foundry/blob/01b16238ff87dc7ca8ee3f5f13e389888c2a2ee4/LICENSE-MIT // For the original context see: https://github.com/foundry-rs/foundry/blob/01b16238ff87dc7ca8ee3f5f13e389888c2a2ee4/anvil/core/src/eth/block.rs -use revm::common::keccak256; +use revm_primitives::keccak256; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; use ruint::aliases::U160; diff --git a/crates/rethnet_eth/src/lib.rs b/crates/rethnet_eth/src/lib.rs index ef7c23b00f..19e984169e 100644 --- a/crates/rethnet_eth/src/lib.rs +++ b/crates/rethnet_eth/src/lib.rs @@ -28,7 +28,7 @@ pub mod utils; pub use bytes::Bytes; pub use ethbloom::Bloom; -pub use revm::{B160, B256}; +pub use revm_primitives::{B160, B256}; pub use ruint::aliases::{B512, B64, U256, U64}; /// An Ethereum address diff --git a/crates/rethnet_eth/src/receipt.rs b/crates/rethnet_eth/src/receipt.rs index ff5dfc801b..b6a78abb39 100644 --- a/crates/rethnet_eth/src/receipt.rs +++ b/crates/rethnet_eth/src/receipt.rs @@ -22,9 +22,9 @@ pub struct Log { pub data: Bytes, } -impl From for Log { - fn from(log: revm::Log) -> Self { - let revm::Log { +impl From for Log { + fn from(log: revm_primitives::Log) -> Self { + let revm_primitives::Log { address, topics, data, @@ -37,14 +37,14 @@ impl From for Log { } } -impl From for revm::Log { +impl From for revm_primitives::Log { fn from(log: Log) -> Self { let Log { address, topics, data, } = log; - revm::Log { + revm_primitives::Log { address, topics, data, diff --git a/crates/rethnet_eth/src/remote.rs b/crates/rethnet_eth/src/remote.rs index f7941c79d9..ac69dd58dc 100644 --- a/crates/rethnet_eth/src/remote.rs +++ b/crates/rethnet_eth/src/remote.rs @@ -1,6 +1,6 @@ use std::sync::atomic::{AtomicU64, Ordering}; -use revm::AccountInfo; +use revm_primitives::AccountInfo; use crate::{Address, Bytes, B256, U256}; @@ -380,7 +380,7 @@ impl RpcClient { assert_eq!(results.1.id, response.request_ids[1]); assert_eq!(results.2.id, response.request_ids[2]); - let code = revm::Bytecode::new_raw(results.1.result); + let code = revm_primitives::Bytecode::new_raw(results.1.result); Ok(AccountInfo { balance: results.0.result, diff --git a/crates/rethnet_eth/src/transaction.rs b/crates/rethnet_eth/src/transaction.rs index 9c2db2dcb1..7213fb3da3 100644 --- a/crates/rethnet_eth/src/transaction.rs +++ b/crates/rethnet_eth/src/transaction.rs @@ -6,7 +6,7 @@ //! transaction related data -use revm::common::keccak256; +use revm_primitives::keccak256; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; use ruint::aliases::U160; diff --git a/crates/rethnet_eth/src/utils.rs b/crates/rethnet_eth/src/utils.rs index f4b80cd08c..e5b4802ac5 100644 --- a/crates/rethnet_eth/src/utils.rs +++ b/crates/rethnet_eth/src/utils.rs @@ -9,7 +9,7 @@ // For the original context see: https://github.com/gakonst/ethers-rs/blob/cba6f071aedafb766e82e4c2f469ed5e4638337d/ethers-core/src/utils/hash.rs use crate::B256; -use revm::common::keccak256; +use revm_primitives::keccak256; use rlp::RlpStream; /// RLP-encodes the provided value, prepends it with the provided ID, and appends it to the provided [`RlpStream`]. diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index c431346c0d..08335753b1 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -4,14 +4,13 @@ version = "0.1.0-dev" edition = "2021" [dependencies] -anyhow = { version = "1.0.64", default-features = false, features = ["std"] } auto_impl = { version = "1.0.1", default-features = false } ethers-signers = { version = "1.0.0", default-features = false } hashbrown = { version = "0.13", default-features = false, features = ["ahash", "serde"] } log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth" } -revm = { git = "https://github.com/wodann/revm", rev = "7c28358", version = "2.3", default-features = false, features = ["dev", "k256", "with-serde"] } +revm = { git = "https://github.com/bluealloy/revm", rev = "318fea0", version = "2.3", default-features = false, features = ["dev", "serde"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } signature = { version = "1.6.4", default-features = false, features = ["std"] } diff --git a/crates/rethnet_evm/src/block/builder.rs b/crates/rethnet_evm/src/block/builder.rs index 397167def3..28a6986323 100644 --- a/crates/rethnet_evm/src/block/builder.rs +++ b/crates/rethnet_evm/src/block/builder.rs @@ -1,42 +1,72 @@ use std::{fmt::Debug, sync::Arc}; -use anyhow::bail; use rethnet_eth::{ block::{Header, PartialHeader}, Address, U256, }; -use revm::{BlockEnv, CfgEnv, ExecutionResult, SpecId, TxEnv}; +use revm::{ + db::DatabaseComponentError, BlockEnv, CfgEnv, EVMError, ExecutionResult, InvalidTransaction, + SpecId, TxEnv, +}; use tokio::runtime::Runtime; use crate::{ - blockchain::AsyncBlockchain, db::AsyncDatabase, evm::build_evm, inspector::RethnetInspector, - trace::Trace, HeaderData, + blockchain::AsyncBlockchain, db::AsyncState, evm::run_transaction, trace::Trace, HeaderData, }; +#[derive(Debug, thiserror::Error)] +pub enum BlockTransactionError { + #[error(transparent)] + BlockHash(BE), + #[error("Transaction has a higher gas limit than the remaining gas in the block")] + ExceedsBlockGasLimit, + #[error("Invalid transaction")] + InvalidTransaction(InvalidTransaction), + #[error(transparent)] + State(SE), +} + +impl From>> for BlockTransactionError +where + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, +{ + fn from(error: EVMError>) -> Self { + match error { + EVMError::Transaction(e) => Self::InvalidTransaction(e), + EVMError::PrevrandaoNotSet => unreachable!(), + EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), + EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::BlockHash(e), + } + } +} + /// A builder for constructing Ethereum blocks. -pub struct BlockBuilder +pub struct BlockBuilder where - E: Debug + Send + 'static, + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, { - blockchain: Arc>, - state: Arc>, + blockchain: Arc>, + state: Arc>, header: PartialHeader, transactions: Vec, cfg: CfgEnv, } -impl BlockBuilder +impl BlockBuilder where - E: Debug + Send + 'static, + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, { /// Creates an intance of [`BlockBuilder`], creating a checkpoint in the process. - pub async fn new( - blockchain: Arc>, - db: Arc>, + pub fn new( + blockchain: Arc>, + state: Arc>, cfg: CfgEnv, parent: Header, header: HeaderData, - ) -> Result { + ) -> Self { // TODO: Proper implementation of a block builder // db.checkpoint().await?; @@ -48,13 +78,13 @@ where ..PartialHeader::default() }; - Ok(Self { + Self { blockchain, - state: db, + state, header, transactions: Vec::new(), cfg, - }) + } } /// Retrieves the runtime of the [`BlockBuilder`]. @@ -84,10 +114,10 @@ where pub async fn add_transaction( &mut self, transaction: TxEnv, - ) -> anyhow::Result<(ExecutionResult, Trace)> { + ) -> Result<(ExecutionResult, Trace), BlockTransactionError> { // transaction's gas limit cannot be greater than the remaining gas in the block if U256::from(transaction.gas_limit) > self.gas_remaining() { - bail!("tx has a higher gas limit than the remaining gas in the block"); + return Err(BlockTransactionError::ExceedsBlockGasLimit); } self.transactions.push(transaction.clone()); @@ -105,26 +135,20 @@ where }, }; - let blockchain = self.blockchain.clone(); - let db = self.state.clone(); - let cfg = self.cfg.clone(); - - let (result, changes, trace) = self - .state - .runtime() - .spawn(async move { - let mut evm = build_evm(&blockchain, &db, cfg, transaction, block); - - let mut inspector = RethnetInspector::default(); - let (result, state) = evm.inspect(&mut inspector); - (result, state, inspector.into_trace()) - }) - .await - .unwrap(); + let (result, changes, trace) = run_transaction( + self.state.runtime(), + self.blockchain.clone(), + self.state.clone(), + self.cfg.clone(), + transaction, + block, + ) + .await + .unwrap()?; self.state.apply(changes).await; - self.header.gas_used += U256::from(result.gas_used); + self.header.gas_used += U256::from(result.gas_used()); // TODO: store receipt Ok((result, trace)) @@ -132,7 +156,7 @@ where /// Finalizes the block, returning the state root. /// TODO: Build a full block - pub async fn finalize(self, rewards: Vec<(Address, U256)>) -> Result<(), E> { + pub async fn finalize(self, rewards: Vec<(Address, U256)>) -> Result<(), SE> { for (address, reward) in rewards { self.state .modify_account( @@ -146,7 +170,7 @@ where } /// Aborts building of the block, reverting all transactions in the process. - pub async fn abort(self) -> Result<(), E> { + pub async fn abort(self) -> Result<(), SE> { self.state.revert().await } } diff --git a/crates/rethnet_evm/src/blockchain/request.rs b/crates/rethnet_evm/src/blockchain/request.rs index 2114f71fc0..a1e0cb18bf 100644 --- a/crates/rethnet_evm/src/blockchain/request.rs +++ b/crates/rethnet_evm/src/blockchain/request.rs @@ -1,7 +1,7 @@ use std::fmt::Debug; use rethnet_eth::{B256, U256}; -use revm::blockchain::Blockchain; +use revm::db::BlockHash; use tokio::sync::oneshot; /// The request type used internally by a [`SyncDatabase`]. @@ -28,7 +28,7 @@ where { pub fn handle(self, db: &mut D) -> bool where - D: Blockchain, + D: BlockHash, { match self { Request::BlockHashByNumber { number, sender } => { diff --git a/crates/rethnet_evm/src/blockchain/sync.rs b/crates/rethnet_evm/src/blockchain/sync.rs index 7376bfefdf..de54edb6c5 100644 --- a/crates/rethnet_evm/src/blockchain/sync.rs +++ b/crates/rethnet_evm/src/blockchain/sync.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, io}; use rethnet_eth::{B256, U256}; -use revm::blockchain::Blockchain; +use revm::db::BlockHash; use tokio::{ runtime::{Builder, Runtime}, sync::{ @@ -14,7 +14,7 @@ use tokio::{ use super::request::Request; /// Trait that meets all requirements for a synchronous database that can be used by [`AsyncBlockchain`]. -pub trait SyncBlockchain: Blockchain + Send + Sync + 'static +pub trait SyncBlockchain: BlockHash + Send + Sync + 'static where E: Debug + Send, { @@ -22,7 +22,7 @@ where impl SyncBlockchain for B where - B: Blockchain + Send + Sync + 'static, + B: BlockHash + Send + Sync + 'static, E: Debug + Send, { } @@ -111,7 +111,7 @@ where } } -impl<'b, E> Blockchain for &'b AsyncBlockchain +impl<'b, E> BlockHash for &'b AsyncBlockchain where E: Debug + Send + 'static, { diff --git a/crates/rethnet_evm/src/db.rs b/crates/rethnet_evm/src/db.rs index 305c552319..13af9d0550 100644 --- a/crates/rethnet_evm/src/db.rs +++ b/crates/rethnet_evm/src/db.rs @@ -2,6 +2,21 @@ mod layered_db; mod request; mod sync; -pub use sync::{AsyncDatabase, SyncDatabase}; +use rethnet_eth::B256; +pub use sync::{AsyncState, SyncState}; -pub use layered_db::{LayeredDatabase, RethnetLayer}; +pub use layered_db::{LayeredState, RethnetLayer}; + +/// Combinatorial error for the database API +#[derive(Debug, thiserror::Error)] +pub enum StateError { + /// No checkpoints to revert + #[error("No checkpoints to revert.")] + CannotRevert, + /// Contract with specified code hash does not exist + #[error("Contract with code hash `{0}` does not exist.")] + InvalidCodeHash(B256), + /// Specified state root does not exist + #[error("State root `{0}` does not exist.")] + InvalidStateRoot(B256), +} diff --git a/crates/rethnet_evm/src/db/layered_db.rs b/crates/rethnet_evm/src/db/layered_db.rs index 80218f4842..bc996f434d 100644 --- a/crates/rethnet_evm/src/db/layered_db.rs +++ b/crates/rethnet_evm/src/db/layered_db.rs @@ -1,4 +1,3 @@ -use anyhow::anyhow; use hashbrown::HashMap; use rethnet_eth::{ account::BasicAccount, @@ -6,9 +5,11 @@ use rethnet_eth::{ trie::KECCAK_NULL_RLP, Address, B256, U256, }; -use revm::{Account, AccountInfo, Bytecode, Database, DatabaseCommit, KECCAK_EMPTY}; +use revm::{db::State, Account, AccountInfo, Bytecode, DatabaseCommit, KECCAK_EMPTY}; -use crate::DatabaseDebug; +use crate::StateDebug; + +use super::StateError; #[derive(Clone, Debug)] struct RevertedLayers { @@ -18,9 +19,9 @@ struct RevertedLayers { pub stack: Vec, } -/// A database consisting of layers. +/// A state consisting of layers. #[derive(Clone, Debug)] -pub struct LayeredDatabase { +pub struct LayeredState { stack: Vec, /// The old parent layer state root and the reverted layers reverted_layers: Option>, @@ -28,8 +29,8 @@ pub struct LayeredDatabase { snapshots: HashMap>, // naive implementation } -impl LayeredDatabase { - /// Creates a [`LayeredDatabase`] with the provided layer at the bottom. +impl LayeredState { + /// Creates a [`LayeredState`] with the provided layer at the bottom. pub fn with_layer(layer: Layer) -> Self { Self { stack: vec![layer], @@ -45,7 +46,7 @@ impl LayeredDatabase { /// Returns a mutable reference to the top layer. pub fn last_layer_mut(&mut self) -> &mut Layer { - // The `LayeredDatabase` always has at least one layer + // The `LayeredState` always has at least one layer self.stack.last_mut().unwrap() } @@ -70,7 +71,7 @@ impl LayeredDatabase { } } -impl LayeredDatabase { +impl LayeredState { /// Adds a default layer to the top, returning its index and a /// mutable reference to the layer. pub fn add_layer_default(&mut self) -> (usize, &mut Layer) { @@ -78,7 +79,7 @@ impl LayeredDatabase { } } -impl Default for LayeredDatabase { +impl Default for LayeredState { fn default() -> Self { Self { stack: vec![Layer::default()], @@ -137,7 +138,7 @@ impl RethnetLayer { } } -impl LayeredDatabase { +impl LayeredState { /// Retrieves a reference to the account corresponding to the address, if it exists. pub fn account(&self, address: &Address) -> Option<&AccountInfo> { self.iter() @@ -237,10 +238,10 @@ impl LayeredDatabase { } } -impl Database for LayeredDatabase { - type Error = anyhow::Error; +impl State for LayeredState { + type Error = StateError; - fn basic(&mut self, address: Address) -> anyhow::Result> { + fn basic(&mut self, address: Address) -> Result, Self::Error> { let account = self .iter() .find_map(|layer| layer.account_infos.get(&address)) @@ -249,7 +250,7 @@ impl Database for LayeredDatabase { log::debug!("account with address `{}`: {:?}", address, account); - // TODO: Move this out of LayeredDatabase when forking + // TODO: Move this out of LayeredState when forking Ok(account.or(Some(AccountInfo { balance: U256::ZERO, nonce: 0, @@ -258,22 +259,17 @@ impl Database for LayeredDatabase { }))) } - fn code_by_hash(&mut self, code_hash: B256) -> anyhow::Result { + fn code_by_hash(&mut self, code_hash: B256) -> Result { if code_hash == KECCAK_EMPTY { return Ok(Bytecode::new()); } self.iter() .find_map(|layer| layer.contracts.get(&code_hash).cloned()) - .ok_or_else(|| { - anyhow!( - "Layered database does not contain contract with code hash: {}.", - code_hash, - ) - }) + .ok_or(StateError::InvalidCodeHash(code_hash)) } - fn storage(&mut self, address: Address, index: U256) -> anyhow::Result { + fn storage(&mut self, address: Address, index: U256) -> Result { Ok(self .iter() .find_map(|layer| layer.storage.get(&address).map(|storage| storage.as_ref())) @@ -284,7 +280,7 @@ impl Database for LayeredDatabase { } } -impl DatabaseCommit for LayeredDatabase { +impl DatabaseCommit for LayeredState { fn commit(&mut self, changes: HashMap) { changes.into_iter().for_each(|(address, account)| { if account.is_empty() || account.is_destroyed { @@ -334,8 +330,8 @@ impl DatabaseCommit for LayeredDatabase { } } -impl DatabaseDebug for LayeredDatabase { - type Error = anyhow::Error; +impl StateDebug for LayeredState { + type Error = StateError; fn account_storage_root(&mut self, address: &Address) -> Result, Self::Error> { Ok(self @@ -372,7 +368,7 @@ impl DatabaseDebug for LayeredDatabase { address: Address, modifier: Box) + Send>, ) -> Result<(), Self::Error> { - // TODO: Move account insertion out of LayeredDatabase when forking + // TODO: Move account insertion out of LayeredState when forking let account_info = self.account_or_insert_mut(&address); let old_code_hash = account_info.code_hash; @@ -522,7 +518,7 @@ impl DatabaseDebug for LayeredDatabase { Ok(()) } else { - Err(anyhow!("Unknown state root: {}", state_root)) + Err(StateError::InvalidStateRoot(*state_root)) } } @@ -585,7 +581,7 @@ impl DatabaseDebug for LayeredDatabase { self.revert_to_layer(last_layer_id - 1); Ok(()) } else { - Err(anyhow!("No checkpoints to revert.")) + Err(StateError::CannotRevert) } } } diff --git a/crates/rethnet_evm/src/db/request.rs b/crates/rethnet_evm/src/db/request.rs index 36c949c39e..3cfa266657 100644 --- a/crates/rethnet_evm/src/db/request.rs +++ b/crates/rethnet_evm/src/db/request.rs @@ -2,10 +2,10 @@ use std::fmt::Debug; use hashbrown::HashMap; use rethnet_eth::{Address, B256, U256}; -use revm::{Account, AccountInfo, Bytecode, Database, DatabaseCommit}; +use revm::{db::State, Account, AccountInfo, Bytecode, DatabaseCommit}; use tokio::sync::oneshot; -use crate::{debug::ModifierFn, DatabaseDebug}; +use crate::{debug::ModifierFn, StateDebug}; /// The request type used internally by a [`SyncDatabase`]. pub enum Request @@ -80,23 +80,23 @@ impl Request where E: Debug, { - pub fn handle(self, db: &mut D) -> bool + pub fn handle(self, state: &mut S) -> bool where - D: Database + DatabaseCommit + DatabaseDebug, + S: State + DatabaseCommit + StateDebug, { match self { Request::AccountByAddress { address, sender } => { - sender.send(db.basic(address)).unwrap() + sender.send(state.basic(address)).unwrap() } Request::AccountStorageRoot { address, sender } => { - sender.send(db.account_storage_root(&address)).unwrap() + sender.send(state.account_storage_root(&address)).unwrap() } - Request::Checkpoint { sender } => sender.send(db.checkpoint()).unwrap(), + Request::Checkpoint { sender } => sender.send(state.checkpoint()).unwrap(), Request::CodeByHash { code_hash, sender } => { - sender.send(db.code_by_hash(code_hash)).unwrap() + sender.send(state.code_by_hash(code_hash)).unwrap() } Request::Commit { changes, sender } => { - db.commit(changes); + state.commit(changes); sender.send(()).unwrap() } Request::InsertAccount { @@ -104,38 +104,40 @@ where account_info, sender, } => sender - .send(db.insert_account(address, account_info)) + .send(state.insert_account(address, account_info)) .unwrap(), - Request::MakeSnapshot { sender } => sender.send(db.make_snapshot()).unwrap(), + Request::MakeSnapshot { sender } => sender.send(state.make_snapshot()).unwrap(), Request::ModifyAccount { address, modifier, sender, - } => sender.send(db.modify_account(address, modifier)).unwrap(), + } => sender + .send(state.modify_account(address, modifier)) + .unwrap(), Request::RemoveAccount { address, sender } => { - sender.send(db.remove_account(address)).unwrap() + sender.send(state.remove_account(address)).unwrap() } Request::RemoveSnapshot { state_root, sender } => { - sender.send(db.remove_snapshot(&state_root)).unwrap() + sender.send(state.remove_snapshot(&state_root)).unwrap() } - Request::Revert { sender } => sender.send(db.revert()).unwrap(), + Request::Revert { sender } => sender.send(state.revert()).unwrap(), Request::SetStorageSlot { address, index, value, sender, } => sender - .send(db.set_account_storage_slot(address, index, value)) + .send(state.set_account_storage_slot(address, index, value)) .unwrap(), Request::SetStateRoot { state_root, sender } => { - sender.send(db.set_state_root(&state_root)).unwrap() + sender.send(state.set_state_root(&state_root)).unwrap() } - Request::StateRoot { sender } => sender.send(db.state_root()).unwrap(), + Request::StateRoot { sender } => sender.send(state.state_root()).unwrap(), Request::StorageSlot { address, index, sender, - } => sender.send(db.storage(address, index)).unwrap(), + } => sender.send(state.storage(address, index)).unwrap(), Request::Terminate => return false, } diff --git a/crates/rethnet_evm/src/db/sync.rs b/crates/rethnet_evm/src/db/sync.rs index de6a21b0d8..5725317c66 100644 --- a/crates/rethnet_evm/src/db/sync.rs +++ b/crates/rethnet_evm/src/db/sync.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, io}; use hashbrown::HashMap; use rethnet_eth::{Address, B256, U256}; -use revm::{db::Database, Account, AccountInfo, Bytecode, DatabaseCommit}; +use revm::{db::State, Account, AccountInfo, Bytecode, DatabaseCommit}; use tokio::{ runtime::{Builder, Runtime}, sync::{ @@ -12,21 +12,21 @@ use tokio::{ task::{self, JoinHandle}, }; -use crate::{debug::ModifierFn, DatabaseDebug}; +use crate::{debug::ModifierFn, StateDebug}; use super::request::Request; /// Trait that meets all requirements for a synchronous database that can be used by [`AsyncDatabase`]. -pub trait SyncDatabase: - Database + DatabaseCommit + DatabaseDebug + Send + Sync + 'static +pub trait SyncState: + State + DatabaseCommit + StateDebug + Send + Sync + 'static where E: Debug + Send, { } -impl SyncDatabase for D +impl SyncState for S where - D: Database + DatabaseCommit + DatabaseDebug + Send + Sync + 'static, + S: State + DatabaseCommit + StateDebug + Send + Sync + 'static, E: Debug + Send, { } @@ -34,7 +34,7 @@ where /// A helper class for converting a synchronous database into an asynchronous database. /// /// Requires the inner database to implement [`Database`], [`DatabaseCommit`], and [`DatabaseDebug`]. -pub struct AsyncDatabase +pub struct AsyncState where E: Debug + Send, { @@ -43,19 +43,19 @@ where db_handle: Option>, } -impl AsyncDatabase +impl AsyncState where E: Debug + Send + 'static, { /// Constructs an [`AsyncDatabase`] instance with the provided database. - pub fn new>(mut db: D) -> io::Result { + pub fn new>(mut state: S) -> io::Result { let runtime = Builder::new_multi_thread().build()?; let (sender, mut receiver) = unbounded_channel::>(); let db_handle = runtime.spawn(async move { while let Some(request) = receiver.recv().await { - if !request.handle(&mut db) { + if !request.handle(&mut state) { break; } } @@ -271,7 +271,7 @@ where } } -impl Drop for AsyncDatabase +impl Drop for AsyncState where E: Debug + Send, { @@ -286,7 +286,7 @@ where } } -impl<'d, E> Database for &'d AsyncDatabase +impl<'d, E> State for &'d AsyncState where E: Debug + Send + 'static, { @@ -295,26 +295,26 @@ where fn basic(&mut self, address: Address) -> Result, Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::account_by_address(*self, address)) + .block_on(AsyncState::account_by_address(*self, address)) }) } fn code_by_hash(&mut self, code_hash: B256) -> Result { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::code_by_hash(*self, code_hash)) + .block_on(AsyncState::code_by_hash(*self, code_hash)) }) } fn storage(&mut self, address: Address, index: U256) -> Result { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::account_storage_slot(*self, address, index)) + .block_on(AsyncState::account_storage_slot(*self, address, index)) }) } } -impl<'d, E> DatabaseCommit for &'d AsyncDatabase +impl<'d, E> DatabaseCommit for &'d AsyncState where E: Debug + Send + 'static, { @@ -323,7 +323,7 @@ where } } -impl<'d, E> DatabaseDebug for &'d AsyncDatabase +impl<'d, E> StateDebug for &'d AsyncState where E: Debug + Send + 'static, { @@ -332,7 +332,7 @@ where fn account_storage_root(&mut self, address: &Address) -> Result, Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::account_storage_root(*self, address)) + .block_on(AsyncState::account_storage_root(*self, address)) }) } @@ -343,7 +343,7 @@ where ) -> Result<(), Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::insert_account(*self, address, account_info)) + .block_on(AsyncState::insert_account(*self, address, account_info)) }) } @@ -354,14 +354,14 @@ where ) -> Result<(), Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::modify_account(*self, address, modifier)) + .block_on(AsyncState::modify_account(*self, address, modifier)) }) } fn remove_account(&mut self, address: Address) -> Result, Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::remove_account(*self, address)) + .block_on(AsyncState::remove_account(*self, address)) }) } @@ -372,40 +372,39 @@ where value: U256, ) -> Result<(), Self::Error> { task::block_in_place(move || { - self.runtime - .block_on(AsyncDatabase::set_account_storage_slot( - *self, address, index, value, - )) + self.runtime.block_on(AsyncState::set_account_storage_slot( + *self, address, index, value, + )) }) } fn set_state_root(&mut self, state_root: &B256) -> Result<(), Self::Error> { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::set_state_root(*self, state_root)) + .block_on(AsyncState::set_state_root(*self, state_root)) }) } fn state_root(&mut self) -> Result { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::state_root(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::state_root(*self))) } fn checkpoint(&mut self) -> Result<(), Self::Error> { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::checkpoint(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::checkpoint(*self))) } fn revert(&mut self) -> Result<(), Self::Error> { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::revert(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::revert(*self))) } fn make_snapshot(&mut self) -> B256 { - task::block_in_place(move || self.runtime.block_on(AsyncDatabase::make_snapshot(*self))) + task::block_in_place(move || self.runtime.block_on(AsyncState::make_snapshot(*self))) } fn remove_snapshot(&mut self, state_root: &B256) -> bool { task::block_in_place(move || { self.runtime - .block_on(AsyncDatabase::remove_snapshot(*self, *state_root)) + .block_on(AsyncState::remove_snapshot(*self, *state_root)) }) } } diff --git a/crates/rethnet_evm/src/debug.rs b/crates/rethnet_evm/src/debug.rs index be867b1999..3a705a3b25 100644 --- a/crates/rethnet_evm/src/debug.rs +++ b/crates/rethnet_evm/src/debug.rs @@ -6,7 +6,7 @@ pub type ModifierFn = Box) + S /// A trait for debug operation on a database. #[auto_impl(Box)] -pub trait DatabaseDebug { +pub trait StateDebug { /// The database's error type. type Error; diff --git a/crates/rethnet_evm/src/evm.rs b/crates/rethnet_evm/src/evm.rs index 3639cafc78..8dd2c02112 100644 --- a/crates/rethnet_evm/src/evm.rs +++ b/crates/rethnet_evm/src/evm.rs @@ -1,27 +1,58 @@ -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; -use revm::{BlockEnv, CfgEnv, TxEnv}; +use revm::{ + db::{DatabaseComponentError, DatabaseComponents}, + BlockEnv, CfgEnv, EVMError, ExecutionResult, ResultAndState, State, TxEnv, +}; +use tokio::{runtime::Runtime, task::JoinHandle}; -use crate::{blockchain::AsyncBlockchain, db::AsyncDatabase}; +use crate::{ + blockchain::AsyncBlockchain, db::AsyncState, inspector::RethnetInspector, trace::Trace, +}; /// Creates an evm from the provided database, config, transaction, and block. #[allow(clippy::type_complexity)] -pub fn build_evm<'b, 'd, E>( - blockchain: &'b AsyncBlockchain, - db: &'d AsyncDatabase, +fn build_evm<'b, 'd, BE, SE>( + blockchain: &'b AsyncBlockchain, + state: &'d AsyncState, cfg: CfgEnv, transaction: TxEnv, block: BlockEnv, -) -> revm::EVM<&'d AsyncDatabase, &'b AsyncBlockchain> +) -> revm::EVM, &'b AsyncBlockchain>> where - E: Debug + Send + 'static, + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, { let mut evm = revm::EVM::new(); - evm.set_blockchain(blockchain); - evm.database(db); + evm.database(DatabaseComponents { + state, + block_hash: blockchain, + }); evm.env.cfg = cfg; evm.env.block = block; evm.env.tx = transaction; evm } + +#[allow(clippy::type_complexity)] +pub fn run_transaction( + runtime: &Runtime, + blockchain: Arc>, + state: Arc>, + cfg: CfgEnv, + transaction: TxEnv, + block: BlockEnv, +) -> JoinHandle>>> +where + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, +{ + runtime.spawn(async move { + let mut evm = build_evm(&blockchain, &state, cfg, transaction, block); + + let mut inspector = RethnetInspector::default(); + let ResultAndState { result, state } = evm.inspect(&mut inspector)?; + Ok((result, state, inspector.into_trace())) + }) +} diff --git a/crates/rethnet_evm/src/inspector.rs b/crates/rethnet_evm/src/inspector.rs index cb3491a354..2d21768612 100644 --- a/crates/rethnet_evm/src/inspector.rs +++ b/crates/rethnet_evm/src/inspector.rs @@ -1,4 +1,4 @@ -use revm::{blockchain::Blockchain, opcode, Database, EVMData, Inspector, Interpreter, Return}; +use revm::{opcode, Database, EVMData, Inspector, InstructionResult, Interpreter}; use crate::trace::Trace; @@ -15,29 +15,28 @@ impl RethnetInspector { } } -impl Inspector for RethnetInspector +impl Inspector for RethnetInspector where D: Database, - BC: Blockchain, { fn step( &mut self, interp: &mut Interpreter, - _data: &mut EVMData<'_, D, BC>, + _data: &mut EVMData<'_, D>, _is_static: bool, - ) -> Return { + ) -> InstructionResult { self.opcode_stack.push(interp.current_opcode()); - Return::Continue + InstructionResult::Continue } fn step_end( &mut self, interp: &mut Interpreter, - _data: &mut EVMData<'_, D, BC>, + _data: &mut EVMData<'_, D>, _is_static: bool, - exit_code: Return, - ) -> Return { + exit_code: InstructionResult, + ) -> InstructionResult { let opcode = self .opcode_stack .pop() @@ -45,10 +44,10 @@ where self.trace.add_step(opcode, interp.gas(), exit_code); - if opcode == opcode::RETURN { + if opcode == opcode::RETURN || opcode == opcode::REVERT { self.trace.return_value = interp.return_value(); } - Return::Continue + InstructionResult::Continue } } diff --git a/crates/rethnet_evm/src/lib.rs b/crates/rethnet_evm/src/lib.rs index b72dd4a1ed..c42ad57813 100644 --- a/crates/rethnet_evm/src/lib.rs +++ b/crates/rethnet_evm/src/lib.rs @@ -4,26 +4,21 @@ //! Virtual Machine (or EVM). #![warn(missing_docs)] -use rethnet_eth::Address; - pub use hashbrown::HashMap; pub use revm::{ - blockchain::{Blockchain, BlockchainRef}, db::EmptyDB, - Account, AccountInfo, BlockEnv, Bytecode, CfgEnv, CreateScheme, Database, DatabaseCommit, - ExecutionResult, Log, Return, SpecId, TransactOut, TransactTo, TxEnv, EVM, + db::{BlockHash, BlockHashRef}, + Account, AccountInfo, BlockEnv, Bytecode, CfgEnv, CreateScheme, Database, DatabaseCommit, Eval, + ExecutionResult, Halt, Log, Output, ResultAndState, SpecId, State, TransactTo, TxEnv, EVM, }; pub use crate::{ block::{BlockBuilder, HeaderData}, - debug::DatabaseDebug, + debug::StateDebug, runtime::Rethnet, transaction::PendingTransaction, }; -/// State mapping of addresses to accounts. -pub type State = HashMap; - /// Types for managing Ethereum blockchain pub mod blockchain; diff --git a/crates/rethnet_evm/src/random.rs b/crates/rethnet_evm/src/random.rs index d0ea9e2941..f63290a330 100644 --- a/crates/rethnet_evm/src/random.rs +++ b/crates/rethnet_evm/src/random.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use rethnet_eth::B256; -use revm::common::keccak256; +use revm::primitives::keccak256; /// A pseudorandom hash generator which allows overriding of the next generated hash. #[derive(Debug)] diff --git a/crates/rethnet_evm/src/runtime.rs b/crates/rethnet_evm/src/runtime.rs index 49a04858cf..8202c5c8c1 100644 --- a/crates/rethnet_evm/src/runtime.rs +++ b/crates/rethnet_evm/src/runtime.rs @@ -3,33 +3,31 @@ use std::{fmt::Debug, sync::Arc}; use revm::{BlockEnv, CfgEnv, ExecutionResult, SpecId, TxEnv}; use crate::{ - blockchain::AsyncBlockchain, db::AsyncDatabase, evm::build_evm, inspector::RethnetInspector, - trace::Trace, transaction::TransactionError, State, + blockchain::AsyncBlockchain, db::AsyncState, evm::run_transaction, trace::Trace, + transaction::TransactionError, State, }; /// The asynchronous Rethnet runtime. -pub struct Rethnet +pub struct Rethnet where - E: Debug + Send + 'static, + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, { - blockchain: Arc>, - db: Arc>, + blockchain: Arc>, + state: Arc>, cfg: CfgEnv, } -impl Rethnet +impl Rethnet where - E: Debug + Send + 'static, + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, { /// Constructs a new [`Rethnet`] instance. - pub fn new( - blockchain: Arc>, - db: Arc>, - cfg: CfgEnv, - ) -> Self { + pub fn new(blockchain: Arc>, db: Arc>, cfg: CfgEnv) -> Self { Self { blockchain, - db, + state: db, cfg, } } @@ -39,27 +37,22 @@ where &self, transaction: TxEnv, block: BlockEnv, - ) -> Result<(ExecutionResult, State, Trace), TransactionError> { + ) -> Result<(ExecutionResult, State, Trace), TransactionError> { if self.cfg.spec_id > SpecId::MERGE && block.prevrandao.is_none() { return Err(TransactionError::MissingPrevrandao); } - let blockchain = self.blockchain.clone(); - let db = self.db.clone(); - let cfg = self.cfg.clone(); - - Ok(self - .db - .runtime() - .spawn(async move { - let mut evm = build_evm(&blockchain, &db, cfg, transaction, block); - - let mut inspector = RethnetInspector::default(); - let (result, state) = evm.inspect(&mut inspector); - (result, state, inspector.into_trace()) - }) - .await - .unwrap()) + run_transaction( + self.state.runtime(), + self.blockchain.clone(), + self.state.clone(), + self.cfg.clone(), + transaction, + block, + ) + .await + .unwrap() + .map_err(TransactionError::from) } /// Runs a transaction without committing the state, while disabling balance checks and creating accounts for new addresses. @@ -67,29 +60,25 @@ where &self, transaction: TxEnv, block: BlockEnv, - ) -> Result<(ExecutionResult, State, Trace), TransactionError> { + ) -> Result<(ExecutionResult, State, Trace), TransactionError> { if self.cfg.spec_id > SpecId::MERGE && block.prevrandao.is_none() { return Err(TransactionError::MissingPrevrandao); } - let blockchain = self.blockchain.clone(); - let db = self.db.clone(); - let mut cfg = self.cfg.clone(); cfg.disable_balance_check = true; - Ok(self - .db - .runtime() - .spawn(async move { - let mut evm = build_evm(&blockchain, &db, cfg, transaction, block); - - let mut inspector = RethnetInspector::default(); - let (result, state) = evm.inspect(&mut inspector); - (result, state, inspector.into_trace()) - }) - .await - .unwrap()) + run_transaction( + self.state.runtime(), + self.blockchain.clone(), + self.state.clone(), + cfg, + transaction, + block, + ) + .await + .unwrap() + .map_err(TransactionError::from) } /// Runs a transaction, committing the state in the process. @@ -97,10 +86,10 @@ where &self, transaction: TxEnv, block: BlockEnv, - ) -> Result<(ExecutionResult, Trace), TransactionError> { + ) -> Result<(ExecutionResult, Trace), TransactionError> { let (result, changes, trace) = self.dry_run(transaction, block).await?; - self.db.apply(changes).await; + self.state.apply(changes).await; Ok((result, trace)) } diff --git a/crates/rethnet_evm/src/trace.rs b/crates/rethnet_evm/src/trace.rs index 1f62e3033a..5624a5970b 100644 --- a/crates/rethnet_evm/src/trace.rs +++ b/crates/rethnet_evm/src/trace.rs @@ -1,5 +1,5 @@ use rethnet_eth::Bytes; -use revm::{Gas, Return}; +use revm::{Gas, InstructionResult}; /// A trace for an EVM call. #[derive(Default)] @@ -20,12 +20,12 @@ pub struct Step { /// The amount of gas that was refunded by the step pub gas_refunded: i64, /// The exit code of the step - pub exit_code: Return, + pub exit_code: InstructionResult, } impl Trace { /// Adds a VM step to the trace. - pub fn add_step(&mut self, opcode: u8, gas: &Gas, exit_code: Return) { + pub fn add_step(&mut self, opcode: u8, gas: &Gas, exit_code: InstructionResult) { let step = if let Some(old_gas) = self.gas.replace(*gas) { Step { opcode, diff --git a/crates/rethnet_evm/src/transaction.rs b/crates/rethnet_evm/src/transaction.rs index ff9a047596..8e6ad2109f 100644 --- a/crates/rethnet_evm/src/transaction.rs +++ b/crates/rethnet_evm/src/transaction.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use rethnet_eth::{ receipt::Log, signature::SignatureError, @@ -7,13 +9,35 @@ use rethnet_eth::{ }, Address, Bloom, Bytes, B256, U256, }; +use revm::{db::DatabaseComponentError, EVMError, InvalidTransaction}; /// Invalid transaction error #[derive(Debug, thiserror::Error)] -pub enum TransactionError { +pub enum TransactionError { + #[error(transparent)] + BlockHash(BE), + #[error("Invalid transaction")] + InvalidTransaction(InvalidTransaction), /// The transaction is expected to have a prevrandao, as the executor's config is on a post-merge hardfork. #[error("Post-merge transaction is missing prevrandao")] MissingPrevrandao, + #[error(transparent)] + State(SE), +} + +impl From>> for TransactionError +where + BE: Debug + Send + 'static, + SE: Debug + Send + 'static, +{ + fn from(error: EVMError>) -> Self { + match error { + EVMError::Transaction(e) => Self::InvalidTransaction(e), + EVMError::PrevrandaoNotSet => unreachable!(), + EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), + EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::BlockHash(e), + } + } } /// Represents all relevant information of an executed transaction @@ -27,7 +51,7 @@ pub struct TransactionInfo { pub logs: Vec, pub logs_bloom: Bloom, // pub traces: todo!(), - pub exit: revm::Return, + pub exit: revm::InstructionResult, pub out: Option, } diff --git a/crates/rethnet_evm_napi/Cargo.toml b/crates/rethnet_evm_napi/Cargo.toml index 997b6da8f7..87c3decf4b 100644 --- a/crates/rethnet_evm_napi/Cargo.toml +++ b/crates/rethnet_evm_napi/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -anyhow = "1.0.64" crossbeam-channel = { version = "0.5.6", default-features = false } napi = { version = "= 2.10.2", default-features = false, features = ["async", "error_anyhow", "napi8", "serde-json"] } napi-derive = "2.9.3" diff --git a/crates/rethnet_evm_napi/src/block/builder.rs b/crates/rethnet_evm_napi/src/block/builder.rs index 4e76958865..99d7d6e211 100644 --- a/crates/rethnet_evm_napi/src/block/builder.rs +++ b/crates/rethnet_evm_napi/src/block/builder.rs @@ -7,6 +7,7 @@ use napi::{ }; use napi_derive::napi; use rethnet_eth::{Address, U256}; +use rethnet_evm::db::StateError; use crate::{ blockchain::Blockchain, cast::TryCast, state::StateManager, transaction::Transaction, Config, @@ -17,13 +18,13 @@ use super::{BlockConfig, BlockHeader}; #[napi] pub struct BlockBuilder { - builder: Arc>>>, + builder: Arc>>>, } #[napi] impl BlockBuilder { #[napi] - pub async fn new( + pub fn new( blockchain: &Blockchain, state_manager: &StateManager, config: Config, @@ -36,13 +37,11 @@ impl BlockBuilder { let builder = rethnet_evm::BlockBuilder::new( blockchain.as_inner().clone(), - state_manager.db.clone(), + state_manager.state.clone(), config, parent, block, - ) - .await - .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?; + ); Ok(Self { builder: Arc::new(Mutex::new(Some(builder))), diff --git a/crates/rethnet_evm_napi/src/blockchain.rs b/crates/rethnet_evm_napi/src/blockchain.rs index 7ad9ce415c..3df4ee10fb 100644 --- a/crates/rethnet_evm_napi/src/blockchain.rs +++ b/crates/rethnet_evm_napi/src/blockchain.rs @@ -16,11 +16,11 @@ use self::js_blockchain::{GetBlockHashCall, JsBlockchain}; #[napi] pub struct Blockchain { - inner: Arc>, + inner: Arc>, } impl Blockchain { - pub fn as_inner(&self) -> &Arc> { + pub fn as_inner(&self) -> &Arc> { &self.inner } } @@ -56,9 +56,9 @@ impl Blockchain { fn with_blockchain(blockchain: B) -> napi::Result where - B: SyncBlockchain, + B: SyncBlockchain, { - let blockchain: Box> = Box::new(blockchain); + let blockchain: Box> = Box::new(blockchain); let blockchain = AsyncBlockchain::new(blockchain) .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?; diff --git a/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs b/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs index 74d33af5e1..44837a8abe 100644 --- a/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs +++ b/crates/rethnet_evm_napi/src/blockchain/js_blockchain.rs @@ -1,9 +1,8 @@ use std::sync::mpsc::{channel, Sender}; -use anyhow::anyhow; use napi::Status; use rethnet_eth::{B256, U256}; -use rethnet_evm::Blockchain; +use rethnet_evm::BlockHash; use crate::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; @@ -16,8 +15,8 @@ pub struct JsBlockchain { pub(super) get_block_hash_fn: ThreadsafeFunction, } -impl Blockchain for JsBlockchain { - type Error = anyhow::Error; +impl BlockHash for JsBlockchain { + type Error = napi::Error; fn block_hash(&mut self, block_number: U256) -> Result { let (sender, receiver) = channel(); @@ -31,6 +30,6 @@ impl Blockchain for JsBlockchain { ); assert_eq!(status, Status::Ok); - receiver.recv().unwrap().map_err(|e| anyhow!(e.to_string())) + receiver.recv().unwrap() } } diff --git a/crates/rethnet_evm_napi/src/lib.rs b/crates/rethnet_evm_napi/src/lib.rs index 69159a5947..0d4a23bf4d 100644 --- a/crates/rethnet_evm_napi/src/lib.rs +++ b/crates/rethnet_evm_napi/src/lib.rs @@ -14,18 +14,18 @@ use std::{fmt::Debug, str::FromStr}; use block::BlockConfig; use blockchain::Blockchain; use napi::{ - bindgen_prelude::{BigInt, Buffer, ToNapiValue}, - Status, + bindgen_prelude::{BigInt, Buffer, Either3, ToNapiValue}, + Either, Status, }; use napi_derive::napi; use once_cell::sync::OnceCell; use rethnet_eth::Address; -use rethnet_evm::{AccountInfo, CfgEnv, TxEnv}; +use rethnet_evm::{db::StateError, AccountInfo, CfgEnv, TxEnv}; use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly}; use sha3::{Digest, Keccak256}; use state::StateManager; use trace::Trace; -use transaction::{Transaction, TransactionOutput}; +use transaction::{CallOutput, CreateOutput, Transaction}; use crate::cast::TryCast; @@ -245,26 +245,149 @@ impl From for Log { } } +/// The possible reasons for a `ExitStatus::Success`. +#[napi] +pub enum SuccessReason { + Stop, + Return, + SelfDestruct, +} + +impl From for SuccessReason { + fn from(eval: rethnet_evm::Eval) -> Self { + match eval { + rethnet_evm::Eval::Stop => Self::Stop, + rethnet_evm::Eval::Return => Self::Return, + rethnet_evm::Eval::SelfDestruct => Self::SelfDestruct, + } + } +} + #[napi(object)] -pub struct ExecutionResult { - pub exit_code: u8, - pub output: TransactionOutput, +pub struct SuccessResult { + pub reason: SuccessReason, pub gas_used: BigInt, pub gas_refunded: BigInt, pub logs: Vec, + pub output: Either, +} + +#[napi(object)] +pub struct RevertResult { + pub gas_used: BigInt, + pub logs: Vec, + pub return_value: Buffer, +} + +/// Indicates that the EVM has experienced an exceptional halt. This causes execution to +/// immediately end with all gas being consumed. +#[napi] +pub enum ExceptionalHalt { + OutOfGas, + OpcodeNotFound, + InvalidFEOpcode, + InvalidJump, + NotActivated, + StackUnderflow, + StackOverflow, + OutOfOffset, + CreateCollision, + OverflowPayment, + PrecompileError, + NonceOverflow, + /// Create init code size exceeds limit (runtime). + CreateContractSizeLimit, + /// Error on created contract that begins with EF + CreateContractStartingWithEF, +} + +impl From for ExceptionalHalt { + fn from(halt: rethnet_evm::Halt) -> Self { + match halt { + rethnet_evm::Halt::OutOfGas => ExceptionalHalt::OutOfGas, + rethnet_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, + rethnet_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode, + rethnet_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump, + rethnet_evm::Halt::NotActivated => ExceptionalHalt::NotActivated, + rethnet_evm::Halt::StackUnderflow => ExceptionalHalt::StackUnderflow, + rethnet_evm::Halt::StackOverflow => ExceptionalHalt::StackOverflow, + rethnet_evm::Halt::OutOfOffset => ExceptionalHalt::OutOfOffset, + rethnet_evm::Halt::CreateCollision => ExceptionalHalt::CreateCollision, + rethnet_evm::Halt::OverflowPayment => ExceptionalHalt::OverflowPayment, + rethnet_evm::Halt::PrecompileError => ExceptionalHalt::PrecompileError, + rethnet_evm::Halt::NonceOverflow => ExceptionalHalt::NonceOverflow, + rethnet_evm::Halt::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit, + rethnet_evm::Halt::CreateContractStartingWithEF => { + ExceptionalHalt::CreateContractStartingWithEF + } + } + } +} + +#[napi(object)] +pub struct HaltResult { + pub reason: ExceptionalHalt, + /// Halting will spend all the gas and will thus be equal to gas_limit. + pub gas_used: BigInt, +} + +#[napi(object)] +pub struct ExecutionResult { + pub result: Either3, pub trace: Trace, } impl From<(rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)> for ExecutionResult { fn from((result, trace): (rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)) -> Self { - let logs = result.logs.into_iter().map(Log::from).collect(); + let result = match result { + rethnet_evm::ExecutionResult::Success { + reason, + gas_used, + gas_refunded, + logs, + output, + } => { + let logs = logs.into_iter().map(Log::from).collect(); + + Either3::A(SuccessResult { + reason: reason.into(), + gas_used: BigInt::from(gas_used), + gas_refunded: BigInt::from(gas_refunded), + logs, + output: match output { + rethnet_evm::Output::Call(return_value) => Either::A(CallOutput { + return_value: Buffer::from(return_value.as_ref()), + }), + rethnet_evm::Output::Create(return_value, address) => { + Either::B(CreateOutput { + return_value: Buffer::from(return_value.as_ref()), + address: address.map(|address| Buffer::from(address.as_bytes())), + }) + } + }, + }) + } + rethnet_evm::ExecutionResult::Revert { + gas_used, + logs, + return_value, + } => { + let logs = logs.into_iter().map(Log::from).collect(); + + Either3::B(RevertResult { + gas_used: BigInt::from(gas_used), + logs, + return_value: Buffer::from(return_value.as_ref()), + }) + } + rethnet_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult { + reason: reason.into(), + gas_used: BigInt::from(gas_used), + }), + }; Self { - exit_code: result.exit_reason as u8, - output: result.out.into(), - gas_used: BigInt::from(result.gas_used), - gas_refunded: BigInt::from(result.gas_refunded), - logs, + result, trace: trace.into(), } } @@ -339,7 +462,7 @@ pub struct TracingMessageResult { #[napi] pub struct Rethnet { - runtime: rethnet_evm::Rethnet, + runtime: rethnet_evm::Rethnet, } #[napi] @@ -357,8 +480,11 @@ impl Rethnet { let cfg = cfg.try_into()?; - let runtime = - rethnet_evm::Rethnet::new(blockchain.as_inner().clone(), state_manager.db.clone(), cfg); + let runtime = rethnet_evm::Rethnet::new( + blockchain.as_inner().clone(), + state_manager.state.clone(), + cfg, + ); Ok(Self { runtime }) } diff --git a/crates/rethnet_evm_napi/src/state.rs b/crates/rethnet_evm_napi/src/state.rs index dc0df360ef..9396f97d56 100644 --- a/crates/rethnet_evm_napi/src/state.rs +++ b/crates/rethnet_evm_napi/src/state.rs @@ -7,8 +7,8 @@ use napi::{bindgen_prelude::*, JsFunction, JsObject, NapiRaw, Status}; use napi_derive::napi; use rethnet_eth::{Address, B256, U256}; use rethnet_evm::{ - db::{AsyncDatabase, LayeredDatabase, RethnetLayer, SyncDatabase}, - AccountInfo, Bytecode, DatabaseDebug, HashMap, + db::{AsyncState, LayeredState, RethnetLayer, StateError, SyncState}, + AccountInfo, Bytecode, HashMap, StateDebug, }; use secp256k1::Secp256k1; @@ -28,7 +28,7 @@ struct ModifyAccountCall { #[napi] pub struct StateManager { - pub(super) db: Arc>, + pub(super) state: Arc>, } #[napi] @@ -67,28 +67,29 @@ impl StateManager { accounts.insert(address, AccountInfo::default()); } - let mut database = - LayeredDatabase::with_layer(RethnetLayer::with_genesis_accounts(accounts)); + let mut state = LayeredState::with_layer(RethnetLayer::with_genesis_accounts(accounts)); - database.checkpoint().unwrap(); + state.checkpoint().unwrap(); - Self::with_db(database) + Self::with_state(state) } - fn with_db(db: D) -> napi::Result + fn with_state(state: S) -> napi::Result where - D: SyncDatabase, + S: SyncState, { - let db: Box> = Box::new(db); - let db = AsyncDatabase::new(db) + let state: Box> = Box::new(state); + let state = AsyncState::new(state) .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?; - Ok(Self { db: Arc::new(db) }) + Ok(Self { + state: Arc::new(state), + }) } #[napi] pub async fn checkpoint(&self) -> napi::Result<()> { - self.db + self.state .checkpoint() .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) @@ -96,7 +97,7 @@ impl StateManager { #[napi] pub async fn revert(&self) -> napi::Result<()> { - self.db + self.state .revert() .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) @@ -106,7 +107,7 @@ impl StateManager { pub async fn get_account_by_address(&self, address: Buffer) -> napi::Result> { let address = Address::from_slice(&address); - self.db.account_by_address(address).await.map_or_else( + self.state.account_by_address(address).await.map_or_else( |e| Err(napi::Error::new(Status::GenericFailure, e.to_string())), |account_info| Ok(account_info.map(Account::from)), ) @@ -117,7 +118,7 @@ impl StateManager { pub async fn get_account_storage_root(&self, address: Buffer) -> napi::Result> { let address = Address::from_slice(&address); - self.db.account_storage_root(&address).await.map_or_else( + self.state.account_storage_root(&address).await.map_or_else( |e| Err(napi::Error::new(Status::GenericFailure, e.to_string())), |root| Ok(root.map(|root| Buffer::from(root.as_ref()))), ) @@ -132,7 +133,7 @@ impl StateManager { let address = Address::from_slice(&address); let index = BigInt::try_cast(index)?; - self.db + self.state .account_storage_slot(address, index) .await .map_or_else( @@ -150,7 +151,7 @@ impl StateManager { pub async fn get_code_by_hash(&self, code_hash: Buffer) -> napi::Result { let code_hash = B256::from_slice(&code_hash); - self.db.code_by_hash(code_hash).await.map_or_else( + self.state.code_by_hash(code_hash).await.map_or_else( |e| Err(napi::Error::new(Status::GenericFailure, e.to_string())), |code| Ok(Buffer::from(&code.bytes()[..code.len()])), ) @@ -158,7 +159,7 @@ impl StateManager { #[napi] pub async fn get_state_root(&self) -> napi::Result { - self.db.state_root().await.map_or_else( + self.state.state_root().await.map_or_else( |e| Err(napi::Error::new(Status::GenericFailure, e.to_string())), |root| Ok(Buffer::from(root.as_ref())), ) @@ -169,7 +170,7 @@ impl StateManager { let address = Address::from_slice(&address); let account = account.try_cast()?; - self.db + self.state .insert_account(address, account) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) @@ -177,7 +178,7 @@ impl StateManager { #[napi] pub async fn make_snapshot(&self) -> Buffer { - >::as_ref(&self.db.make_snapshot().await).into() + >::as_ref(&self.state.make_snapshot().await).into() } /// Modifies the account with the provided address using the specified modifier function. @@ -232,9 +233,9 @@ impl StateManager { )?; let (deferred, promise) = env.create_deferred()?; - let db = self.db.clone(); + let db = self.state.clone(); - self.db.runtime().spawn(async move { + self.state.runtime().spawn(async move { let result = db .modify_account( address, @@ -275,7 +276,7 @@ impl StateManager { pub async fn remove_account(&self, address: Buffer) -> napi::Result> { let address = Address::from_slice(&address); - self.db.remove_account(address).await.map_or_else( + self.state.remove_account(address).await.map_or_else( |e| Err(napi::Error::new(Status::GenericFailure, e.to_string())), |account| Ok(account.map(Account::from)), ) @@ -285,7 +286,7 @@ impl StateManager { pub async fn remove_snapshot(&self, state_root: Buffer) -> bool { let state_root = B256::from_slice(&state_root); - self.db.remove_snapshot(state_root).await + self.state.remove_snapshot(state_root).await } #[napi] @@ -299,7 +300,7 @@ impl StateManager { let index = BigInt::try_cast(index)?; let value = BigInt::try_cast(value)?; - self.db + self.state .set_account_storage_slot(address, index, value) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) @@ -309,7 +310,7 @@ impl StateManager { pub async fn set_state_root(&self, state_root: Buffer) -> napi::Result<()> { let state_root = B256::from_slice(&state_root); - self.db + self.state .set_state_root(&state_root) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) diff --git a/crates/rethnet_evm_napi/src/transaction.rs b/crates/rethnet_evm_napi/src/transaction.rs index 2c08fcd950..671b8e847c 100644 --- a/crates/rethnet_evm_napi/src/transaction.rs +++ b/crates/rethnet_evm_napi/src/transaction.rs @@ -90,26 +90,15 @@ pub struct TransactionConfig { } #[napi(object)] -pub struct TransactionOutput { - /// Return value from Call or Create transactions - #[napi(readonly)] - pub output: Option, - /// Optionally, a 160-bit address from Create transactions - #[napi(readonly)] - pub address: Option, +pub struct CallOutput { + /// Return value + pub return_value: Buffer, } -impl From for TransactionOutput { - fn from(value: rethnet_evm::TransactOut) -> Self { - let (output, address) = match value { - rethnet_evm::TransactOut::None => (None, None), - rethnet_evm::TransactOut::Call(output) => (Some(Buffer::from(output.as_ref())), None), - rethnet_evm::TransactOut::Create(output, address) => ( - Some(Buffer::from(output.as_ref())), - address.map(|address| Buffer::from(address.as_bytes())), - ), - }; - - Self { output, address } - } +#[napi(object)] +pub struct CreateOutput { + /// Return value + pub return_value: Buffer, + /// Optionally, a 160-bit address + pub address: Option, } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts index 57ce82a883..09782ac180 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts @@ -27,10 +27,9 @@ export function rpcToTxData( chainId: rpcTransaction.chainId ?? undefined, maxFeePerGas: rpcTransaction.maxFeePerGas, maxPriorityFeePerGas: rpcTransaction.maxPriorityFeePerGas, - accessList: - rpcTransaction.accessList?.map((item) => [ - item.address, - item.storageKeys ?? [], - ]) ?? undefined, + accessList: rpcTransaction.accessList?.map((item) => [ + item.address, + item.storageKeys ?? [], + ]), }; } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts index 9d2b907e4f..96bbb86954 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts @@ -18,7 +18,13 @@ import { } from "rethnet-evm"; import { fromBigIntLike } from "../../../util/bigint"; import { HardforkName } from "../../../util/hardforks"; -import { Exit } from "../vm/exit"; +import { + isCreateOutput, + isHaltResult, + isRevertResult, + isSuccessResult, +} from "../../stack-traces/message-trace"; +import { Exit, ExitCode } from "../vm/exit"; import { RunTxResult } from "../vm/vm-adapter"; import { Bloom } from "./bloom"; @@ -154,30 +160,45 @@ export function rethnetResultToRunTxResult( rethnetResult: ExecutionResult, blockGasUsed: bigint ): RunTxResult { - const vmError = Exit.fromRethnetExitCode(rethnetResult.exitCode); - // We return an object with only the properties that are used by Hardhat. - // To be extra sure that the other properties are not used, we add getters - // that exit the process if accessed. + const createdAddress = + isSuccessResult(rethnetResult.result) && + isCreateOutput(rethnetResult.result.output) + ? rethnetResult.result.output.address + : undefined; + + const exit = isSuccessResult(rethnetResult.result) + ? Exit.fromRethnetSuccessReason(rethnetResult.result.reason) + : isHaltResult(rethnetResult.result) + ? Exit.fromRethnetExceptionalHalt(rethnetResult.result.reason) + : new Exit(ExitCode.REVERT); + + const returnValue = isRevertResult(rethnetResult.result) + ? rethnetResult.result.returnValue + : isSuccessResult(rethnetResult.result) + ? rethnetResult.result.output.returnValue + : Buffer.from([]); - const bloom = rethnetLogsToBloom(rethnetResult.logs); + const bloom = isSuccessResult(rethnetResult.result) + ? rethnetLogsToBloom(rethnetResult.result.logs) + : new Bloom(undefined); return { - gasUsed: rethnetResult.gasUsed, + gasUsed: rethnetResult.result.gasUsed, createdAddress: - rethnetResult.output.address !== undefined - ? new Address(rethnetResult.output.address) - : undefined, - exit: vmError, - returnValue: rethnetResult.output.output ?? Buffer.from([]), + createdAddress !== undefined ? new Address(createdAddress) : undefined, + exit, + returnValue, bloom, receipt: { // Receipts have a 0 as status on error - status: vmError.isError() ? 0 : 1, - cumulativeBlockGasUsed: blockGasUsed + rethnetResult.gasUsed, + status: exit.isError() ? 0 : 1, + cumulativeBlockGasUsed: blockGasUsed + rethnetResult.result.gasUsed, bitvector: bloom.bitvector, - logs: rethnetResult.logs.map((log) => { - return [log.address, log.topics, log.data]; - }), + logs: !isHaltResult(rethnetResult.result) + ? rethnetResult.result.logs.map((log) => { + return [log.address, log.topics, log.data]; + }) + : [], }, }; } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index b49d5e94f9..bf453da73b 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -15,17 +15,18 @@ import { RpcDebugTraceOutput } from "../output"; import { HardhatBlockchainInterface } from "../types/HardhatBlockchainInterface"; import { EthereumJSAdapter } from "./ethereumjs"; +import { ExitCode } from "./exit"; import { RethnetAdapter } from "./rethnet"; import { RunTxResult, Trace, TracingCallbacks, VMAdapter } from "./vm-adapter"; /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -function printEthereumJSTrace(trace: any) { +function _printEthereumJSTrace(trace: any) { console.log(JSON.stringify(trace, null, 2)); } -function printRethnetTrace(trace: any) { +function _printRethnetTrace(trace: any) { console.log( JSON.stringify( trace, @@ -73,7 +74,7 @@ export class DualModeAdapter implements VMAdapter { blockContext: Block, forceBaseFeeZero?: boolean ): Promise<[RunTxResult, Trace]> { - const [ethereumJSResult, ethereumJSTrace] = + const [ethereumJSResult, _ethereumJSTrace] = await this._ethereumJSAdapter.dryRun(tx, blockContext, forceBaseFeeZero); const [rethnetResult, rethnetTrace] = await this._rethnetAdapter.dryRun( @@ -87,11 +88,11 @@ export class DualModeAdapter implements VMAdapter { return [rethnetResult, rethnetTrace]; } catch (e) { // if the results didn't match, print the traces - console.log("EthereumJS trace"); - printEthereumJSTrace(ethereumJSTrace); - console.log(); - console.log("Rethnet trace"); - printRethnetTrace(rethnetTrace); + // console.log("EthereumJS trace"); + // printEthereumJSTrace(ethereumJSTrace); + // console.log(); + // console.log("Rethnet trace"); + // printRethnetTrace(rethnetTrace); throw e; } @@ -246,24 +247,20 @@ export class DualModeAdapter implements VMAdapter { const [ethereumJSResult, ethereumJSTrace] = await this._ethereumJSAdapter.runTxInBlock(tx, block); - const [rethnetResult, rethnetTrace] = + const [rethnetResult, _rethnetTrace] = await this._rethnetAdapter.runTxInBlock(tx, block); try { assertEqualRunTxResults(ethereumJSResult, rethnetResult); - if (rethnetResult.createdAddress !== undefined) { - const _test = this.getAccount(rethnetResult.createdAddress); - } - return [ethereumJSResult, ethereumJSTrace]; } catch (e) { // if the results didn't match, print the traces - console.log("EthereumJS trace"); - printEthereumJSTrace(ethereumJSTrace); - console.log(); - console.log("Rethnet trace"); - printRethnetTrace(rethnetTrace); + // console.log("EthereumJS trace"); + // printEthereumJSTrace(ethereumJSTrace); + // console.log(); + // console.log("Rethnet trace"); + // printRethnetTrace(rethnetTrace); throw e; } @@ -307,34 +304,26 @@ function assertEqualRunTxResults( ethereumJSResult: RunTxResult, rethnetResult: RunTxResult ) { - if (ethereumJSResult.gasUsed !== rethnetResult.gasUsed) { - console.trace( - `Different totalGasSpent: ${ethereumJSResult.gasUsed} !== ${rethnetResult.gasUsed}` - ); - throw new Error("Different totalGasSpent"); - } - - if ( - ethereumJSResult.createdAddress?.toString() !== - rethnetResult.createdAddress?.toString() - ) { + if (ethereumJSResult.exit.kind !== rethnetResult.exit.kind) { console.trace( - `Different createdAddress: ${ethereumJSResult.createdAddress?.toString()} !== ${rethnetResult.createdAddress?.toString()}` + `Different exceptionError.error: ${ethereumJSResult.exit.kind} !== ${rethnetResult.exit.kind}` ); - throw new Error("Different createdAddress"); + throw new Error("Different exceptionError.error"); } - if (ethereumJSResult.exit.kind !== rethnetResult.exit.kind) { + if (ethereumJSResult.gasUsed !== rethnetResult.gasUsed) { console.trace( - `Different exceptionError.error: ${ethereumJSResult.exit.kind} !== ${rethnetResult.exit.kind}` + `Different totalGasSpent: ${ethereumJSResult.gasUsed} !== ${rethnetResult.gasUsed}` ); - throw new Error("Different exceptionError.error"); + throw new Error("Different totalGasSpent"); } - // TODO: we only compare the return values when a contract was *not* created, - // because sometimes ethereumjs has the created bytecode in the return value - // and rethnet doesn't - if (ethereumJSResult.createdAddress === undefined) { + const exitCode = ethereumJSResult.exit.kind; + if (exitCode === ExitCode.SUCCESS || exitCode === ExitCode.REVERT) { + // TODO: we only compare the return values when a contract was *not* created, + // because sometimes ethereumjs has the created bytecode in the return value + // and rethnet doesn't + // if (ethereumJSResult.createdAddress === undefined) { if ( ethereumJSResult.returnValue.toString("hex") !== rethnetResult.returnValue.toString("hex") @@ -346,35 +335,55 @@ function assertEqualRunTxResults( ); throw new Error("Different returnValue"); } - } + // } - if (!ethereumJSResult.bloom.equals(rethnetResult.bloom)) { - console.trace( - `Different bloom: ${ethereumJSResult.bloom} !== ${rethnetResult.bloom}` - ); - throw new Error("Different bloom"); - } + if (!ethereumJSResult.bloom.equals(rethnetResult.bloom)) { + console.trace( + `Different bloom: ${ethereumJSResult.bloom} !== ${rethnetResult.bloom}` + ); + throw new Error("Different bloom"); + } - if ( - !ethereumJSResult.receipt.bitvector.equals(rethnetResult.receipt.bitvector) - ) { - console.trace( - `Different receipt bitvector: ${ethereumJSResult.receipt.bitvector} !== ${rethnetResult.receipt.bitvector}` - ); - throw new Error("Different receipt bitvector"); - } + if ( + !ethereumJSResult.receipt.bitvector.equals( + rethnetResult.receipt.bitvector + ) + ) { + console.trace( + `Different receipt bitvector: ${ethereumJSResult.receipt.bitvector} !== ${rethnetResult.receipt.bitvector}` + ); + throw new Error("Different receipt bitvector"); + } - if ( - ethereumJSResult.receipt.cumulativeBlockGasUsed !== - rethnetResult.receipt.cumulativeBlockGasUsed - ) { - console.trace( - `Different receipt cumulativeBlockGasUsed: ${ethereumJSResult.receipt.cumulativeBlockGasUsed} !== ${rethnetResult.receipt.cumulativeBlockGasUsed}` - ); - throw new Error("Different receipt cumulativeBlockGasUsed"); + if ( + ethereumJSResult.receipt.cumulativeBlockGasUsed !== + rethnetResult.receipt.cumulativeBlockGasUsed + ) { + console.trace( + `Different receipt cumulativeBlockGasUsed: ${ethereumJSResult.receipt.cumulativeBlockGasUsed} !== ${rethnetResult.receipt.cumulativeBlockGasUsed}` + ); + throw new Error("Different receipt cumulativeBlockGasUsed"); + } + + assertEqualLogs(ethereumJSResult.receipt.logs, rethnetResult.receipt.logs); } - assertEqualLogs(ethereumJSResult.receipt.logs, rethnetResult.receipt.logs); + if (exitCode === ExitCode.SUCCESS) { + if ( + ethereumJSResult.createdAddress?.toString() !== + rethnetResult.createdAddress?.toString() && + // ethereumjs returns a createdAddress, even when reverting + !( + rethnetResult.createdAddress === undefined && + ethereumJSResult.exit.kind !== ExitCode.SUCCESS + ) + ) { + console.trace( + `Different createdAddress: ${ethereumJSResult.createdAddress?.toString()} !== ${rethnetResult.createdAddress?.toString()}` + ); + throw new Error("Different createdAddress"); + } + } } function assertEqualLogs(ethereumJSLogs: Log[], rethnetLogs: Log[]) { diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index a4f2364f3c..df4d8a5ce4 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -6,6 +6,7 @@ import { InterpreterStep, Message, } from "@nomicfoundation/ethereumjs-evm"; +import { ERROR } from "@nomicfoundation/ethereumjs-evm/dist/exceptions"; import { DefaultStateManager, StateManager, @@ -17,6 +18,7 @@ import { RunTxResult as EthereumJSRunTxResult, VM, } from "@nomicfoundation/ethereumjs-vm"; +import { SuccessReason } from "rethnet-evm"; import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; import { @@ -527,30 +529,64 @@ export class EthereumJSAdapter implements VMAdapter { private _afterMessageHandler = (result: EVMResult, next: any) => { if (this._tracingCallbacks !== undefined) { - const vmError = Exit.fromEthereumJSEvmError( - result.execResult.exceptionError - ); + const getLogs = () => { + return ( + result.execResult.logs?.map((log) => { + return { + address: log[0], + topics: log[1], + data: log[2], + }; + }) ?? [] + ); + }; + + const gasUsed = result.execResult.executionGasUsed; + + let executionResult; + + if (result.execResult.exceptionError === undefined) { + const reason = + result.execResult.selfdestruct === undefined + ? SuccessReason.Return + : SuccessReason.SelfDestruct; + + executionResult = { + reason, + gasUsed, + gasRefunded: result.execResult.gasRefund ?? 0n, + logs: getLogs(), + output: + result.createdAddress === undefined + ? { + returnValue: result.execResult.returnValue, + } + : { + address: result.createdAddress.toBuffer(), + returnValue: result.execResult.returnValue, + }, + }; + } else if (result.execResult.exceptionError.error === ERROR.REVERT) { + executionResult = { + gasUsed, + logs: getLogs(), + returnValue: result.execResult.returnValue, + }; + } else { + const vmError = Exit.fromEthereumJSEvmError( + result.execResult.exceptionError + ); - const rethnetExitCode = vmError.getRethnetExitCode(); + executionResult = { + reason: vmError.getRethnetExceptionalHalt(), + gasUsed, + }; + } return this._tracingCallbacks.afterMessage( { executionResult: { - exitCode: rethnetExitCode, - output: { - address: result.createdAddress?.toBuffer(), - output: result.execResult.returnValue, - }, - gasUsed: result.execResult.executionGasUsed, - gasRefunded: result.execResult.gasRefund ?? 0n, - logs: - result.execResult.logs?.map((log) => { - return { - address: log[0], - topics: log[1], - data: log[2], - }; - }) ?? [], + result: executionResult, trace: { steps: [], returnValue: result.execResult.returnValue, diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts index feff8d1309..abfb50ee99 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts @@ -1,5 +1,6 @@ import { EvmError } from "@nomicfoundation/ethereumjs-evm"; import { ERROR } from "@nomicfoundation/ethereumjs-evm/dist/exceptions"; +import { ExceptionalHalt, SuccessReason } from "rethnet-evm"; export enum ExitCode { SUCCESS, @@ -10,36 +11,33 @@ export enum ExitCode { CODESIZE_EXCEEDS_MAXIMUM, } -const exitCodeToRethnetExitCode: Record = { - [ExitCode.SUCCESS]: 0x00, - [ExitCode.REVERT]: 0x20, - [ExitCode.OUT_OF_GAS]: 0x50, - [ExitCode.INTERNAL_ERROR]: 0x20, - [ExitCode.INVALID_OPCODE]: 0x53, - [ExitCode.CODESIZE_EXCEEDS_MAXIMUM]: 0x65, -}; - export class Exit { - public static fromRethnetExitCode(rethnetExitCode: number): Exit { - switch (rethnetExitCode) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: + public static fromRethnetSuccessReason(reason: SuccessReason): Exit { + switch (reason) { + case SuccessReason.Return: + case SuccessReason.SelfDestruct: + case SuccessReason.Stop: return new Exit(ExitCode.SUCCESS); - case 0x20: - return new Exit(ExitCode.REVERT); - case 0x50: + // TODO: Should we throw an error if default is hit? + } + } + + public static fromRethnetExceptionalHalt(halt: ExceptionalHalt): Exit { + switch (halt) { + case ExceptionalHalt.OutOfGas: return new Exit(ExitCode.OUT_OF_GAS); - case 0x51: - case 0x53: + + case ExceptionalHalt.OpcodeNotFound: + case ExceptionalHalt.InvalidFEOpcode: return new Exit(ExitCode.INVALID_OPCODE); - case 0x65: + + case ExceptionalHalt.CreateContractSizeLimit: return new Exit(ExitCode.CODESIZE_EXCEEDS_MAXIMUM); + default: { // TODO temporary, should be removed in production // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error - throw new Error(`Unmatched rethnet exit code: ${rethnetExitCode}`); + throw new Error(`Unmatched rethnet exceptional halt: ${halt}`); } } } @@ -102,7 +100,7 @@ export class Exit { public getEthereumJSError(): EvmError | undefined { switch (this.kind) { case ExitCode.SUCCESS: - return; + return undefined; case ExitCode.REVERT: return new EvmError(ERROR.REVERT); case ExitCode.OUT_OF_GAS: @@ -118,7 +116,19 @@ export class Exit { const _exhaustiveCheck: never = this.kind; } - public getRethnetExitCode(): number { - return exitCodeToRethnetExitCode[this.kind]; + public getRethnetExceptionalHalt(): ExceptionalHalt { + switch (this.kind) { + case ExitCode.OUT_OF_GAS: + return ExceptionalHalt.OutOfGas; + case ExitCode.INVALID_OPCODE: + return ExceptionalHalt.OpcodeNotFound; + case ExitCode.CODESIZE_EXCEEDS_MAXIMUM: + return ExceptionalHalt.CreateContractSizeLimit; + + default: + // TODO temporary, should be removed in production + // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error + throw new Error(`Unmatched rethnet exceptional halt: ${this.kind}`); + } } } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 084dc661eb..18e95148a7 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -90,8 +90,8 @@ export class RethnetAdapter implements VMAdapter { ); return [result, rethnetResult.execResult.trace]; } catch (e) { - console.log("Rethnet trace"); - console.log(rethnetResult.execResult.trace); + // console.log("Rethnet trace"); + // console.log(rethnetResult.execResult.trace); throw e; } } @@ -218,8 +218,8 @@ export class RethnetAdapter implements VMAdapter { ); return [result, rethnetResult.trace]; } catch (e) { - console.log("Rethnet trace"); - console.log(rethnetResult.trace); + // console.log("Rethnet trace"); + // console.log(rethnetResult.trace); throw e; } } @@ -230,7 +230,7 @@ export class RethnetAdapter implements VMAdapter { public async addBlockRewards( rewards: Array<[Address, bigint]> ): Promise { - const blockBuilder = await BlockBuilder.new( + const blockBuilder = BlockBuilder.new( this._blockchain, this._state.asInner(), {}, diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/message-trace.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/message-trace.ts index 1faad79f83..a75a3be407 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/message-trace.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/message-trace.ts @@ -1,5 +1,12 @@ import type { Bytecode } from "./model"; import type { Exit } from "../provider/vm/exit"; +import { + CallOutput, + CreateOutput, + HaltResult, + RevertResult, + SuccessResult, +} from "rethnet-evm"; export type MessageTrace = | CreateMessageTrace @@ -91,3 +98,34 @@ export type MessageTraceStep = MessageTrace | EvmStep; export interface EvmStep { pc: number; } + +export function isCallOutput( + output: CallOutput | CreateOutput +): output is CallOutput { + return !isCreateOutput(output); +} + +export function isCreateOutput( + output: CallOutput | CreateOutput +): output is CreateOutput { + return "address" in output; +} + +export function isSuccessResult( + result: SuccessResult | RevertResult | HaltResult +): result is SuccessResult { + // Only need to check for one unique field + return "gasRefunded" in result; +} + +export function isRevertResult( + result: SuccessResult | RevertResult | HaltResult +): result is RevertResult { + return !("reason" in result); +} + +export function isHaltResult( + result: SuccessResult | RevertResult | HaltResult +): result is HaltResult { + return !("logs" in result); +} diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts index a4cfa4a9ad..5893ee2449 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts @@ -1,5 +1,6 @@ import type { Common } from "@nomicfoundation/ethereumjs-common"; -import type { +import { + CreateOutput, TracingMessage, TracingMessageResult, TracingStep, @@ -16,7 +17,9 @@ import { CallMessageTrace, CreateMessageTrace, isCreateTrace, + isHaltResult, isPrecompileTrace, + isSuccessResult, MessageTrace, PrecompileMessageTrace, } from "./message-trace"; @@ -226,14 +229,22 @@ export class VMTracer { try { const trace = this._messageTraces[this._messageTraces.length - 1]; + trace.gasUsed = result.executionResult.result.gasUsed; - trace.exit = Exit.fromRethnetExitCode(result.executionResult.exitCode); - trace.returnData = - result.executionResult.output.output ?? Buffer.from([]); - trace.gasUsed = result.executionResult.gasUsed; + const executionResult = result.executionResult.result; + if (isSuccessResult(executionResult)) { + trace.exit = Exit.fromRethnetSuccessReason(executionResult.reason); + trace.returnData = executionResult.output.returnValue; - if (isCreateTrace(trace)) { - trace.deployedContract = result.executionResult.output.address; + if (isCreateTrace(trace)) { + trace.deployedContract = ( + executionResult.output as CreateOutput + ).address; + } + } else if (isHaltResult(executionResult)) { + trace.exit = Exit.fromRethnetExceptionalHalt(executionResult.reason); + } else { + trace.exit = new Exit(ExitCode.REVERT); } if (this._messageTraces.length > 1) { diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts index a1f82d957e..9001a0e52e 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts @@ -414,7 +414,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[9 ], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[10], ""); - assert.match(this.logger.lines[11], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[11], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[12], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[13], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[14], /^ Value:\s+0 ETH$/); @@ -458,7 +458,7 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[2 ], ""); assert.match(this.logger.lines[3 ], /^ Block #\d+:\s+0x[0-9a-f]{64}$/); assert.match(this.logger.lines[4 ], /^ Base fee: \d+$/); - assert.match(this.logger.lines[5 ], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[5 ], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[6 ], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[7 ], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); @@ -527,7 +527,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[22], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[23], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[24], ""); - assert.match(this.logger.lines[25], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[25], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[26], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[27], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[28], /^ Value:\s+0 ETH$/); @@ -570,7 +570,7 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[2 ], ""); assert.match(this.logger.lines[3 ], /^ Block #\d+:\s+0x[0-9a-f]{64}$/); assert.match(this.logger.lines[4 ], /^ Base fee: \d+$/); - assert.match(this.logger.lines[5 ], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[5 ], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[6 ], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[7 ], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); @@ -643,7 +643,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[ 8], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[ 9], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[10], ""); - assert.match(this.logger.lines[11], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[11], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[12], /^ Contract call:\s+/); assert.match(this.logger.lines[13], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[14], /^ To:\s+0x[0-9a-f]{40}/); From 6881a18d9c286e7254eb4d92a94f9e32a429de82 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 25 Jan 2023 00:20:42 -0300 Subject: [PATCH 12/31] improvement: throw error when using forking with Rethnet --- .../src/internal/hardhat-network/provider/vm/rethnet.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 18e95148a7..e6a8411ed9 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -3,7 +3,7 @@ import { Account, Address } from "@nomicfoundation/ethereumjs-util"; import { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; import { BlockBuilder, Blockchain, Rethnet } from "rethnet-evm"; -import { NodeConfig } from "../node-types"; +import { isForkedNodeConfig, NodeConfig } from "../node-types"; import { ethereumjsHeaderDataToRethnet, ethereumjsTransactionToRethnet, @@ -33,6 +33,11 @@ export class RethnetAdapter implements VMAdapter { selectHardfork: (blockNumber: bigint) => string, getBlockHash: (blockNumber: bigint) => Promise ): Promise { + if (isForkedNodeConfig(config)) { + // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error + throw new Error("Forking is not supported for Rethnet yet"); + } + const blockchain = new Blockchain(getBlockHash); const limitContractCodeSize = From 5956096e9cc7c3712c87307b969ef6ba20bdff51 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 25 Jan 2023 01:45:06 -0300 Subject: [PATCH 13/31] fix: bug fix in revm --- crates/rethnet_eth/Cargo.toml | 2 +- crates/rethnet_evm/Cargo.toml | 2 +- .../internal/hardhat-network/provider/vm/rethnet.ts | 3 --- .../test/internal/hardhat-network/provider/logs.ts | 10 +++++----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index 49a4d11b7a..10866db11a 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,7 +14,7 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -revm = { git = "https://github.com/wodann/revm", rev = "d7286a1", version = "2.3", default-features = false, features = ["dev", "serde"] } +revm = { git = "https://github.com/wodann/revm", rev = "ed1e39c", version = "2.3", default-features = false, features = ["dev", "serde"] } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"] } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index 4ae2f6acc6..8b7ee61dee 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -11,7 +11,7 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth" } -revm = { git = "https://github.com/wodann/revm", rev = "d7286a1", version = "2.3", default-features = false, features = ["dev", "serde", "std"] } +revm = { git = "https://github.com/wodann/revm", rev = "ed1e39c", version = "2.3", default-features = false, features = ["dev", "serde", "std"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } signature = { version = "1.6.4", default-features = false, features = ["std"] } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 28f8230594..a7d30dccc4 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -256,7 +256,6 @@ export class RethnetAdapter implements VMAdapter { rethnetResult, block.header.gasUsed ); - console.log("runTxInBlock", result.exit) return [result, rethnetResult.trace]; } catch (e) { console.log("Rethnet trace"); @@ -339,8 +338,6 @@ export class RethnetAdapter implements VMAdapter { const trace = this._vmTracer.getLastTopLevelMessageTrace(); const error = this._vmTracer.getLastError(); - console.log("getLastTrace", trace?.exit); - return { trace, error }; } diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts index a1f82d957e..9001a0e52e 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts @@ -414,7 +414,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[9 ], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[10], ""); - assert.match(this.logger.lines[11], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[11], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[12], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[13], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[14], /^ Value:\s+0 ETH$/); @@ -458,7 +458,7 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[2 ], ""); assert.match(this.logger.lines[3 ], /^ Block #\d+:\s+0x[0-9a-f]{64}$/); assert.match(this.logger.lines[4 ], /^ Base fee: \d+$/); - assert.match(this.logger.lines[5 ], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[5 ], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[6 ], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[7 ], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); @@ -527,7 +527,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[22], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[23], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[24], ""); - assert.match(this.logger.lines[25], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[25], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[26], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[27], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[28], /^ Value:\s+0 ETH$/); @@ -570,7 +570,7 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[2 ], ""); assert.match(this.logger.lines[3 ], /^ Block #\d+:\s+0x[0-9a-f]{64}$/); assert.match(this.logger.lines[4 ], /^ Base fee: \d+$/); - assert.match(this.logger.lines[5 ], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[5 ], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[6 ], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[7 ], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); @@ -643,7 +643,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[ 8], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[ 9], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[10], ""); - assert.match(this.logger.lines[11], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[11], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[12], /^ Contract call:\s+/); assert.match(this.logger.lines[13], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[14], /^ To:\s+0x[0-9a-f]{40}/); From fd03cc9cf2048dcaf3711e8886fe2870c9781fcd Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 25 Jan 2023 17:43:10 -0300 Subject: [PATCH 14/31] Use proper block number when mining a tx --- .../provider/vm/block-builder.ts | 4 ++ .../hardhat-network/helpers/contracts.ts | 58 +++++++++++++++++++ .../modules/eth/methods/sendTransaction.ts | 36 +++++++++++- 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/block-builder.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/block-builder.ts index 0af88fee5a..dee765b51c 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/block-builder.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/block-builder.ts @@ -82,6 +82,10 @@ export class BlockBuilder { gasUsed: this._gasUsed, }; + if (header.number === undefined) { + header.number = this._opts.parentBlock.header.number + 1n; + } + const blockData = { header, transactions: this._transactions }; const block = Block.fromBlockData(blockData, { common: this._common, diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts index edeb21430b..8b87863d45 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts @@ -420,6 +420,64 @@ contract Foo { topics: {}, }; +export const EXAMPLE_BLOCK_NUMBER_CONTRACT = { + sourceCode: `pragma solidity 0.8.17; + +contract Example { + uint public blockNumber; + + constructor() { + blockNumber = block.number; + } + + function setBlockNumber() public { + blockNumber = block.number; + } + + function getBlockNumber() public view returns (uint) { + return block.number; + } +}`, + bytecode: { + linkReferences: {}, + object: + "608060405234801561001057600080fd5b504360008190555060fa806100266000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c806342cbb15c14604157806357e871e714605b578063fbf6eaa5146075575b600080fd5b6047607d565b6040516052919060ab565b60405180910390f35b60616085565b604051606c919060ab565b60405180910390f35b607b608b565b005b600043905090565b60005481565b43600081905550565b6000819050919050565b60a5816094565b82525050565b600060208201905060be6000830184609e565b9291505056fea264697066735822122004d0ac1993d4db65d9aff533e23fe270bb1e47c8341f51f778e3e59c767c881964736f6c63430008110033", + opcodes: + "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP NUMBER PUSH1 0x0 DUP2 SWAP1 SSTORE POP PUSH1 0xFA DUP1 PUSH2 0x26 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x3C JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x42CBB15C EQ PUSH1 0x41 JUMPI DUP1 PUSH4 0x57E871E7 EQ PUSH1 0x5B JUMPI DUP1 PUSH4 0xFBF6EAA5 EQ PUSH1 0x75 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x47 PUSH1 0x7D JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH1 0x52 SWAP2 SWAP1 PUSH1 0xAB JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x61 PUSH1 0x85 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH1 0x6C SWAP2 SWAP1 PUSH1 0xAB JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x7B PUSH1 0x8B JUMP JUMPDEST STOP JUMPDEST PUSH1 0x0 NUMBER SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD DUP2 JUMP JUMPDEST NUMBER PUSH1 0x0 DUP2 SWAP1 SSTORE POP JUMP JUMPDEST PUSH1 0x0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0xA5 DUP2 PUSH1 0x94 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH1 0xBE PUSH1 0x0 DUP4 ADD DUP5 PUSH1 0x9E JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 DIV 0xD0 0xAC NOT SWAP4 0xD4 0xDB PUSH6 0xD9AFF533E23F 0xE2 PUSH17 0xBB1E47C8341F51F778E3E59C767C881964 PUSH20 0x6F6C634300081100330000000000000000000000 ", + sourceMap: "25:263:0:-:0;;;74:51;;;;;;;;;;108:12;94:11;:26;;;;25:263;;;;;;", + }, + abi: [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + inputs: [], + name: "blockNumber", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBlockNumber", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "setBlockNumber", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], + selectors: { + blockNumber: "0x57e871e7", + getBlockNumber: "0x42cbb15c", + setBlockNumber: "0xfbf6eaa5", + }, + topics: {}, +}; + export const EXAMPLE_CHAIN_ID_CONTRACT = { sourceCode: `pragma solidity 0.8.14; diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts index 16cdc7ff5b..5e5df08e25 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts @@ -6,6 +6,7 @@ import { numberToRpcQuantity, rpcQuantityToNumber, rpcQuantityToBigInt, + rpcDataToNumber, } from "../../../../../../../src/internal/core/jsonrpc/types/base-types"; import { InvalidInputError } from "../../../../../../../src/internal/core/providers/errors"; import { workaroundWindowsCiFailures } from "../../../../../../utils/workaround-windows-ci-failures"; @@ -14,7 +15,10 @@ import { assertReceiptMatchesGethOne, assertTransactionFailure, } from "../../../../helpers/assertions"; -import { EXAMPLE_REVERT_CONTRACT } from "../../../../helpers/contracts"; +import { + EXAMPLE_BLOCK_NUMBER_CONTRACT, + EXAMPLE_REVERT_CONTRACT, +} from "../../../../helpers/contracts"; import { setCWD } from "../../../../helpers/cwd"; import { getPendingBaseFeePerGas } from "../../../../helpers/getPendingBaseFeePerGas"; import { @@ -1103,6 +1107,36 @@ describe("Eth module", function () { // assert: assert.equal(await getChainIdFromContract(this.provider), chainId); }); + + it("Should use the correct value of block.number", async function () { + const contractAddress = await deployContract( + this.provider, + `0x${EXAMPLE_BLOCK_NUMBER_CONTRACT.bytecode.object}` + ); + + const blockNumberBeforeTx = rpcQuantityToNumber( + await this.provider.send("eth_blockNumber") + ); + + await this.provider.send("eth_sendTransaction", [ + { + to: contractAddress, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: `${EXAMPLE_BLOCK_NUMBER_CONTRACT.selectors.setBlockNumber}`, + }, + ]); + + const contractBlockNumber = rpcDataToNumber( + await this.provider.send("eth_call", [ + { + to: contractAddress, + data: `${EXAMPLE_BLOCK_NUMBER_CONTRACT.selectors.blockNumber}`, + }, + ]) + ); + + assert.equal(contractBlockNumber, blockNumberBeforeTx + 1); + }); }); describe("eth_sendTransaction with minGasPrice", function () { From fdd09cbfe2e47ccaa4c0e753cfaf479afcf699c5 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 25 Jan 2023 17:45:13 -0300 Subject: [PATCH 15/31] Add CREATE_COLLISION and STATIC_STATE_CHANGE exit codes --- .../hardhat-network/provider/vm/exit.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts index feff8d1309..b1b4dfff2e 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts @@ -8,6 +8,8 @@ export enum ExitCode { INTERNAL_ERROR, INVALID_OPCODE, CODESIZE_EXCEEDS_MAXIMUM, + CREATE_COLLISION, + STATIC_STATE_CHANGE, } const exitCodeToRethnetExitCode: Record = { @@ -15,8 +17,10 @@ const exitCodeToRethnetExitCode: Record = { [ExitCode.REVERT]: 0x20, [ExitCode.OUT_OF_GAS]: 0x50, [ExitCode.INTERNAL_ERROR]: 0x20, + [ExitCode.STATIC_STATE_CHANGE]: 0x52, [ExitCode.INVALID_OPCODE]: 0x53, [ExitCode.CODESIZE_EXCEEDS_MAXIMUM]: 0x65, + [ExitCode.CREATE_COLLISION]: 0x61, }; export class Exit { @@ -34,6 +38,10 @@ export class Exit { case 0x51: case 0x53: return new Exit(ExitCode.INVALID_OPCODE); + case 0x52: + return new Exit(ExitCode.STATIC_STATE_CHANGE); + case 0x61: + return new Exit(ExitCode.CREATE_COLLISION); case 0x65: return new Exit(ExitCode.CODESIZE_EXCEEDS_MAXIMUM); default: { @@ -69,9 +77,17 @@ export class Exit { return new Exit(ExitCode.CODESIZE_EXCEEDS_MAXIMUM); } + if (evmError.error === ERROR.CREATE_COLLISION) { + return new Exit(ExitCode.CREATE_COLLISION); + } + + if (evmError.error === ERROR.STATIC_STATE_CHANGE) { + return new Exit(ExitCode.STATIC_STATE_CHANGE); + } + // TODO temporary, should be removed in production // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error - throw new Error(`Unmatched rethnet exit code: ${evmError.error}`); + throw new Error(`Unmatched evm error: ${evmError.error}`); } constructor(public kind: ExitCode) {} @@ -94,6 +110,10 @@ export class Exit { return "Invalid opcode"; case ExitCode.CODESIZE_EXCEEDS_MAXIMUM: return "Codesize exceeds maximum"; + case ExitCode.CREATE_COLLISION: + return "Create collision"; + case ExitCode.STATIC_STATE_CHANGE: + return "State change during static call"; } const _exhaustiveCheck: never = this.kind; @@ -113,6 +133,10 @@ export class Exit { return new EvmError(ERROR.INVALID_OPCODE); case ExitCode.CODESIZE_EXCEEDS_MAXIMUM: return new EvmError(ERROR.CODESIZE_EXCEEDS_MAXIMUM); + case ExitCode.CREATE_COLLISION: + return new EvmError(ERROR.CREATE_COLLISION); + case ExitCode.STATIC_STATE_CHANGE: + return new EvmError(ERROR.STATIC_STATE_CHANGE); } const _exhaustiveCheck: never = this.kind; From e3fcbebcc2ea333ae9aa2bb7748323221ca872a6 Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 26 Jan 2023 11:57:53 -0300 Subject: [PATCH 16/31] bump: revm to incorporate bug fixes --- crates/rethnet_eth/Cargo.toml | 2 +- crates/rethnet_evm/Cargo.toml | 2 +- crates/rethnet_evm/src/block/builder.rs | 4 ++-- crates/rethnet_evm/src/db/layered_db.rs | 6 ++++- crates/rethnet_evm/src/db/request.rs | 6 ++++- crates/rethnet_evm/src/db/sync.rs | 6 ++++- crates/rethnet_evm/src/debug.rs | 2 +- crates/rethnet_evm/src/evm.rs | 2 +- crates/rethnet_evm/src/inspector.rs | 5 +++- crates/rethnet_evm/src/lib.rs | 6 +++-- crates/rethnet_evm/src/runtime.rs | 2 +- crates/rethnet_evm/src/trace.rs | 2 +- crates/rethnet_evm/src/transaction.rs | 16 ++++++++----- crates/rethnet_evm_napi/src/lib.rs | 20 ++++------------ .../provider/utils/convertToRethnet.ts | 4 ++-- .../hardhat-network/provider/vm/ethereumjs.ts | 24 +++++++------------ 16 files changed, 57 insertions(+), 52 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index a3bc1feeaa..2766c312b0 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,7 +14,7 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "318fea0", version = "3.0", default-features = false } +revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "72355f4", version = "3.0", default-features = false } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"] } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index 08335753b1..f3e5a9865b 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -10,7 +10,7 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth" } -revm = { git = "https://github.com/bluealloy/revm", rev = "318fea0", version = "2.3", default-features = false, features = ["dev", "serde"] } +revm = { git = "https://github.com/bluealloy/revm", rev = "72355f4", version = "2.3", default-features = false, features = ["dev", "serde"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } signature = { version = "1.6.4", default-features = false, features = ["std"] } diff --git a/crates/rethnet_evm/src/block/builder.rs b/crates/rethnet_evm/src/block/builder.rs index 28a6986323..3063a1d30c 100644 --- a/crates/rethnet_evm/src/block/builder.rs +++ b/crates/rethnet_evm/src/block/builder.rs @@ -5,8 +5,8 @@ use rethnet_eth::{ Address, U256, }; use revm::{ - db::DatabaseComponentError, BlockEnv, CfgEnv, EVMError, ExecutionResult, InvalidTransaction, - SpecId, TxEnv, + db::DatabaseComponentError, + primitives::{BlockEnv, CfgEnv, EVMError, ExecutionResult, InvalidTransaction, SpecId, TxEnv}, }; use tokio::runtime::Runtime; diff --git a/crates/rethnet_evm/src/db/layered_db.rs b/crates/rethnet_evm/src/db/layered_db.rs index bc996f434d..22c5fcb940 100644 --- a/crates/rethnet_evm/src/db/layered_db.rs +++ b/crates/rethnet_evm/src/db/layered_db.rs @@ -5,7 +5,11 @@ use rethnet_eth::{ trie::KECCAK_NULL_RLP, Address, B256, U256, }; -use revm::{db::State, Account, AccountInfo, Bytecode, DatabaseCommit, KECCAK_EMPTY}; +use revm::{ + db::State, + primitives::{Account, AccountInfo, Bytecode, KECCAK_EMPTY}, + DatabaseCommit, +}; use crate::StateDebug; diff --git a/crates/rethnet_evm/src/db/request.rs b/crates/rethnet_evm/src/db/request.rs index 3cfa266657..44c97a98ea 100644 --- a/crates/rethnet_evm/src/db/request.rs +++ b/crates/rethnet_evm/src/db/request.rs @@ -2,7 +2,11 @@ use std::fmt::Debug; use hashbrown::HashMap; use rethnet_eth::{Address, B256, U256}; -use revm::{db::State, Account, AccountInfo, Bytecode, DatabaseCommit}; +use revm::{ + db::State, + primitives::{Account, AccountInfo, Bytecode}, + DatabaseCommit, +}; use tokio::sync::oneshot; use crate::{debug::ModifierFn, StateDebug}; diff --git a/crates/rethnet_evm/src/db/sync.rs b/crates/rethnet_evm/src/db/sync.rs index 5725317c66..196730702d 100644 --- a/crates/rethnet_evm/src/db/sync.rs +++ b/crates/rethnet_evm/src/db/sync.rs @@ -2,7 +2,11 @@ use std::{fmt::Debug, io}; use hashbrown::HashMap; use rethnet_eth::{Address, B256, U256}; -use revm::{db::State, Account, AccountInfo, Bytecode, DatabaseCommit}; +use revm::{ + db::State, + primitives::{Account, AccountInfo, Bytecode}, + DatabaseCommit, +}; use tokio::{ runtime::{Builder, Runtime}, sync::{ diff --git a/crates/rethnet_evm/src/debug.rs b/crates/rethnet_evm/src/debug.rs index 3a705a3b25..4b8abaad9e 100644 --- a/crates/rethnet_evm/src/debug.rs +++ b/crates/rethnet_evm/src/debug.rs @@ -1,6 +1,6 @@ use auto_impl::auto_impl; use rethnet_eth::{Address, B256, U256}; -use revm::{AccountInfo, Bytecode}; +use revm::primitives::{AccountInfo, Bytecode}; pub type ModifierFn = Box) + Send>; diff --git a/crates/rethnet_evm/src/evm.rs b/crates/rethnet_evm/src/evm.rs index 8dd2c02112..da6e43afea 100644 --- a/crates/rethnet_evm/src/evm.rs +++ b/crates/rethnet_evm/src/evm.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, sync::Arc}; use revm::{ db::{DatabaseComponentError, DatabaseComponents}, - BlockEnv, CfgEnv, EVMError, ExecutionResult, ResultAndState, State, TxEnv, + primitives::{BlockEnv, CfgEnv, EVMError, ExecutionResult, ResultAndState, State, TxEnv}, }; use tokio::{runtime::Runtime, task::JoinHandle}; diff --git a/crates/rethnet_evm/src/inspector.rs b/crates/rethnet_evm/src/inspector.rs index 2d21768612..df91ed8829 100644 --- a/crates/rethnet_evm/src/inspector.rs +++ b/crates/rethnet_evm/src/inspector.rs @@ -1,4 +1,7 @@ -use revm::{opcode, Database, EVMData, Inspector, InstructionResult, Interpreter}; +use revm::{ + interpreter::{opcode, InstructionResult, Interpreter}, + Database, EVMData, Inspector, +}; use crate::trace::Trace; diff --git a/crates/rethnet_evm/src/lib.rs b/crates/rethnet_evm/src/lib.rs index c42ad57813..3e9f83746a 100644 --- a/crates/rethnet_evm/src/lib.rs +++ b/crates/rethnet_evm/src/lib.rs @@ -8,8 +8,10 @@ pub use hashbrown::HashMap; pub use revm::{ db::EmptyDB, db::{BlockHash, BlockHashRef}, - Account, AccountInfo, BlockEnv, Bytecode, CfgEnv, CreateScheme, Database, DatabaseCommit, Eval, - ExecutionResult, Halt, Log, Output, ResultAndState, SpecId, State, TransactTo, TxEnv, EVM, + primitives::{ + Account, AccountInfo, BlockEnv, Bytecode, CfgEnv, CreateScheme, Eval, ExecutionResult, + Halt, Log, Output, ResultAndState, SpecId, State, TransactTo, TxEnv, + }, }; pub use crate::{ diff --git a/crates/rethnet_evm/src/runtime.rs b/crates/rethnet_evm/src/runtime.rs index 8202c5c8c1..a9d936fbdf 100644 --- a/crates/rethnet_evm/src/runtime.rs +++ b/crates/rethnet_evm/src/runtime.rs @@ -1,6 +1,6 @@ use std::{fmt::Debug, sync::Arc}; -use revm::{BlockEnv, CfgEnv, ExecutionResult, SpecId, TxEnv}; +use revm::primitives::{BlockEnv, CfgEnv, ExecutionResult, SpecId, TxEnv}; use crate::{ blockchain::AsyncBlockchain, db::AsyncState, evm::run_transaction, trace::Trace, diff --git a/crates/rethnet_evm/src/trace.rs b/crates/rethnet_evm/src/trace.rs index 5624a5970b..e2adf69c28 100644 --- a/crates/rethnet_evm/src/trace.rs +++ b/crates/rethnet_evm/src/trace.rs @@ -1,5 +1,5 @@ use rethnet_eth::Bytes; -use revm::{Gas, InstructionResult}; +use revm::interpreter::{Gas, InstructionResult}; /// A trace for an EVM call. #[derive(Default)] diff --git a/crates/rethnet_evm/src/transaction.rs b/crates/rethnet_evm/src/transaction.rs index 8e6ad2109f..a341aede54 100644 --- a/crates/rethnet_evm/src/transaction.rs +++ b/crates/rethnet_evm/src/transaction.rs @@ -9,7 +9,11 @@ use rethnet_eth::{ }, Address, Bloom, Bytes, B256, U256, }; -use revm::{db::DatabaseComponentError, EVMError, InvalidTransaction}; +use revm::{ + db::DatabaseComponentError, + interpreter::InstructionResult, + primitives::{CreateScheme, EVMError, InvalidTransaction, TransactTo, TxEnv}, +}; /// Invalid transaction error #[derive(Debug, thiserror::Error)] @@ -51,7 +55,7 @@ pub struct TransactionInfo { pub logs: Vec, pub logs_bloom: Bloom, // pub traces: todo!(), - pub exit: revm::InstructionResult, + pub exit: InstructionResult, pub out: Option, } @@ -78,12 +82,12 @@ impl PendingTransaction { } } -impl From for revm::TxEnv { +impl From for TxEnv { fn from(transaction: PendingTransaction) -> Self { - fn transact_to(kind: TransactionKind) -> revm::TransactTo { + fn transact_to(kind: TransactionKind) -> TransactTo { match kind { - TransactionKind::Call(address) => revm::TransactTo::Call(address), - TransactionKind::Create => revm::TransactTo::Create(revm::CreateScheme::Create), + TransactionKind::Call(address) => TransactTo::Call(address), + TransactionKind::Create => TransactTo::Create(CreateScheme::Create), } } diff --git a/crates/rethnet_evm_napi/src/lib.rs b/crates/rethnet_evm_napi/src/lib.rs index 0d4a23bf4d..b1d3fa2b0b 100644 --- a/crates/rethnet_evm_napi/src/lib.rs +++ b/crates/rethnet_evm_napi/src/lib.rs @@ -275,8 +275,7 @@ pub struct SuccessResult { #[napi(object)] pub struct RevertResult { pub gas_used: BigInt, - pub logs: Vec, - pub return_value: Buffer, + pub output: Buffer, } /// Indicates that the EVM has experienced an exceptional halt. This causes execution to @@ -367,19 +366,10 @@ impl From<(rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)> for Executi }, }) } - rethnet_evm::ExecutionResult::Revert { - gas_used, - logs, - return_value, - } => { - let logs = logs.into_iter().map(Log::from).collect(); - - Either3::B(RevertResult { - gas_used: BigInt::from(gas_used), - logs, - return_value: Buffer::from(return_value.as_ref()), - }) - } + rethnet_evm::ExecutionResult::Revert { gas_used, output } => Either3::B(RevertResult { + gas_used: BigInt::from(gas_used), + output: Buffer::from(output.as_ref()), + }), rethnet_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult { reason: reason.into(), gas_used: BigInt::from(gas_used), diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts index 96bbb86954..788ffb6765 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts @@ -173,7 +173,7 @@ export function rethnetResultToRunTxResult( : new Exit(ExitCode.REVERT); const returnValue = isRevertResult(rethnetResult.result) - ? rethnetResult.result.returnValue + ? rethnetResult.result.output : isSuccessResult(rethnetResult.result) ? rethnetResult.result.output.returnValue : Buffer.from([]); @@ -194,7 +194,7 @@ export function rethnetResultToRunTxResult( status: exit.isError() ? 0 : 1, cumulativeBlockGasUsed: blockGasUsed + rethnetResult.result.gasUsed, bitvector: bloom.bitvector, - logs: !isHaltResult(rethnetResult.result) + logs: isSuccessResult(rethnetResult.result) ? rethnetResult.result.logs.map((log) => { return [log.address, log.topics, log.data]; }) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index df4d8a5ce4..75fc94811a 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -529,18 +529,6 @@ export class EthereumJSAdapter implements VMAdapter { private _afterMessageHandler = (result: EVMResult, next: any) => { if (this._tracingCallbacks !== undefined) { - const getLogs = () => { - return ( - result.execResult.logs?.map((log) => { - return { - address: log[0], - topics: log[1], - data: log[2], - }; - }) ?? [] - ); - }; - const gasUsed = result.execResult.executionGasUsed; let executionResult; @@ -555,7 +543,14 @@ export class EthereumJSAdapter implements VMAdapter { reason, gasUsed, gasRefunded: result.execResult.gasRefund ?? 0n, - logs: getLogs(), + logs: + result.execResult.logs?.map((log) => { + return { + address: log[0], + topics: log[1], + data: log[2], + }; + }) ?? [], output: result.createdAddress === undefined ? { @@ -569,8 +564,7 @@ export class EthereumJSAdapter implements VMAdapter { } else if (result.execResult.exceptionError.error === ERROR.REVERT) { executionResult = { gasUsed, - logs: getLogs(), - returnValue: result.execResult.returnValue, + output: result.execResult.returnValue, }; } else { const vmError = Exit.fromEthereumJSEvmError( From 349129121e47b331296e90bea444983957965e5b Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 26 Jan 2023 11:27:12 -0300 Subject: [PATCH 17/31] Fix stack traces tests setup --- .../hardhat-network/stack-traces/execution.ts | 20 +++++++------------ .../hardhat-network/stack-traces/test.ts | 2 +- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts index 11a144b7ac..8ecfde4755 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/execution.ts @@ -11,11 +11,10 @@ import abi from "ethereumjs-abi"; import { HardhatBlockchain } from "../../../../src/internal/hardhat-network/provider/HardhatBlockchain"; import { VMAdapter } from "../../../../src/internal/hardhat-network/provider/vm/vm-adapter"; -import { DualModeAdapter } from "../../../../src/internal/hardhat-network/provider/vm/dual"; import { MessageTrace } from "../../../../src/internal/hardhat-network/stack-traces/message-trace"; -import { VMTracer } from "../../../../src/internal/hardhat-network/stack-traces/vm-tracer"; import { defaultHardhatNetworkParams } from "../../../../src/internal/core/config/default-config"; import { BlockBuilder } from "../../../../src/internal/hardhat-network/provider/vm/block-builder"; +import { createVm } from "../../../../src/internal/hardhat-network/provider/vm/creation"; const senderPrivateKey = Buffer.from( "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109", @@ -36,7 +35,7 @@ export async function instantiateVm(): Promise<[VMAdapter, Common]> { }) ); - const vm = await DualModeAdapter.create( + const vm = await createVm( common, blockchain, { @@ -106,10 +105,6 @@ export async function traceTransaction( const signedTx = tx.sign(senderPrivateKey); - const vmTracer = new VMTracer(vm as any, common); - // TODO adapt this part of the tests - // vmTracer.enableTracing(); - try { const blockBuilder = new BlockBuilder(vm, common, { parentBlock: Block.fromBlockData( @@ -127,14 +122,13 @@ export async function traceTransaction( await blockBuilder.addRewards([]); await blockBuilder.seal(); - const messageTrace = vmTracer.getLastTopLevelMessageTrace(); - if (messageTrace === undefined) { - const lastError = vmTracer.getLastError(); - throw lastError ?? new Error("Cannot get last top level message trace"); + const { trace, error } = vm.getLastTrace(); + if (trace === undefined) { + throw error ?? new Error("Cannot get last top level message trace"); } - return messageTrace; + return trace; } finally { - // vmTracer.disableTracing(); + vm.clearLastError(); } } diff --git a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts index da2efedb6d..7d8b01b89d 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/stack-traces/test.ts @@ -842,7 +842,7 @@ const solidity08Compilers = solidityCompilers.filter( filterSolcVersionBy("^0.8.0") ); -describe.skip("Stack traces", function () { +describe("Stack traces", function () { setCWD(); // if a path to a solc file was specified, we only run these tests and use From b4c23ad3317b335b652b83da5e9ebe10fd4224f6 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 26 Jan 2023 11:27:39 -0300 Subject: [PATCH 18/31] Remove ethJsOnly logic --- .../src/internal/hardhat-network/provider/vm/dual.ts | 9 +-------- .../internal/hardhat-network/provider/vm/rethnet.ts | 11 +---------- .../hardhat-network/provider/vm/vm-adapter.ts | 2 +- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index 91210de5f0..523a2e5d1c 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -172,18 +172,11 @@ export class DualModeAdapter implements VMAdapter { return rethnetStorageSlot; } - public async getContractCode( - address: Address, - ethJsOnly?: boolean - ): Promise { + public async getContractCode(address: Address): Promise { const ethereumJSCode = await this._ethereumJSAdapter.getContractCode( address ); - if (ethJsOnly === true) { - return ethereumJSCode; - } - const rethnetCode = await this._rethnetAdapter.getContractCode(address); if (!ethereumJSCode.equals(rethnetCode)) { diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index a7d30dccc4..b0eb64914f 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -149,16 +149,7 @@ export class RethnetAdapter implements VMAdapter { /** * Get the contract code at the given address. */ - public async getContractCode( - address: Address, - ethJsOnly?: boolean - ): Promise { - if (ethJsOnly === true) { - throw new Error( - "Calling RethnetAdapter.getContractCode with ethJsOnly=true, this shouldn't happen" - ); - } - + public async getContractCode(address: Address): Promise { return this._state.getContractCode(address); } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts index e00dcd089c..30a166d797 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts @@ -40,7 +40,7 @@ export interface VMAdapter { // getters getAccount(address: Address): Promise; getContractStorage(address: Address, key: Buffer): Promise; - getContractCode(address: Address, ethJsOnly?: boolean): Promise; + getContractCode(address: Address): Promise; // setters putAccount(address: Address, account: Account): Promise; From 2496df26d538041fd91fd688f5ff42892b6a87dd Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 26 Jan 2023 13:23:58 -0300 Subject: [PATCH 19/31] Get contract code in message trace --- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 4 ++++ .../hardhat-network/provider/vm/dual.ts | 4 ++-- .../hardhat-network/provider/vm/ethereumjs.ts | 7 ++++++- .../hardhat-network/provider/vm/rethnet.ts | 2 +- .../hardhat-network/stack-traces/vm-tracer.ts | 19 ++++++++----------- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index b1498abfea..c13cf318fc 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -36,6 +36,10 @@ pub struct TracingMessage { /// is being done. #[napi(readonly)] pub code_address: Option, + + /// Code of the contract that is being executed. + #[napi(readonly)] + pub code: Option, } #[napi(object)] diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index 523a2e5d1c..66533f71cb 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -70,8 +70,8 @@ export class DualModeAdapter implements VMAdapter { common ); - const ethereumJSVMTracer = new VMTracer(ethereumJSAdapter, common, false); - const rethnetVMTracer = new VMTracer(rethnetAdapter, common, false); + const ethereumJSVMTracer = new VMTracer(common, false); + const rethnetVMTracer = new VMTracer(common, false); return new DualModeAdapter( ethereumJSAdapter, diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index 55aee4e0b4..5969054cc5 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -58,7 +58,7 @@ export class EthereumJSAdapter implements VMAdapter { private readonly _forkNetworkId?: number, private readonly _forkBlockNumber?: bigint ) { - this._vmTracer = new VMTracer(this, _common, false); + this._vmTracer = new VMTracer(_common, false); assertHardhatInvariant( this._vm.evm.events !== undefined, @@ -489,11 +489,16 @@ export class EthereumJSAdapter implements VMAdapter { private _beforeMessageHandler = async (message: Message, next: any) => { try { + const code = + message.to !== undefined + ? await this.getContractCode(message.codeAddress) + : undefined; await this._vmTracer.addBeforeMessage({ ...message, to: message.to?.toBuffer(), codeAddress: message.to !== undefined ? message.codeAddress.toBuffer() : undefined, + code, }); return next(); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index b0eb64914f..7a8296c03f 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -41,7 +41,7 @@ export class RethnetAdapter implements VMAdapter { private readonly _selectHardfork: (blockNumber: bigint) => string, common: Common ) { - this._vmTracer = new VMTracer(this, common, false); + this._vmTracer = new VMTracer(common, false); } public static async create( diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts index bbf409a719..6aa987650d 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts @@ -6,11 +6,10 @@ import type { } from "rethnet-evm"; import { getActivePrecompiles } from "@nomicfoundation/ethereumjs-evm"; -import { Address, bufferToBigInt } from "@nomicfoundation/ethereumjs-util"; +import { bufferToBigInt } from "@nomicfoundation/ethereumjs-util"; import { assertHardhatInvariant } from "../../core/errors"; import { Exit, ExitCode } from "../provider/vm/exit"; -import { VMAdapter } from "../provider/vm/vm-adapter"; import { CallMessageTrace, @@ -35,11 +34,7 @@ export class VMTracer { private _lastError: Error | undefined; private _maxPrecompileNumber; - constructor( - private readonly _vm: VMAdapter, - common: Common, - private readonly _throwErrors = true - ) { + constructor(common: Common, private readonly _throwErrors = true) { this._maxPrecompileNumber = getActivePrecompiles(common).size; } @@ -109,16 +104,18 @@ export class VMTracer { const codeAddress = message.codeAddress; // if we enter here, then `to` is not undefined, therefore - // `codeAddress` should be defined + // `codeAddress` and `code` should be defined assertHardhatInvariant( codeAddress !== undefined, "codeAddress should be defined" ); - - const code = await this._vm.getContractCode(new Address(codeAddress)); + assertHardhatInvariant( + message.code !== undefined, + "code should be defined" + ); const callTrace: CallMessageTrace = { - code, + code: message.code, calldata: message.data, steps: [], value: message.value, From 8df8ed033d6f5f5a3b3f8aee9635e5a5b05d1399 Mon Sep 17 00:00:00 2001 From: Wodann Date: Sat, 28 Jan 2023 13:14:40 -0300 Subject: [PATCH 20/31] chore: split rethnet_evm_napi into separate files --- crates/rethnet_evm_napi/src/account.rs | 60 +++ crates/rethnet_evm_napi/src/block/builder.rs | 8 +- crates/rethnet_evm_napi/src/cast.rs | 2 +- crates/rethnet_evm_napi/src/config.rs | 133 +++++ crates/rethnet_evm_napi/src/lib.rs | 469 +----------------- crates/rethnet_evm_napi/src/log.rs | 26 + crates/rethnet_evm_napi/src/receipt.rs | 2 +- crates/rethnet_evm_napi/src/runtime.rs | 110 ++++ crates/rethnet_evm_napi/src/state.rs | 12 +- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 3 +- crates/rethnet_evm_napi/src/transaction.rs | 16 +- .../src/transaction/result.rs | 205 ++++++++ 12 files changed, 566 insertions(+), 480 deletions(-) create mode 100644 crates/rethnet_evm_napi/src/account.rs create mode 100644 crates/rethnet_evm_napi/src/config.rs create mode 100644 crates/rethnet_evm_napi/src/log.rs create mode 100644 crates/rethnet_evm_napi/src/runtime.rs create mode 100644 crates/rethnet_evm_napi/src/transaction/result.rs diff --git a/crates/rethnet_evm_napi/src/account.rs b/crates/rethnet_evm_napi/src/account.rs new file mode 100644 index 0000000000..dded796189 --- /dev/null +++ b/crates/rethnet_evm_napi/src/account.rs @@ -0,0 +1,60 @@ +use std::fmt::Debug; + +use napi::bindgen_prelude::{BigInt, Buffer}; +use napi_derive::napi; +use rethnet_evm::AccountInfo; + +#[napi(object)] +pub struct Account { + /// Account balance + #[napi(readonly)] + pub balance: BigInt, + /// Account nonce + #[napi(readonly)] + pub nonce: BigInt, + /// 256-bit code hash + #[napi(readonly)] + pub code_hash: Buffer, + /// Optionally, byte code + #[napi(readonly)] + pub code: Option, +} + +#[napi(object)] +pub struct AccountData { + /// Account balance + #[napi(readonly)] + pub balance: BigInt, + /// Account nonce + #[napi(readonly)] + pub nonce: BigInt, + /// Optionally, byte code + #[napi(readonly)] + pub code: Option, +} + +impl Debug for Account { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Account") + .field("balance", &self.balance) + .field("nonce", &self.nonce) + .field("code_hash", &self.code_hash.as_ref()) + .finish() + } +} + +impl From for Account { + fn from(account_info: AccountInfo) -> Self { + Self { + balance: BigInt { + sign_bit: false, + words: account_info.balance.as_limbs().to_vec(), + }, + nonce: BigInt::from(account_info.nonce), + code_hash: Buffer::from(account_info.code_hash.as_bytes()), + code: account_info + .code + .map(|code| Buffer::from(&code.bytes()[..code.len()])), + } + } +} diff --git a/crates/rethnet_evm_napi/src/block/builder.rs b/crates/rethnet_evm_napi/src/block/builder.rs index 47c66118ba..4628bf68ef 100644 --- a/crates/rethnet_evm_napi/src/block/builder.rs +++ b/crates/rethnet_evm_napi/src/block/builder.rs @@ -10,8 +10,12 @@ use rethnet_eth::{Address, U256}; use rethnet_evm::state::StateError; use crate::{ - blockchain::Blockchain, cast::TryCast, state::StateManager, tracer::Tracer, - transaction::Transaction, Config, ExecutionResult, + blockchain::Blockchain, + cast::TryCast, + config::Config, + state::StateManager, + tracer::Tracer, + transaction::{result::ExecutionResult, Transaction}, }; use super::{BlockConfig, BlockHeader}; diff --git a/crates/rethnet_evm_napi/src/cast.rs b/crates/rethnet_evm_napi/src/cast.rs index 26310c9d23..b17f7a0a64 100644 --- a/crates/rethnet_evm_napi/src/cast.rs +++ b/crates/rethnet_evm_napi/src/cast.rs @@ -5,7 +5,7 @@ use napi::{ use rethnet_eth::{Bytes, B256, U256}; use rethnet_evm::{AccountInfo, Bytecode}; -use crate::{Account, AccountData}; +use crate::account::{Account, AccountData}; /// An attempted conversion that consumes `self`, which may or may not be /// expensive. It is identical to [`TryInto`], but it allows us to implement diff --git a/crates/rethnet_evm_napi/src/config.rs b/crates/rethnet_evm_napi/src/config.rs new file mode 100644 index 0000000000..ff136b0224 --- /dev/null +++ b/crates/rethnet_evm_napi/src/config.rs @@ -0,0 +1,133 @@ +use napi::{ + bindgen_prelude::{BigInt, ToNapiValue}, + Status, +}; +use napi_derive::napi; +use rethnet_evm::CfgEnv; + +use crate::cast::TryCast; + +/// Identifier for the Ethereum spec. +#[napi] +pub enum SpecId { + /// Frontier + Frontier = 0, + /// Frontier Thawing + FrontierThawing = 1, + /// Homestead + Homestead = 2, + /// DAO Fork + DaoFork = 3, + /// Tangerine + Tangerine = 4, + /// Spurious Dragon + SpuriousDragon = 5, + /// Byzantium + Byzantium = 6, + /// Constantinople + Constantinople = 7, + /// Petersburg + Petersburg = 8, + /// Istanbul + Istanbul = 9, + /// Muir Glacier + MuirGlacier = 10, + /// Berlin + Berlin = 11, + /// London + London = 12, + /// Arrow Glacier + ArrowGlacier = 13, + /// Gray Glacier + GrayGlacier = 14, + /// Merge + Merge = 15, + /// Merge + EOF + MergeEOF = 16, + /// Latest + Latest = 17, +} + +impl From for rethnet_evm::SpecId { + fn from(value: SpecId) -> Self { + match value { + SpecId::Frontier => rethnet_evm::SpecId::FRONTIER, + SpecId::FrontierThawing => rethnet_evm::SpecId::FRONTIER_THAWING, + SpecId::Homestead => rethnet_evm::SpecId::HOMESTEAD, + SpecId::DaoFork => rethnet_evm::SpecId::DAO_FORK, + SpecId::Tangerine => rethnet_evm::SpecId::TANGERINE, + SpecId::SpuriousDragon => rethnet_evm::SpecId::SPURIOUS_DRAGON, + SpecId::Byzantium => rethnet_evm::SpecId::BYZANTIUM, + SpecId::Constantinople => rethnet_evm::SpecId::CONSTANTINOPLE, + SpecId::Petersburg => rethnet_evm::SpecId::PETERSBURG, + SpecId::Istanbul => rethnet_evm::SpecId::ISTANBUL, + SpecId::MuirGlacier => rethnet_evm::SpecId::MUIR_GLACIER, + SpecId::Berlin => rethnet_evm::SpecId::BERLIN, + SpecId::London => rethnet_evm::SpecId::LONDON, + SpecId::ArrowGlacier => rethnet_evm::SpecId::ARROW_GLACIER, + SpecId::GrayGlacier => rethnet_evm::SpecId::GRAY_GLACIER, + SpecId::Merge => rethnet_evm::SpecId::MERGE, + SpecId::MergeEOF => rethnet_evm::SpecId::MERGE_EOF, + SpecId::Latest => rethnet_evm::SpecId::LATEST, + } + } +} + +/// If not set, uses defaults from [`CfgEnv`]. +#[napi(object)] +pub struct Config { + /// The blockchain's ID + pub chain_id: Option, + /// Identifier for the Ethereum spec + pub spec_id: Option, + /// The contract code size limit for EIP-170 + pub limit_contract_code_size: Option, + /// Disables block limit validation + pub disable_block_gas_limit: Option, + /// Disables EIP-3607, which rejects transactions from sender with deployed code + pub disable_eip3607: Option, +} + +impl TryFrom for CfgEnv { + type Error = napi::Error; + + fn try_from(value: Config) -> std::result::Result { + let default = CfgEnv::default(); + let chain_id = value + .chain_id + .map_or(Ok(default.chain_id), |chain_id| chain_id.try_cast())?; + + let spec_id = value + .spec_id + .map_or(default.spec_id, |spec_id| spec_id.into()); + + let limit_contract_code_size = value.limit_contract_code_size.map_or(Ok(None), |size| { + // TODO: the lossless check in get_u64 is broken: https://github.com/napi-rs/napi-rs/pull/1348 + if let (false, size, _lossless) = size.get_u64() { + usize::try_from(size).map_or_else( + |e| Err(napi::Error::new(Status::InvalidArg, e.to_string())), + |size| Ok(Some(size)), + ) + } else { + Err(napi::Error::new( + Status::InvalidArg, + "BigInt cannot be larger than usize::MAX".to_owned(), + )) + } + })?; + + let disable_block_gas_limit = value + .disable_block_gas_limit + .unwrap_or(default.disable_block_gas_limit); + let disable_eip3607 = value.disable_eip3607.unwrap_or(default.disable_eip3607); + + Ok(Self { + chain_id, + spec_id, + limit_contract_code_size, + disable_block_gas_limit, + disable_eip3607, + ..default + }) + } +} diff --git a/crates/rethnet_evm_napi/src/lib.rs b/crates/rethnet_evm_napi/src/lib.rs index a67fbfb27c..3f7cf8c126 100644 --- a/crates/rethnet_evm_napi/src/lib.rs +++ b/crates/rethnet_evm_napi/src/lib.rs @@ -1,8 +1,15 @@ +//! NAPI bindings for the Rethnet EVM +#![warn(missing_docs)] + mod access_list; +mod account; mod block; mod blockchain; mod cast; +mod config; +mod log; mod receipt; +mod runtime; mod state; mod sync; mod threadsafe_function; @@ -10,88 +17,15 @@ mod trace; mod tracer; mod transaction; -use std::{fmt::Debug, str::FromStr}; +use std::str::FromStr; -use block::BlockConfig; -use blockchain::Blockchain; -use napi::{ - bindgen_prelude::{BigInt, Buffer, Either3, ToNapiValue}, - Either, Status, -}; -use napi_derive::napi; -use once_cell::sync::OnceCell; +use napi::Status; use rethnet_eth::Address; -use rethnet_evm::{state::StateError, AccountInfo, CfgEnv, TxEnv}; use secp256k1::{PublicKey, Secp256k1, SecretKey, SignOnly}; use sha3::{Digest, Keccak256}; -use state::StateManager; -use trace::Trace; -use tracer::Tracer; -use transaction::{CallOutput, CreateOutput, Transaction}; use crate::cast::TryCast; -struct Logger; - -unsafe impl Sync for Logger {} - -static LOGGER: OnceCell = OnceCell::new(); - -#[napi(object)] -pub struct Account { - /// Account balance - #[napi(readonly)] - pub balance: BigInt, - /// Account nonce - #[napi(readonly)] - pub nonce: BigInt, - /// 256-bit code hash - #[napi(readonly)] - pub code_hash: Buffer, - /// Optionally, byte code - #[napi(readonly)] - pub code: Option, -} - -#[napi(object)] -pub struct AccountData { - /// Account balance - #[napi(readonly)] - pub balance: BigInt, - /// Account nonce - #[napi(readonly)] - pub nonce: BigInt, - /// Optionally, byte code - #[napi(readonly)] - pub code: Option, -} - -impl Debug for Account { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Account") - .field("balance", &self.balance) - .field("nonce", &self.nonce) - .field("code_hash", &self.code_hash.as_ref()) - .finish() - } -} - -impl From for Account { - fn from(account_info: AccountInfo) -> Self { - Self { - balance: BigInt { - sign_bit: false, - words: account_info.balance.as_limbs().to_vec(), - }, - nonce: BigInt::from(account_info.nonce), - code_hash: Buffer::from(account_info.code_hash.as_bytes()), - code: account_info - .code - .map(|code| Buffer::from(&code.bytes()[..code.len()])), - } - } -} - fn private_key_to_address( context: &Secp256k1, private_key: String, @@ -116,388 +50,3 @@ fn public_key_to_address(public_key: PublicKey) -> Address { // Only take the lower 160 bits of the hash Address::from_slice(&hash[12..]) } - -#[napi(object)] -pub struct GenesisAccount { - /// Account private key - pub private_key: String, - /// Account balance - pub balance: BigInt, -} - -/// If not set, uses defaults from [`CfgEnv`]. -#[napi(object)] -pub struct Config { - pub chain_id: Option, - pub spec_id: Option, - pub limit_contract_code_size: Option, - pub disable_block_gas_limit: Option, - pub disable_eip3607: Option, -} - -impl TryFrom for CfgEnv { - type Error = napi::Error; - - fn try_from(value: Config) -> std::result::Result { - let default = CfgEnv::default(); - let chain_id = value - .chain_id - .map_or(Ok(default.chain_id), |chain_id| chain_id.try_cast())?; - - let spec_id = value - .spec_id - .map_or(default.spec_id, |spec_id| spec_id.into()); - - let limit_contract_code_size = value.limit_contract_code_size.map_or(Ok(None), |size| { - // TODO: the lossless check in get_u64 is broken: https://github.com/napi-rs/napi-rs/pull/1348 - if let (false, size, _lossless) = size.get_u64() { - usize::try_from(size).map_or_else( - |e| Err(napi::Error::new(Status::InvalidArg, e.to_string())), - |size| Ok(Some(size)), - ) - } else { - Err(napi::Error::new( - Status::InvalidArg, - "BigInt cannot be larger than usize::MAX".to_owned(), - )) - } - })?; - - let disable_block_gas_limit = value - .disable_block_gas_limit - .unwrap_or(default.disable_block_gas_limit); - let disable_eip3607 = value.disable_eip3607.unwrap_or(default.disable_eip3607); - - Ok(Self { - chain_id, - spec_id, - limit_contract_code_size, - disable_block_gas_limit, - disable_eip3607, - ..default - }) - } -} - -#[napi] -pub enum SpecId { - Frontier = 0, - FrontierThawing = 1, - Homestead = 2, - DaoFork = 3, - Tangerine = 4, - SpuriousDragon = 5, - Byzantium = 6, - Constantinople = 7, - Petersburg = 8, - Istanbul = 9, - MuirGlacier = 10, - Berlin = 11, - London = 12, - ArrowGlacier = 13, - GrayGlacier = 14, - Merge = 15, - Latest = 16, -} - -impl From for rethnet_evm::SpecId { - fn from(value: SpecId) -> Self { - match value { - SpecId::Frontier => rethnet_evm::SpecId::FRONTIER, - SpecId::FrontierThawing => rethnet_evm::SpecId::FRONTIER_THAWING, - SpecId::Homestead => rethnet_evm::SpecId::HOMESTEAD, - SpecId::DaoFork => rethnet_evm::SpecId::DAO_FORK, - SpecId::Tangerine => rethnet_evm::SpecId::TANGERINE, - SpecId::SpuriousDragon => rethnet_evm::SpecId::SPURIOUS_DRAGON, - SpecId::Byzantium => rethnet_evm::SpecId::BYZANTIUM, - SpecId::Constantinople => rethnet_evm::SpecId::CONSTANTINOPLE, - SpecId::Petersburg => rethnet_evm::SpecId::PETERSBURG, - SpecId::Istanbul => rethnet_evm::SpecId::ISTANBUL, - SpecId::MuirGlacier => rethnet_evm::SpecId::MUIR_GLACIER, - SpecId::Berlin => rethnet_evm::SpecId::BERLIN, - SpecId::London => rethnet_evm::SpecId::LONDON, - SpecId::ArrowGlacier => rethnet_evm::SpecId::ARROW_GLACIER, - SpecId::GrayGlacier => rethnet_evm::SpecId::GRAY_GLACIER, - SpecId::Merge => rethnet_evm::SpecId::MERGE, - SpecId::Latest => rethnet_evm::SpecId::LATEST, - } - } -} - -#[napi(object)] -pub struct Log { - pub address: Buffer, - pub topics: Vec, - pub data: Buffer, -} - -impl From for Log { - fn from(log: rethnet_evm::Log) -> Self { - let topics = log - .topics - .into_iter() - .map(|topic| Buffer::from(topic.as_bytes())) - .collect(); - - Self { - address: Buffer::from(log.address.as_bytes()), - topics, - data: Buffer::from(log.data.as_ref()), - } - } -} - -/// The possible reasons for a `ExitStatus::Success`. -#[napi] -pub enum SuccessReason { - Stop, - Return, - SelfDestruct, -} - -impl From for SuccessReason { - fn from(eval: rethnet_evm::Eval) -> Self { - match eval { - rethnet_evm::Eval::Stop => Self::Stop, - rethnet_evm::Eval::Return => Self::Return, - rethnet_evm::Eval::SelfDestruct => Self::SelfDestruct, - } - } -} - -#[napi(object)] -pub struct SuccessResult { - pub reason: SuccessReason, - pub gas_used: BigInt, - pub gas_refunded: BigInt, - pub logs: Vec, - pub output: Either, -} - -#[napi(object)] -pub struct RevertResult { - pub gas_used: BigInt, - pub output: Buffer, -} - -/// Indicates that the EVM has experienced an exceptional halt. This causes execution to -/// immediately end with all gas being consumed. -#[napi] -pub enum ExceptionalHalt { - OutOfGas, - OpcodeNotFound, - InvalidFEOpcode, - InvalidJump, - NotActivated, - StackUnderflow, - StackOverflow, - OutOfOffset, - CreateCollision, - OverflowPayment, - PrecompileError, - NonceOverflow, - /// Create init code size exceeds limit (runtime). - CreateContractSizeLimit, - /// Error on created contract that begins with EF - CreateContractStartingWithEF, -} - -impl From for ExceptionalHalt { - fn from(halt: rethnet_evm::Halt) -> Self { - match halt { - rethnet_evm::Halt::OutOfGas => ExceptionalHalt::OutOfGas, - rethnet_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, - rethnet_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode, - rethnet_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump, - rethnet_evm::Halt::NotActivated => ExceptionalHalt::NotActivated, - rethnet_evm::Halt::StackUnderflow => ExceptionalHalt::StackUnderflow, - rethnet_evm::Halt::StackOverflow => ExceptionalHalt::StackOverflow, - rethnet_evm::Halt::OutOfOffset => ExceptionalHalt::OutOfOffset, - rethnet_evm::Halt::CreateCollision => ExceptionalHalt::CreateCollision, - rethnet_evm::Halt::OverflowPayment => ExceptionalHalt::OverflowPayment, - rethnet_evm::Halt::PrecompileError => ExceptionalHalt::PrecompileError, - rethnet_evm::Halt::NonceOverflow => ExceptionalHalt::NonceOverflow, - rethnet_evm::Halt::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit, - rethnet_evm::Halt::CreateContractStartingWithEF => { - ExceptionalHalt::CreateContractStartingWithEF - } - } - } -} - -#[napi(object)] -pub struct HaltResult { - pub reason: ExceptionalHalt, - /// Halting will spend all the gas and will thus be equal to gas_limit. - pub gas_used: BigInt, -} - -#[napi(object)] -pub struct ExecutionResult { - pub result: Either3, - pub trace: Trace, -} - -impl From<(rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)> for ExecutionResult { - fn from((result, trace): (rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)) -> Self { - let result = match result { - rethnet_evm::ExecutionResult::Success { - reason, - gas_used, - gas_refunded, - logs, - output, - } => { - let logs = logs.into_iter().map(Log::from).collect(); - - Either3::A(SuccessResult { - reason: reason.into(), - gas_used: BigInt::from(gas_used), - gas_refunded: BigInt::from(gas_refunded), - logs, - output: match output { - rethnet_evm::Output::Call(return_value) => Either::A(CallOutput { - return_value: Buffer::from(return_value.as_ref()), - }), - rethnet_evm::Output::Create(return_value, address) => { - Either::B(CreateOutput { - return_value: Buffer::from(return_value.as_ref()), - address: address.map(|address| Buffer::from(address.as_bytes())), - }) - } - }, - }) - } - rethnet_evm::ExecutionResult::Revert { gas_used, output } => Either3::B(RevertResult { - gas_used: BigInt::from(gas_used), - output: Buffer::from(output.as_ref()), - }), - rethnet_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult { - reason: reason.into(), - gas_used: BigInt::from(gas_used), - }), - }; - - Self { - result, - trace: trace.into(), - } - } -} - -#[napi(object)] -pub struct TransactionResult { - pub exec_result: ExecutionResult, - pub state: serde_json::Value, -} - -impl - TryFrom<( - rethnet_evm::ExecutionResult, - rethnet_evm::State, - rethnet_evm::trace::Trace, - )> for TransactionResult -{ - type Error = napi::Error; - - fn try_from( - (result, state, trace): ( - rethnet_evm::ExecutionResult, - rethnet_evm::State, - rethnet_evm::trace::Trace, - ), - ) -> std::result::Result { - let exec_result = (result, trace).into(); - let state = serde_json::to_value(state)?; - - Ok(Self { exec_result, state }) - } -} - -#[napi] -pub struct Rethnet { - runtime: rethnet_evm::Rethnet, -} - -#[napi] -impl Rethnet { - #[napi(constructor)] - pub fn new( - blockchain: &Blockchain, - state_manager: &StateManager, - cfg: Config, - ) -> napi::Result { - let _logger = LOGGER.get_or_init(|| { - pretty_env_logger::init(); - Logger - }); - - let cfg = cfg.try_into()?; - - let runtime = rethnet_evm::Rethnet::new( - blockchain.as_inner().clone(), - state_manager.state.clone(), - cfg, - ); - - Ok(Self { runtime }) - } - - #[napi] - pub async fn dry_run( - &self, - transaction: Transaction, - block: BlockConfig, - tracer: Option<&Tracer>, - ) -> napi::Result { - let transaction = transaction.try_into()?; - let block = block.try_into()?; - - let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); - - self.runtime - .dry_run(transaction, block, inspector) - .await - .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? - .try_into() - } - - #[napi] - pub async fn guaranteed_dry_run( - &self, - transaction: Transaction, - block: BlockConfig, - tracer: Option<&Tracer>, - ) -> napi::Result { - let transaction = transaction.try_into()?; - let block = block.try_into()?; - - let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); - - self.runtime - .guaranteed_dry_run(transaction, block, inspector) - .await - .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? - .try_into() - } - - #[napi] - pub async fn run( - &self, - transaction: Transaction, - block: BlockConfig, - tracer: Option<&Tracer>, - ) -> napi::Result { - let transaction: TxEnv = transaction.try_into()?; - let block = block.try_into()?; - - let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); - - Ok(self - .runtime - .run(transaction, block, inspector) - .await - .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? - .into()) - } -} diff --git a/crates/rethnet_evm_napi/src/log.rs b/crates/rethnet_evm_napi/src/log.rs new file mode 100644 index 0000000000..321c2743b1 --- /dev/null +++ b/crates/rethnet_evm_napi/src/log.rs @@ -0,0 +1,26 @@ +use napi::bindgen_prelude::Buffer; +use napi_derive::napi; + +/// Ethereum log. +#[napi(object)] +pub struct Log { + pub address: Buffer, + pub topics: Vec, + pub data: Buffer, +} + +impl From for Log { + fn from(log: rethnet_evm::Log) -> Self { + let topics = log + .topics + .into_iter() + .map(|topic| Buffer::from(topic.as_bytes())) + .collect(); + + Self { + address: Buffer::from(log.address.as_bytes()), + topics, + data: Buffer::from(log.data.as_ref()), + } + } +} diff --git a/crates/rethnet_evm_napi/src/receipt.rs b/crates/rethnet_evm_napi/src/receipt.rs index e01724fdb1..fe1fcec363 100644 --- a/crates/rethnet_evm_napi/src/receipt.rs +++ b/crates/rethnet_evm_napi/src/receipt.rs @@ -1,7 +1,7 @@ use napi::bindgen_prelude::{BigInt, Buffer}; use napi_derive::napi; -use crate::Log; +use crate::log::Log; #[napi(object)] pub struct Receipt { diff --git a/crates/rethnet_evm_napi/src/runtime.rs b/crates/rethnet_evm_napi/src/runtime.rs new file mode 100644 index 0000000000..d481d81ef8 --- /dev/null +++ b/crates/rethnet_evm_napi/src/runtime.rs @@ -0,0 +1,110 @@ +use napi::Status; +use napi_derive::napi; +use once_cell::sync::OnceCell; +use rethnet_evm::{state::StateError, TxEnv}; + +use crate::{ + block::BlockConfig, + blockchain::Blockchain, + config::Config, + state::StateManager, + tracer::Tracer, + transaction::{ + result::{ExecutionResult, TransactionResult}, + Transaction, + }, +}; + +struct Logger; + +unsafe impl Sync for Logger {} + +static LOGGER: OnceCell = OnceCell::new(); + +#[napi] +pub struct Rethnet { + runtime: rethnet_evm::Rethnet, +} + +#[napi] +impl Rethnet { + #[napi(constructor)] + pub fn new( + blockchain: &Blockchain, + state_manager: &StateManager, + cfg: Config, + ) -> napi::Result { + let _logger = LOGGER.get_or_init(|| { + pretty_env_logger::init(); + Logger + }); + + let cfg = cfg.try_into()?; + + let runtime = rethnet_evm::Rethnet::new( + blockchain.as_inner().clone(), + state_manager.state.clone(), + cfg, + ); + + Ok(Self { runtime }) + } + + #[napi] + pub async fn dry_run( + &self, + transaction: Transaction, + block: BlockConfig, + tracer: Option<&Tracer>, + ) -> napi::Result { + let transaction = transaction.try_into()?; + let block = block.try_into()?; + + let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); + + self.runtime + .dry_run(transaction, block, inspector) + .await + .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? + .try_into() + } + + #[napi] + pub async fn guaranteed_dry_run( + &self, + transaction: Transaction, + block: BlockConfig, + tracer: Option<&Tracer>, + ) -> napi::Result { + let transaction = transaction.try_into()?; + let block = block.try_into()?; + + let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); + + self.runtime + .guaranteed_dry_run(transaction, block, inspector) + .await + .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? + .try_into() + } + + #[napi] + pub async fn run( + &self, + transaction: Transaction, + block: BlockConfig, + tracer: Option<&Tracer>, + ) -> napi::Result { + let transaction: TxEnv = transaction.try_into()?; + let block = block.try_into()?; + + let inspector = tracer.map(|tracer| tracer.as_dyn_inspector()); + + Ok(self + .runtime + .run(transaction, block, inspector) + .await + .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? + .into()) + } +} diff --git a/crates/rethnet_evm_napi/src/state.rs b/crates/rethnet_evm_napi/src/state.rs index 9147d8ec26..2207c9d828 100644 --- a/crates/rethnet_evm_napi/src/state.rs +++ b/crates/rethnet_evm_napi/src/state.rs @@ -13,10 +13,11 @@ use rethnet_evm::{ use secp256k1::Secp256k1; use crate::{ + account::{Account, AccountData}, private_key_to_address, sync::{await_promise, handle_error}, threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, - Account, AccountData, GenesisAccount, TryCast, + TryCast, }; struct ModifyAccountCall { @@ -26,6 +27,15 @@ struct ModifyAccountCall { pub sender: Sender)>>, } +/// An account that needs to be created during the genesis block. +#[napi(object)] +pub struct GenesisAccount { + /// Account private key + pub private_key: String, + /// Account balance + pub balance: BigInt, +} + #[napi] pub struct StateManager { pub(super) state: Arc>, diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 2c491f85ed..4533e360e7 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -9,9 +9,10 @@ use rethnet_eth::{Address, Bytes, U256}; use rethnet_evm::{Gas, InstructionResult, SuccessOrHalt, OPCODE_JUMPMAP}; use crate::{ + account::Account, sync::{await_void_promise, handle_error}, threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, - Account, ExecutionResult, + transaction::result::ExecutionResult, }; #[napi(object)] diff --git a/crates/rethnet_evm_napi/src/transaction.rs b/crates/rethnet_evm_napi/src/transaction.rs index 671b8e847c..7ae5005e90 100644 --- a/crates/rethnet_evm_napi/src/transaction.rs +++ b/crates/rethnet_evm_napi/src/transaction.rs @@ -1,3 +1,5 @@ +pub mod result; + use napi::bindgen_prelude::{BigInt, Buffer}; use napi_derive::napi; use rethnet_eth::{Address, Bytes, U256}; @@ -88,17 +90,3 @@ impl TryFrom for rethnet_evm::TxEnv { pub struct TransactionConfig { pub disable_balance_check: Option, } - -#[napi(object)] -pub struct CallOutput { - /// Return value - pub return_value: Buffer, -} - -#[napi(object)] -pub struct CreateOutput { - /// Return value - pub return_value: Buffer, - /// Optionally, a 160-bit address - pub address: Option, -} diff --git a/crates/rethnet_evm_napi/src/transaction/result.rs b/crates/rethnet_evm_napi/src/transaction/result.rs new file mode 100644 index 0000000000..1f5192f9a6 --- /dev/null +++ b/crates/rethnet_evm_napi/src/transaction/result.rs @@ -0,0 +1,205 @@ +use napi::{ + bindgen_prelude::{BigInt, Buffer, Either3, ToNapiValue}, + Either, +}; +use napi_derive::napi; + +use crate::{log::Log, trace::Trace}; + +/// The possible reasons for successful termination of the EVM. +#[napi] +pub enum SuccessReason { + /// The opcode `STOP` was called + Stop, + /// The opcode `RETURN` was called + Return, + /// The opcode `SELFDESTRUCT` was called + SelfDestruct, +} + +impl From for SuccessReason { + fn from(eval: rethnet_evm::Eval) -> Self { + match eval { + rethnet_evm::Eval::Stop => Self::Stop, + rethnet_evm::Eval::Return => Self::Return, + rethnet_evm::Eval::SelfDestruct => Self::SelfDestruct, + } + } +} + +#[napi(object)] +pub struct CallOutput { + /// Return value + pub return_value: Buffer, +} + +#[napi(object)] +pub struct CreateOutput { + /// Return value + pub return_value: Buffer, + /// Optionally, a 160-bit address + pub address: Option, +} + +/// The result when the EVM terminates successfully. +#[napi(object)] +pub struct SuccessResult { + /// The reason for termination + pub reason: SuccessReason, + /// The amount of gas used + pub gas_used: BigInt, + /// The amount of gas refunded + pub gas_refunded: BigInt, + /// The logs + pub logs: Vec, + /// The transaction output + pub output: Either, +} + +/// The result when the EVM terminates due to a revert. +#[napi(object)] +pub struct RevertResult { + /// The amount of gas used + pub gas_used: BigInt, + /// The transaction output + pub output: Buffer, +} + +/// Indicates that the EVM has experienced an exceptional halt. This causes execution to +/// immediately end with all gas being consumed. +#[napi] +pub enum ExceptionalHalt { + OutOfGas, + OpcodeNotFound, + InvalidFEOpcode, + InvalidJump, + NotActivated, + StackUnderflow, + StackOverflow, + OutOfOffset, + CreateCollision, + OverflowPayment, + PrecompileError, + NonceOverflow, + /// Create init code size exceeds limit (runtime). + CreateContractSizeLimit, + /// Error on created contract that begins with EF + CreateContractStartingWithEF, +} + +impl From for ExceptionalHalt { + fn from(halt: rethnet_evm::Halt) -> Self { + match halt { + rethnet_evm::Halt::OutOfGas => ExceptionalHalt::OutOfGas, + rethnet_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, + rethnet_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode, + rethnet_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump, + rethnet_evm::Halt::NotActivated => ExceptionalHalt::NotActivated, + rethnet_evm::Halt::StackUnderflow => ExceptionalHalt::StackUnderflow, + rethnet_evm::Halt::StackOverflow => ExceptionalHalt::StackOverflow, + rethnet_evm::Halt::OutOfOffset => ExceptionalHalt::OutOfOffset, + rethnet_evm::Halt::CreateCollision => ExceptionalHalt::CreateCollision, + rethnet_evm::Halt::OverflowPayment => ExceptionalHalt::OverflowPayment, + rethnet_evm::Halt::PrecompileError => ExceptionalHalt::PrecompileError, + rethnet_evm::Halt::NonceOverflow => ExceptionalHalt::NonceOverflow, + rethnet_evm::Halt::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit, + rethnet_evm::Halt::CreateContractStartingWithEF => { + ExceptionalHalt::CreateContractStartingWithEF + } + } + } +} + +/// The result when the EVM terminates due to an exceptional halt. +#[napi(object)] +pub struct HaltResult { + /// The exceptional halt that occurred + pub reason: ExceptionalHalt, + /// Halting will spend all the gas and will thus be equal to the specified gas limit + pub gas_used: BigInt, +} + +/// The result of executing a transaction. +#[napi(object)] +pub struct ExecutionResult { + /// The transaction result + pub result: Either3, + /// The transaction trace + pub trace: Trace, +} + +impl From<(rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)> for ExecutionResult { + fn from((result, trace): (rethnet_evm::ExecutionResult, rethnet_evm::trace::Trace)) -> Self { + let result = match result { + rethnet_evm::ExecutionResult::Success { + reason, + gas_used, + gas_refunded, + logs, + output, + } => { + let logs = logs.into_iter().map(Log::from).collect(); + + Either3::A(SuccessResult { + reason: reason.into(), + gas_used: BigInt::from(gas_used), + gas_refunded: BigInt::from(gas_refunded), + logs, + output: match output { + rethnet_evm::Output::Call(return_value) => Either::A(CallOutput { + return_value: Buffer::from(return_value.as_ref()), + }), + rethnet_evm::Output::Create(return_value, address) => { + Either::B(CreateOutput { + return_value: Buffer::from(return_value.as_ref()), + address: address.map(|address| Buffer::from(address.as_bytes())), + }) + } + }, + }) + } + rethnet_evm::ExecutionResult::Revert { gas_used, output } => Either3::B(RevertResult { + gas_used: BigInt::from(gas_used), + output: Buffer::from(output.as_ref()), + }), + rethnet_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult { + reason: reason.into(), + gas_used: BigInt::from(gas_used), + }), + }; + + Self { + result, + trace: trace.into(), + } + } +} + +#[napi(object)] +pub struct TransactionResult { + pub exec_result: ExecutionResult, + pub state: serde_json::Value, +} + +impl + TryFrom<( + rethnet_evm::ExecutionResult, + rethnet_evm::State, + rethnet_evm::trace::Trace, + )> for TransactionResult +{ + type Error = napi::Error; + + fn try_from( + (result, state, trace): ( + rethnet_evm::ExecutionResult, + rethnet_evm::State, + rethnet_evm::trace::Trace, + ), + ) -> std::result::Result { + let exec_result = (result, trace).into(); + let state = serde_json::to_value(state)?; + + Ok(Self { exec_result, state }) + } +} From be2eb4957ce9838fccb5c2c3704fe6f6592fc7ed Mon Sep 17 00:00:00 2001 From: Wodann Date: Sat, 28 Jan 2023 13:36:39 -0300 Subject: [PATCH 21/31] docs: add documentation to blockchain, runtime, and state NAPI bindings --- crates/rethnet_evm/src/debug.rs | 4 ++-- crates/rethnet_evm_napi/src/blockchain.rs | 3 +++ crates/rethnet_evm_napi/src/lib.rs | 1 + crates/rethnet_evm_napi/src/runtime.rs | 5 +++++ crates/rethnet_evm_napi/src/state.rs | 14 ++++++++++++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/rethnet_evm/src/debug.rs b/crates/rethnet_evm/src/debug.rs index 4b8abaad9e..cf10ca7afa 100644 --- a/crates/rethnet_evm/src/debug.rs +++ b/crates/rethnet_evm/src/debug.rs @@ -13,7 +13,7 @@ pub trait StateDebug { /// Retrieves the storage root of the account at the specified address. fn account_storage_root(&mut self, address: &Address) -> Result, Self::Error>; - /// Inserts an account with the specified address. + /// Inserts the provided account at the specified address. fn insert_account( &mut self, address: Address, @@ -50,6 +50,6 @@ pub trait StateDebug { /// Makes a snapshot of the database that's retained until [`remove_snapshot`] is called. Returns the snapshot's identifier. fn make_snapshot(&mut self) -> B256; - /// Removes the snapshot corresponding to the specified id, if it exists. Returns whether a snapshot was removed. + /// Removes the snapshot corresponding to the specified state root, if it exists. Returns whether a snapshot was removed. fn remove_snapshot(&mut self, state_root: &B256) -> bool; } diff --git a/crates/rethnet_evm_napi/src/blockchain.rs b/crates/rethnet_evm_napi/src/blockchain.rs index 3df4ee10fb..4ddea105aa 100644 --- a/crates/rethnet_evm_napi/src/blockchain.rs +++ b/crates/rethnet_evm_napi/src/blockchain.rs @@ -14,12 +14,14 @@ use crate::{ use self::js_blockchain::{GetBlockHashCall, JsBlockchain}; +/// The Rethnet blockchain #[napi] pub struct Blockchain { inner: Arc>, } impl Blockchain { + /// Provides immutable access to the inner implementation. pub fn as_inner(&self) -> &Arc> { &self.inner } @@ -27,6 +29,7 @@ impl Blockchain { #[napi] impl Blockchain { + /// Constructs a new blockchain that queries the blockhash using a callback. #[napi(constructor)] pub fn new( env: Env, diff --git a/crates/rethnet_evm_napi/src/lib.rs b/crates/rethnet_evm_napi/src/lib.rs index 3f7cf8c126..68a7db8099 100644 --- a/crates/rethnet_evm_napi/src/lib.rs +++ b/crates/rethnet_evm_napi/src/lib.rs @@ -9,6 +9,7 @@ mod cast; mod config; mod log; mod receipt; +/// Rethnet runtime for executing individual transactions mod runtime; mod state; mod sync; diff --git a/crates/rethnet_evm_napi/src/runtime.rs b/crates/rethnet_evm_napi/src/runtime.rs index d481d81ef8..aa677ea5fd 100644 --- a/crates/rethnet_evm_napi/src/runtime.rs +++ b/crates/rethnet_evm_napi/src/runtime.rs @@ -21,6 +21,7 @@ unsafe impl Sync for Logger {} static LOGGER: OnceCell = OnceCell::new(); +/// The Rethnet runtime, which can execute individual transactions. #[napi] pub struct Rethnet { runtime: rethnet_evm::Rethnet, @@ -28,6 +29,7 @@ pub struct Rethnet { #[napi] impl Rethnet { + /// Constructs a `Rethnet` runtime. #[napi(constructor)] pub fn new( blockchain: &Blockchain, @@ -50,6 +52,7 @@ impl Rethnet { Ok(Self { runtime }) } + /// Executes the provided transaction without changing state. #[napi] pub async fn dry_run( &self, @@ -69,6 +72,7 @@ impl Rethnet { .try_into() } + /// Executes the provided transaction without changing state, ignoring validation checks in the process. #[napi] pub async fn guaranteed_dry_run( &self, @@ -88,6 +92,7 @@ impl Rethnet { .try_into() } + /// Executes the provided transaction, changing state in the process. #[napi] pub async fn run( &self, diff --git a/crates/rethnet_evm_napi/src/state.rs b/crates/rethnet_evm_napi/src/state.rs index 2207c9d828..3dabc526d6 100644 --- a/crates/rethnet_evm_napi/src/state.rs +++ b/crates/rethnet_evm_napi/src/state.rs @@ -36,6 +36,7 @@ pub struct GenesisAccount { pub balance: BigInt, } +/// The Rethnet state #[napi] pub struct StateManager { pub(super) state: Arc>, @@ -43,11 +44,13 @@ pub struct StateManager { #[napi] impl StateManager { + /// Constructs a [`StateManager`] with an empty state. #[napi(constructor)] pub fn new() -> napi::Result { Self::with_accounts(HashMap::default()) } + /// Constructs a [`StateManager`] with the provided accounts present in the genesis state. #[napi(factory)] pub fn with_genesis_accounts(accounts: Vec) -> napi::Result { let context = Secp256k1::signing_only(); @@ -97,6 +100,7 @@ impl StateManager { }) } + /// Creates a state checkpoint that can be reverted to using [`revert`]. #[napi] pub async fn checkpoint(&self) -> napi::Result<()> { self.state @@ -105,6 +109,7 @@ impl StateManager { .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) } + /// Reverts to the previous checkpoint, created using [`checkpoint`]. #[napi] pub async fn revert(&self) -> napi::Result<()> { self.state @@ -113,6 +118,7 @@ impl StateManager { .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) } + /// Retrieves the account corresponding to the specified address. #[napi] pub async fn get_account_by_address(&self, address: Buffer) -> napi::Result> { let address = Address::from_slice(&address); @@ -134,6 +140,7 @@ impl StateManager { ) } + /// Retrieves the storage slot at the specified address and index. #[napi] pub async fn get_account_storage_slot( &self, @@ -167,6 +174,7 @@ impl StateManager { ) } + /// Retrieves the storage root of the database. #[napi] pub async fn get_state_root(&self) -> napi::Result { self.state.state_root().await.map_or_else( @@ -175,6 +183,7 @@ impl StateManager { ) } + /// Inserts the provided account at the specified address. #[napi] pub async fn insert_account(&self, address: Buffer, account: Account) -> napi::Result<()> { let address = Address::from_slice(&address); @@ -186,6 +195,7 @@ impl StateManager { .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) } + /// Makes a snapshot of the database that's retained until [`removeSnapshot`] is called. Returns the snapshot's identifier. #[napi] pub async fn make_snapshot(&self) -> Buffer { >::as_ref(&self.state.make_snapshot().await).into() @@ -282,6 +292,7 @@ impl StateManager { Ok(promise) } + /// Removes and returns the account at the specified address, if it exists. #[napi] pub async fn remove_account(&self, address: Buffer) -> napi::Result> { let address = Address::from_slice(&address); @@ -292,6 +303,7 @@ impl StateManager { ) } + /// Removes the snapshot corresponding to the specified state root, if it exists. Returns whether a snapshot was removed. #[napi] pub async fn remove_snapshot(&self, state_root: Buffer) -> bool { let state_root = B256::from_slice(&state_root); @@ -299,6 +311,7 @@ impl StateManager { self.state.remove_snapshot(state_root).await } + /// Sets the storage slot at the specified address and index to the provided value. #[napi] pub async fn set_account_storage_slot( &self, @@ -316,6 +329,7 @@ impl StateManager { .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string())) } + /// Reverts the state to match the specified state root. #[napi] pub async fn set_state_root(&self, state_root: Buffer) -> napi::Result<()> { let state_root = B256::from_slice(&state_root); From 18c275c1bb0782a6b6a56f3a153d80826faf4be5 Mon Sep 17 00:00:00 2001 From: Wodann Date: Tue, 7 Feb 2023 00:45:33 -0600 Subject: [PATCH 22/31] WIP: fix stack traces --- crates/rethnet_eth/Cargo.toml | 4 +- crates/rethnet_evm/Cargo.toml | 4 +- crates/rethnet_evm/src/lib.rs | 4 +- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 394 ++++++++++-------- .../src/transaction/result.rs | 4 + .../hardhat-network/provider/vm/dual.ts | 181 ++++++-- .../hardhat-network/provider/vm/ethereumjs.ts | 7 +- .../hardhat-network/stack-traces/model.ts | 61 +++ .../hardhat-network/stack-traces/vm-tracer.ts | 3 - 9 files changed, 453 insertions(+), 209 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index 598583a1fd..0237653745 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,8 +14,8 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -# revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "72355f4", version = "3.0", default-features = false } -revm-primitives = { path = "../../../revm/crates/primitives", version = "3.0", default-features = false } +revm-primitives = { git = "https://github.com/wodann/revm", rev = "13d2f20", version = "1.0", default-features = false } +# revm-primitives = { path = "../../../revm/crates/primitives", version = "1.0", default-features = false } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"] } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index a98595ded0..d301c1c737 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -10,8 +10,8 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth", features = ["serde"] } -# revm = { git = "https://github.com/bluealloy/revm", rev = "72355f4", version = "2.3", default-features = false, features = ["dev", "serde", "std"] } -revm = { path = "../../../revm/crates/revm", version = "2.3", default-features = false, features = ["dev", "serde", "std"] } +revm = { git = "https://github.com/wodann/revm", rev = "13d2f20", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } +# revm = { path = "../../../revm/crates/revm", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } signature = { version = "1.6.4", default-features = false, features = ["std"] } diff --git a/crates/rethnet_evm/src/lib.rs b/crates/rethnet_evm/src/lib.rs index b363905d79..574ab0859d 100644 --- a/crates/rethnet_evm/src/lib.rs +++ b/crates/rethnet_evm/src/lib.rs @@ -12,8 +12,8 @@ pub use revm::{ DatabaseComponents, State as StateMut, StateRef, }, interpreter::{ - instruction_result::SuccessOrHalt, CallInputs, CreateInputs, Gas, InstructionResult, - Interpreter, OPCODE_JUMPMAP, + instruction_result::SuccessOrHalt, opcode, return_revert, CallInputs, CreateInputs, Gas, + InstructionResult, Interpreter, OPCODE_JUMPMAP, }, primitives::*, EVMData, Inspector, diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 9b7125d806..c9fd97aafa 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -1,4 +1,7 @@ -use std::sync::mpsc::{channel, Sender}; +use std::{ + fmt::Debug, + sync::mpsc::{channel, Sender}, +}; use napi::{ bindgen_prelude::{BigInt, Buffer}, @@ -6,7 +9,9 @@ use napi::{ }; use napi_derive::napi; use rethnet_eth::{Address, Bytes, U256}; -use rethnet_evm::{Gas, InstructionResult, SuccessOrHalt, OPCODE_JUMPMAP}; +use rethnet_evm::{ + opcode, return_revert, Bytecode, Gas, InstructionResult, SuccessOrHalt, OPCODE_JUMPMAP, +}; use crate::{ account::Account, @@ -100,13 +105,19 @@ pub struct TracingCallbacks { pub after_message: JsFunction, } -pub struct BeforeMessageHandlerCall { +#[derive(Clone)] +struct BeforeMessage { pub depth: usize, pub to: Option
, pub data: Bytes, pub value: U256, pub code_address: Option
, - pub sender: Sender>, + pub code: Option, +} + +struct BeforeMessageHandlerCall { + message: BeforeMessage, + sender: Sender>, } pub struct StepHandlerCall { @@ -116,18 +127,18 @@ pub struct StepHandlerCall { pub pc: u64, /// The executed op code pub opcode: u8, - /// The return value of the step - pub return_value: InstructionResult, - /// The amount of gas that was used by the step - pub gas_cost: u64, - /// The amount of gas that was refunded by the step - pub gas_refunded: i64, - /// The amount of gas left - pub gas_left: u64, - /// The stack - pub stack: Vec, - /// The memory - pub memory: Bytes, + // /// The return value of the step + // pub return_value: InstructionResult, + // /// The amount of gas that was used by the step + // pub gas_cost: u64, + // /// The amount of gas that was refunded by the step + // pub gas_refunded: i64, + // /// The amount of gas left + // pub gas_left: u64, + // /// The stack + // pub stack: Vec, + // /// The memory + // pub memory: Bytes, /// The contract being executed pub contract: rethnet_evm::AccountInfo, /// The address of the contract @@ -142,20 +153,12 @@ pub struct AfterMessageHandlerCall { pub sender: Sender>, } -#[derive(Clone)] -struct StepData { - depth: usize, - pc: u64, - opcode: u8, - gas: Gas, -} - #[derive(Clone)] pub struct JsTracer { before_message_fn: ThreadsafeFunction, step_fn: ThreadsafeFunction, after_message_fn: ThreadsafeFunction, - pre_steps: Vec, + pending_before: Option, } impl JsTracer { @@ -171,10 +174,11 @@ impl JsTracer { let mut tracing_message = ctx.env.create_object()?; ctx.env - .create_int64(ctx.value.depth as i64) + .create_int64(ctx.value.message.depth as i64) .and_then(|depth| tracing_message.set_named_property("depth", depth))?; ctx.value + .message .to .as_ref() .map_or_else( @@ -188,14 +192,15 @@ impl JsTracer { .and_then(|to| tracing_message.set_named_property("to", to))?; ctx.env - .create_buffer_copy(&ctx.value.data) + .create_buffer_copy(&ctx.value.message.data) .and_then(|data| tracing_message.set_named_property("data", data.into_raw()))?; ctx.env - .create_bigint_from_words(false, ctx.value.value.as_limbs().to_vec()) + .create_bigint_from_words(false, ctx.value.message.value.as_limbs().to_vec()) .and_then(|value| tracing_message.set_named_property("value", value))?; ctx.value + .message .code_address .as_ref() .map_or_else( @@ -210,6 +215,22 @@ impl JsTracer { tracing_message.set_named_property("codeAddress", code_address) })?; + ctx.value + .message + .code + .as_ref() + .map_or_else( + || ctx.env.get_undefined().map(JsUndefined::into_unknown), + |code| { + ctx.env + .create_buffer_copy(&code.bytes()[..code.len()]) + .map(JsBufferValue::into_unknown) + }, + ) + .and_then(|code_address| { + tracing_message.set_named_property("code", code_address) + })?; + let next = ctx.env.create_object()?; let promise = ctx.callback.call(None, &[tracing_message, next])?; @@ -240,47 +261,47 @@ impl JsTracer { .create_string(OPCODE_JUMPMAP[usize::from(ctx.value.opcode)].unwrap_or("")) .and_then(|opcode| tracing_step.set_named_property("opcode", opcode))?; - ctx.env - .create_uint32((ctx.value.return_value as u8).into()) - .and_then(|return_value| { - tracing_step.set_named_property("returnValue", return_value) - })?; - - ctx.env - .create_bigint_from_u64(ctx.value.gas_cost) - .and_then(|gas_cost| tracing_step.set_named_property("gasCost", gas_cost))?; - - ctx.env - .create_bigint_from_i64(ctx.value.gas_refunded) - .and_then(|gas_refunded| { - tracing_step.set_named_property("gasRefunded", gas_refunded) - })?; - - ctx.env - .create_bigint_from_u64(ctx.value.gas_left) - .and_then(|gas_left| tracing_step.set_named_property("gasLeft", gas_left))?; - - let mut stack = - ctx.env - .create_array(u32::try_from(ctx.value.stack.len()).map_err(|e| { - napi::Error::new(Status::GenericFailure, e.to_string()) - })?)?; - - for value in ctx.value.stack { - ctx.env - .create_bigint_from_words(false, value.as_limbs().to_vec()) - .and_then(|value| stack.insert(value))?; - } - - stack - .coerce_to_object() - .and_then(|stack| tracing_step.set_named_property("stack", stack))?; - - ctx.env - .create_buffer_copy(&ctx.value.memory) - .and_then(|memory| { - tracing_step.set_named_property("memory", memory.into_raw()) - })?; + // ctx.env + // .create_uint32((ctx.value.return_value as u8).into()) + // .and_then(|return_value| { + // tracing_step.set_named_property("returnValue", return_value) + // })?; + + // ctx.env + // .create_bigint_from_u64(ctx.value.gas_cost) + // .and_then(|gas_cost| tracing_step.set_named_property("gasCost", gas_cost))?; + + // ctx.env + // .create_bigint_from_i64(ctx.value.gas_refunded) + // .and_then(|gas_refunded| { + // tracing_step.set_named_property("gasRefunded", gas_refunded) + // })?; + + // ctx.env + // .create_bigint_from_u64(ctx.value.gas_left) + // .and_then(|gas_left| tracing_step.set_named_property("gasLeft", gas_left))?; + + // let mut stack = + // ctx.env + // .create_array(u32::try_from(ctx.value.stack.len()).map_err(|e| { + // napi::Error::new(Status::GenericFailure, e.to_string()) + // })?)?; + + // for value in ctx.value.stack { + // ctx.env + // .create_bigint_from_words(false, value.as_limbs().to_vec()) + // .and_then(|value| stack.insert(value))?; + // } + + // stack + // .coerce_to_object() + // .and_then(|stack| tracing_step.set_named_property("stack", stack))?; + + // ctx.env + // .create_buffer_copy(&ctx.value.memory) + // .and_then(|memory| { + // tracing_step.set_named_property("memory", memory.into_raw()) + // })?; let mut contract = ctx.env.create_object()?; @@ -292,7 +313,7 @@ impl JsTracer { contract.set_named_property("nonce", nonce)?; ctx.env - .create_buffer_copy(&ctx.value.memory) + .create_buffer_copy(ctx.value.contract.code_hash) .and_then(|code_hash| { contract.set_named_property("codeHash", code_hash.into_unknown()) })?; @@ -477,14 +498,32 @@ impl JsTracer { before_message_fn, step_fn, after_message_fn, - pre_steps: Vec::new(), + pending_before: None, }) } + + fn validate_before_message(&mut self) { + if let Some(message) = self.pending_before.take() { + let (sender, receiver) = channel(); + + let status = self.before_message_fn.call( + BeforeMessageHandlerCall { message, sender }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); + + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + } + } } impl rethnet_evm::Inspector for JsTracer where D: rethnet_evm::Database, + D::Error: Debug, { fn call( &mut self, @@ -492,25 +531,34 @@ where inputs: &mut rethnet_evm::CallInputs, _is_static: bool, ) -> (InstructionResult, Gas, rethnet_eth::Bytes) { - let (sender, receiver) = channel(); - - let status = self.before_message_fn.call( - BeforeMessageHandlerCall { - depth: data.journaled_state.depth, - to: Some(inputs.context.address), - data: inputs.input.clone(), - value: inputs.transfer.value, - code_address: Some(inputs.context.code_address), - sender, - }, - ThreadsafeFunctionCallMode::Blocking, - ); - assert_eq!(status, Status::Ok); + self.validate_before_message(); + + let code = data + .journaled_state + .state + .get(&inputs.context.code_address) + .map(|account| { + if let Some(code) = &account.info.code { + code.clone() + } else { + data.db.code_by_hash(account.info.code_hash).unwrap() + } + }) + .unwrap_or_else(|| { + let account = data.db.basic(inputs.context.code_address).unwrap().unwrap(); + account + .code + .unwrap_or_else(|| data.db.code_by_hash(account.code_hash).unwrap()) + }); - receiver - .recv() - .unwrap() - .expect("Failed call to BeforeMessageHandler"); + self.pending_before = Some(BeforeMessage { + depth: data.journaled_state.depth, + to: Some(inputs.context.address), + data: inputs.input.clone(), + value: inputs.transfer.value, + code_address: Some(inputs.context.code_address), + code: Some(code), + }); (InstructionResult::Continue, Gas::new(0), Bytes::default()) } @@ -524,39 +572,54 @@ where out: Bytes, _is_static: bool, ) -> (InstructionResult, Gas, Bytes) { + match ret { + return_revert!() if self.pending_before.is_some() => { + self.pending_before = None; + return (ret, remaining_gas, out); + } + _ => (), + } + + self.validate_before_message(); + + let ret = if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFund { + InstructionResult::Revert + } else { + ret + }; + let result = match ret.into() { - SuccessOrHalt::Success(reason) => Some(rethnet_evm::ExecutionResult::Success { + SuccessOrHalt::Success(reason) => rethnet_evm::ExecutionResult::Success { reason, gas_used: remaining_gas.spend(), gas_refunded: remaining_gas.refunded() as u64, logs: data.journaled_state.logs.clone(), output: rethnet_evm::Output::Call(out.clone()), - }), - SuccessOrHalt::Revert => Some(rethnet_evm::ExecutionResult::Revert { + }, + SuccessOrHalt::Revert => rethnet_evm::ExecutionResult::Revert { gas_used: remaining_gas.spend(), output: out.clone(), - }), - SuccessOrHalt::Halt(reason) => Some(rethnet_evm::ExecutionResult::Halt { + }, + SuccessOrHalt::Halt(reason) => rethnet_evm::ExecutionResult::Halt { reason, gas_used: remaining_gas.limit(), - }), - SuccessOrHalt::FatalExternalError | SuccessOrHalt::Internal => None, + }, + SuccessOrHalt::Internal => panic!("Internal error: {:?}", ret), + SuccessOrHalt::FatalExternalError => panic!("Fatal external error"), }; - if let Some(result) = result { - let (sender, receiver) = channel(); + let (sender, receiver) = channel(); - let status = self.after_message_fn.call( - AfterMessageHandlerCall { result, sender }, - ThreadsafeFunctionCallMode::Blocking, - ); - assert_eq!(status, Status::Ok); + let status = self.after_message_fn.call( + AfterMessageHandlerCall { result, sender }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); - receiver - .recv() - .unwrap() - .expect("Failed call to BeforeMessageHandler"); - } + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); (ret, remaining_gas, out) } @@ -566,25 +629,16 @@ where data: &mut rethnet_evm::EVMData<'_, D>, inputs: &mut rethnet_evm::CreateInputs, ) -> (InstructionResult, Option, Gas, Bytes) { - let (sender, receiver) = channel(); + self.validate_before_message(); - let status = self.before_message_fn.call( - BeforeMessageHandlerCall { - depth: data.journaled_state.depth, - to: None, - data: inputs.init_code.clone(), - value: inputs.value, - code_address: None, - sender, - }, - ThreadsafeFunctionCallMode::Blocking, - ); - assert_eq!(status, Status::Ok); - - receiver - .recv() - .unwrap() - .expect("Failed call to BeforeMessageHandler"); + self.pending_before = Some(BeforeMessage { + depth: data.journaled_state.depth, + to: None, + data: inputs.init_code.clone(), + value: inputs.value, + code_address: None, + code: None, + }); ( InstructionResult::Continue, @@ -603,6 +657,14 @@ where remaining_gas: Gas, out: Bytes, ) -> (InstructionResult, Option, Gas, Bytes) { + self.validate_before_message(); + + let ret = if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFund { + InstructionResult::Revert + } else { + ret + }; + let result = match ret.into() { SuccessOrHalt::Success(reason) => Some(rethnet_evm::ExecutionResult::Success { reason, @@ -646,49 +708,32 @@ where data: &mut rethnet_evm::EVMData<'_, D>, _is_static: bool, ) -> InstructionResult { - self.pre_steps.push(StepData { - depth: data.journaled_state.depth, - pc: interp.program_counter() as u64, - opcode: interp.current_opcode(), - gas: *interp.gas(), - }); - - InstructionResult::Continue - } + if interp.current_opcode() == opcode::STOP { + self.pending_before = None; + } else { + self.validate_before_message(); + } - fn step_end( - &mut self, - interp: &mut rethnet_evm::Interpreter, - data: &mut rethnet_evm::EVMData<'_, D>, - _is_static: bool, - _eval: InstructionResult, - ) -> InstructionResult { - // TODO: temporary fix - let StepData { - depth, - pc, - opcode, - gas: pre_step_gas, - } = self - .pre_steps - .pop() - .expect("At least one pre-step should exist"); - - let post_step_gas = interp.gas(); + // self.pre_steps.push(StepData { + // depth: data.journaled_state.depth, + // pc: interp.program_counter() as u64, + // opcode: interp.current_opcode(), + // gas: *interp.gas(), + // }); let (sender, receiver) = channel(); let status = self.step_fn.call( StepHandlerCall { - depth, - pc, - opcode, - return_value: interp.instruction_result, - gas_cost: post_step_gas.spend() - pre_step_gas.spend(), - gas_refunded: post_step_gas.refunded() - pre_step_gas.refunded(), - gas_left: interp.gas().remaining(), - stack: interp.stack().data().clone(), - memory: Bytes::copy_from_slice(interp.memory.data().as_slice()), + depth: data.journaled_state.depth, + pc: interp.program_counter() as u64, + opcode: interp.current_opcode(), + // return_value: interp.instruction_result, + // gas_cost: post_step_gas.spend() - pre_step_gas.spend(), + // gas_refunded: post_step_gas.refunded() - pre_step_gas.refunded(), + // gas_left: interp.gas().remaining(), + // stack: interp.stack().data().clone(), + // memory: Bytes::copy_from_slice(interp.memory.data().as_slice()), contract: data .journaled_state .account(interp.contract.address) @@ -708,4 +753,27 @@ where InstructionResult::Continue } + + // fn step_end( + // &mut self, + // interp: &mut rethnet_evm::Interpreter, + // _data: &mut rethnet_evm::EVMData<'_, D>, + // _is_static: bool, + // _eval: InstructionResult, + // ) -> InstructionResult { + // // TODO: temporary fix + // let StepData { + // depth, + // pc, + // opcode, + // gas: pre_step_gas, + // } = self + // .pre_steps + // .pop() + // .expect("At least one pre-step should exist"); + + // let post_step_gas = interp.gas(); + + // InstructionResult::Continue + // } } diff --git a/crates/rethnet_evm_napi/src/transaction/result.rs b/crates/rethnet_evm_napi/src/transaction/result.rs index 1f5192f9a6..04e9301ead 100644 --- a/crates/rethnet_evm_napi/src/transaction/result.rs +++ b/crates/rethnet_evm_napi/src/transaction/result.rs @@ -71,6 +71,7 @@ pub struct RevertResult { pub enum ExceptionalHalt { OutOfGas, OpcodeNotFound, + // CallNotAllowedInsideStatic, InvalidFEOpcode, InvalidJump, NotActivated, @@ -92,6 +93,9 @@ impl From for ExceptionalHalt { match halt { rethnet_evm::Halt::OutOfGas => ExceptionalHalt::OutOfGas, rethnet_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, + // rethnet_evm::Halt::CallNotAllowedInsideStatic => { + // ExceptionalHalt::CallNotAllowedInsideStatic + // } rethnet_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode, rethnet_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump, rethnet_evm::Halt::NotActivated => ExceptionalHalt::NotActivated, diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index 51d9e4a426..80ea092e0f 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -10,7 +10,11 @@ import { import { assertHardhatInvariant } from "../../../core/errors"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; -import { MessageTrace } from "../../stack-traces/message-trace"; +import { + isEvmStep, + isPrecompileTrace, + MessageTrace, +} from "../../stack-traces/message-trace"; import { VMTracer } from "../../stack-traces/vm-tracer"; import { NodeConfig } from "../node-types"; import { RpcDebugTraceOutput } from "../output"; @@ -333,38 +337,7 @@ export class DualModeAdapter implements VMAdapter { ); } - // both traces are defined - if (ethereumJSTrace.depth !== rethnetTrace.depth) { - throw new Error( - `Different depth: ${ethereumJSTrace.depth} !== ${rethnetTrace.depth}` - ); - } - - if (ethereumJSTrace.exit.kind !== rethnetTrace.exit.kind) { - throw new Error( - `Different exit: ${ethereumJSTrace.exit.kind} !== ${rethnetTrace.exit.kind}` - ); - } - - if (ethereumJSTrace.gasUsed !== rethnetTrace.gasUsed) { - console.log("ethereumjs:", ethereumJSTrace); - console.log("rethnet:", rethnetTrace); - throw new Error( - `Different gasUsed: ${ethereumJSTrace.gasUsed} !== ${rethnetTrace.gasUsed}` - ); - } - - if (!ethereumJSTrace.returnData.equals(rethnetTrace.returnData)) { - throw new Error( - `Different returnData: ${ethereumJSTrace.returnData} !== ${rethnetTrace.returnData}` - ); - } - - if (ethereumJSTrace.value !== rethnetTrace.value) { - throw new Error( - `Different value: ${ethereumJSTrace.value} !== ${rethnetTrace.value}` - ); - } + assertEqualTraces(ethereumJSTrace, rethnetTrace); } if (ethereumJSError === undefined) { @@ -733,3 +706,145 @@ function assertEqualAccounts( // throw new Error("Different storageRoot"); } } + +function assertEqualTraces( + ethereumJSTrace: MessageTrace, + rethnetTrace: MessageTrace +) { + // both traces are defined + if (ethereumJSTrace.depth !== rethnetTrace.depth) { + throw new Error( + `Different depth: ${ethereumJSTrace.depth} !== ${rethnetTrace.depth}` + ); + } + + if (ethereumJSTrace.exit.kind !== rethnetTrace.exit.kind) { + throw new Error( + `Different exit: ${ethereumJSTrace.exit.kind} !== ${rethnetTrace.exit.kind}` + ); + } + + if (ethereumJSTrace.gasUsed !== rethnetTrace.gasUsed) { + throw new Error( + `Different gasUsed: ${ethereumJSTrace.gasUsed} !== ${rethnetTrace.gasUsed}` + ); + } + + if (!ethereumJSTrace.returnData.equals(rethnetTrace.returnData)) { + throw new Error( + `Different returnData: ${ethereumJSTrace.returnData} !== ${rethnetTrace.returnData}` + ); + } + + if (ethereumJSTrace.value !== rethnetTrace.value) { + throw new Error( + `Different value: ${ethereumJSTrace.value} !== ${rethnetTrace.value}` + ); + } + + if (isPrecompileTrace(ethereumJSTrace)) { + if (!isPrecompileTrace(rethnetTrace)) { + throw new Error( + `ethereumJSTrace is a precompiled trace but rethnetTrace is not` + ); + } + + // Both traces are precompile traces + if (ethereumJSTrace.precompile !== rethnetTrace.precompile) { + throw new Error( + `Different precompile: ${ethereumJSTrace.precompile} !== ${rethnetTrace.precompile}` + ); + } + + if (!ethereumJSTrace.calldata.equals(rethnetTrace.calldata)) { + throw new Error( + `Different calldata: ${ethereumJSTrace.calldata} !== ${rethnetTrace.calldata}` + ); + } + } else { + if (isPrecompileTrace(rethnetTrace)) { + throw new Error( + `ethereumJSTrace is a precompiled trace but ethereumJSTrace is not` + ); + } + + // Both traces are NOT precompile traces + if (!ethereumJSTrace.code.equals(rethnetTrace.code)) { + console.log("ethereumjs:", ethereumJSTrace); + console.log("rethnet:", rethnetTrace); + throw new Error( + `Different code: ${ethereumJSTrace.code.toString( + "hex" + )} !== ${rethnetTrace.code.toString("hex")}` + ); + } + + if (ethereumJSTrace.steps.length !== rethnetTrace.steps.length) { + throw new Error( + `Different steps length: ${ethereumJSTrace.steps.length} !== ${rethnetTrace.steps.length}` + ); + } + + for (let stepIdx = 0; stepIdx < ethereumJSTrace.steps.length; stepIdx++) { + const ethereumJSStep = ethereumJSTrace.steps[stepIdx]; + const rethnetStep = rethnetTrace.steps[stepIdx]; + + if (isEvmStep(ethereumJSStep)) { + // if (stepIdx >= rethnetTrace.steps.length) { + // console.log("code:", ethereumJSTrace.code); + // console.log(stepIdx); + // console.log(ethereumJSStep); + // console.log("opcode:", ethereumJSTrace.code[ethereumJSStep.pc]); + // continue; + // } + + if (!isEvmStep(rethnetStep)) { + throw new Error( + `ethereumJSStep '${stepIdx}' is an EVM step but rethnetStep '${stepIdx}' is not` + ); + } + + if (ethereumJSStep.pc !== rethnetStep.pc) { + throw new Error( + `Different step[${stepIdx}]: ${ethereumJSStep.pc} !== ${rethnetStep.pc}` + ); + } + } else { + if (isEvmStep(rethnetStep)) { + throw new Error( + `rethnetStep '${stepIdx}' is an EVM step but ethereumJSStep '${stepIdx}' is not` + ); + } + + assertEqualTraces(ethereumJSStep, rethnetStep); + } + } + + if (ethereumJSTrace.bytecode === undefined) { + if (rethnetTrace.bytecode !== undefined) { + throw new Error( + "ethereumJSTrace.bytecode is undefined but rethnetTrace.bytecode is defined" + ); + } + } else { + if (rethnetTrace.bytecode === undefined) { + throw new Error( + "ethereumJSTrace.bytecode is defined but rethnetTrace.bytecode is undefined" + ); + } + + // Both traces contain bytecode + if (!ethereumJSTrace.bytecode.equals(rethnetTrace.bytecode)) { + throw new Error( + `Different bytecode: ${ethereumJSTrace.bytecode} !== ${rethnetTrace.bytecode}` + ); + } + } + + if (ethereumJSTrace.numberOfSubtraces !== rethnetTrace.numberOfSubtraces) { + throw new Error( + `Different numberOfSubtraces: ${ethereumJSTrace.numberOfSubtraces} !== ${rethnetTrace.numberOfSubtraces}` + ); + } + } +} diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index b166df2531..4aea036571 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -542,10 +542,9 @@ export class EthereumJSAdapter implements VMAdapter { let executionResult; if (result.execResult.exceptionError === undefined) { - const reason = - result.execResult.selfdestruct === undefined - ? SuccessReason.Return - : SuccessReason.SelfDestruct; + const reason = !result.execResult.returnValue.equals(Buffer.from([])) + ? SuccessReason.Return + : SuccessReason.SelfDestruct; executionResult = { reason, diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/model.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/model.ts index c82a4788e6..0be42c1807 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/model.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/model.ts @@ -298,6 +298,49 @@ export class Instruction { public readonly pushData?: Buffer, public readonly location?: SourceLocation ) {} + + /** + * Checks equality with another Instruction. + */ + public equals(other: Instruction): boolean { + if (this.pc !== other.pc) { + return false; + } + + if (this.opcode !== other.opcode) { + return false; + } + + if (this.jumpType !== other.jumpType) { + return false; + } + + if (this.pushData !== undefined) { + if (other.pushData === undefined) { + return false; + } + + if (!this.pushData.equals(other.pushData)) { + return false; + } + } else if (other.pushData !== undefined) { + return false; + } + + if (this.location !== undefined) { + if (other.location === undefined) { + return false; + } + + if (!this.location.equals(other.location)) { + return false; + } + } else if (other.location !== undefined) { + return false; + } + + return true; + } } interface ImmutableReference { @@ -335,4 +378,22 @@ export class Bytecode { public hasInstruction(pc: number): boolean { return this._pcToInstruction.has(pc); } + + /** + * Checks equality with another Bytecode. + */ + public equals(other: Bytecode): boolean { + if (this._pcToInstruction.size !== other._pcToInstruction.size) { + return false; + } + + for (const [key, val] of this._pcToInstruction) { + const otherVal = other._pcToInstruction.get(key); + if (otherVal === undefined || !val.equals(otherVal)) { + return false; + } + } + + return true; + } } diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts index 141a983eeb..03145a65fa 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts @@ -150,7 +150,6 @@ export class VMTracer { this._messageTraces.push(trace); } catch (error) { - console.log("addBeforeMessage", error); // TODO delete if (this._throwErrors) { throw error; } else { @@ -177,7 +176,6 @@ export class VMTracer { trace.steps.push({ pc: Number(step.pc) }); } catch (error) { - console.log("addStep", error); // TODO delete if (this._throwErrors) { throw error; } else { @@ -219,7 +217,6 @@ export class VMTracer { this._messageTraces.pop(); } } catch (error) { - console.log("addAfterMessage", error); // TODO delete if (this._throwErrors) { throw error; } else { From faf7e5d7a5014e2252dd82b3c9318e9ac8288cb3 Mon Sep 17 00:00:00 2001 From: Wodann Date: Tue, 7 Feb 2023 00:45:57 -0600 Subject: [PATCH 23/31] fix: allow stack traces with no steps --- .../hardhat-network/stack-traces/error-inferrer.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/error-inferrer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/error-inferrer.ts index 2a64ea920f..573f9b1ed2 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/error-inferrer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/error-inferrer.ts @@ -557,6 +557,10 @@ export class ErrorInferrer { functionJumpdests: Instruction[], jumpedIntoFunction: boolean ): SolidityStackTrace | undefined { + if (trace.steps.length === 0) { + return; + } + const lastStep = trace.steps[trace.steps.length - 1]; if (!isEvmStep(lastStep)) { @@ -1144,6 +1148,10 @@ export class ErrorInferrer { } private _solidity063MaybeUnmappedRevert(trace: DecodedEvmMessageTrace) { + if (trace.steps.length === 0) { + return false; + } + const lastStep = trace.steps[trace.steps.length - 1]; if (!isEvmStep(lastStep)) { return false; From e6690e8e0f814025214f0465923701aca188a84f Mon Sep 17 00:00:00 2001 From: Wodann Date: Tue, 7 Feb 2023 10:13:49 -0600 Subject: [PATCH 24/31] fix: revert supposed fix --- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index c9fd97aafa..b54548aba8 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -708,12 +708,12 @@ where data: &mut rethnet_evm::EVMData<'_, D>, _is_static: bool, ) -> InstructionResult { - if interp.current_opcode() == opcode::STOP { - self.pending_before = None; - } else { - self.validate_before_message(); + if interp.current_opcode() == opcode::STOP && self.pending_before.is_some() { + return InstructionResult::Continue; } + self.validate_before_message(); + // self.pre_steps.push(StepData { // depth: data.journaled_state.depth, // pc: interp.program_counter() as u64, From f79c3cc4af85e68a6c29d1199e6df96540443e7c Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 8 Feb 2023 01:28:00 -0600 Subject: [PATCH 25/31] fix: miscellaneous --- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 75 ++++++++++--------- .../hardhat-network/provider/vm/dual.ts | 16 ++-- .../hardhat-network/provider/vm/ethereumjs.ts | 21 +++--- .../hardhat-network/provider/vm/exit.ts | 49 +++++++++--- .../hardhat-network/provider/vm/rethnet.ts | 2 +- .../hardhat-network/stack-traces/vm-tracer.ts | 6 +- .../internal/hardhat-network/provider/logs.ts | 10 +-- 7 files changed, 105 insertions(+), 74 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index b54548aba8..39cbfd158d 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -708,48 +708,51 @@ where data: &mut rethnet_evm::EVMData<'_, D>, _is_static: bool, ) -> InstructionResult { - if interp.current_opcode() == opcode::STOP && self.pending_before.is_some() { - return InstructionResult::Continue; - } + // Skip the step + let skip_step = self.pending_before.as_ref().map_or(false, |message| { + message.code.is_some() && interp.current_opcode() == opcode::STOP + }); self.validate_before_message(); - // self.pre_steps.push(StepData { - // depth: data.journaled_state.depth, - // pc: interp.program_counter() as u64, - // opcode: interp.current_opcode(), - // gas: *interp.gas(), - // }); + if !skip_step { + // self.pre_steps.push(StepData { + // depth: data.journaled_state.depth, + // pc: interp.program_counter() as u64, + // opcode: interp.current_opcode(), + // gas: *interp.gas(), + // }); - let (sender, receiver) = channel(); + let (sender, receiver) = channel(); - let status = self.step_fn.call( - StepHandlerCall { - depth: data.journaled_state.depth, - pc: interp.program_counter() as u64, - opcode: interp.current_opcode(), - // return_value: interp.instruction_result, - // gas_cost: post_step_gas.spend() - pre_step_gas.spend(), - // gas_refunded: post_step_gas.refunded() - pre_step_gas.refunded(), - // gas_left: interp.gas().remaining(), - // stack: interp.stack().data().clone(), - // memory: Bytes::copy_from_slice(interp.memory.data().as_slice()), - contract: data - .journaled_state - .account(interp.contract.address) - .info - .clone(), - contract_address: interp.contract().address, - sender, - }, - ThreadsafeFunctionCallMode::Blocking, - ); - assert_eq!(status, Status::Ok); + let status = self.step_fn.call( + StepHandlerCall { + depth: data.journaled_state.depth, + pc: interp.program_counter() as u64, + opcode: interp.current_opcode(), + // return_value: interp.instruction_result, + // gas_cost: post_step_gas.spend() - pre_step_gas.spend(), + // gas_refunded: post_step_gas.refunded() - pre_step_gas.refunded(), + // gas_left: interp.gas().remaining(), + // stack: interp.stack().data().clone(), + // memory: Bytes::copy_from_slice(interp.memory.data().as_slice()), + contract: data + .journaled_state + .account(interp.contract.address) + .info + .clone(), + contract_address: interp.contract().address, + sender, + }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); - receiver - .recv() - .unwrap() - .expect("Failed call to BeforeMessageHandler"); + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); + } InstructionResult::Continue } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index 80ea092e0f..a4155584ec 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -558,8 +558,8 @@ function assertEqualRunTxResults( throw new Error("Different totalGasSpent"); } - const exitCode = ethereumJSResult.exit.kind; - if (exitCode === ExitCode.SUCCESS || exitCode === ExitCode.REVERT) { + const exitCode = ethereumJSResult.exit; + if (exitCode.isSuccess() || exitCode.kind === ExitCode.REVERT) { // TODO: we only compare the return values when a contract was *not* created, // because sometimes ethereumjs has the created bytecode in the return value // and rethnet doesn't @@ -608,15 +608,15 @@ function assertEqualRunTxResults( assertEqualLogs(ethereumJSResult.receipt.logs, rethnetResult.receipt.logs); } - if (exitCode === ExitCode.SUCCESS) { + if (exitCode.isSuccess()) { if ( ethereumJSResult.createdAddress?.toString() !== - rethnetResult.createdAddress?.toString() && + rethnetResult.createdAddress?.toString() // ethereumjs returns a createdAddress, even when reverting - !( - rethnetResult.createdAddress === undefined && - ethereumJSResult.exit.kind !== ExitCode.SUCCESS - ) + // && !( + // rethnetResult.createdAddress === undefined && + // ethereumJSResult.exit.isError() + // ) ) { console.trace( `Different createdAddress: ${ethereumJSResult.createdAddress?.toString()} !== ${rethnetResult.createdAddress?.toString()}` diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index 4aea036571..48749bf5fb 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -211,14 +211,13 @@ export class EthereumJSAdapter implements VMAdapter { "Should have a result" ); - const ethereumJSError = ethereumJSResult.execResult.exceptionError; const result: RunTxResult = { bloom: new Bloom(ethereumJSResult.bloom.bitvector), gasUsed: ethereumJSResult.totalGasSpent, receipt: ethereumJSResult.receipt, returnValue: ethereumJSResult.execResult.returnValue, createdAddress: ethereumJSResult.createdAddress, - exit: Exit.fromEthereumJSEvmError(ethereumJSError), + exit: Exit.fromEthereumJSEvmResult(ethereumJSResult), }; return [result, trace]; @@ -398,14 +397,13 @@ export class EthereumJSAdapter implements VMAdapter { "Should have a result" ); - const ethereumJSError = ethereumJSResult.execResult.exceptionError; const result: RunTxResult = { bloom: new Bloom(ethereumJSResult.bloom.bitvector), gasUsed: ethereumJSResult.totalGasSpent, receipt: ethereumJSResult.receipt, returnValue: ethereumJSResult.execResult.returnValue, createdAddress: ethereumJSResult.createdAddress, - exit: Exit.fromEthereumJSEvmError(ethereumJSError), + exit: Exit.fromEthereumJSEvmResult(ethereumJSResult), }; return [result, trace]; @@ -542,9 +540,14 @@ export class EthereumJSAdapter implements VMAdapter { let executionResult; if (result.execResult.exceptionError === undefined) { - const reason = !result.execResult.returnValue.equals(Buffer.from([])) - ? SuccessReason.Return - : SuccessReason.SelfDestruct; + const reason = + result.execResult.selfdestruct !== undefined && + Object.keys(result.execResult.selfdestruct).length > 0 + ? SuccessReason.SelfDestruct + : result.createdAddress !== undefined || + result.execResult.returnValue.length > 0 + ? SuccessReason.Return + : SuccessReason.Stop; executionResult = { reason, @@ -574,9 +577,7 @@ export class EthereumJSAdapter implements VMAdapter { output: result.execResult.returnValue, }; } else { - const vmError = Exit.fromEthereumJSEvmError( - result.execResult.exceptionError - ); + const vmError = Exit.fromEthereumJSEvmResult(result); executionResult = { reason: vmError.getRethnetExceptionalHalt(), diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts index 12b44d81cc..290e0baa94 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts @@ -1,9 +1,11 @@ -import { EvmError } from "@nomicfoundation/ethereumjs-evm"; +import { EVMResult, EvmError } from "@nomicfoundation/ethereumjs-evm"; import { ERROR } from "@nomicfoundation/ethereumjs-evm/dist/exceptions"; import { ExceptionalHalt, SuccessReason } from "rethnet-evm"; export enum ExitCode { - SUCCESS, + STOP, + RETURN, + SELF_DESTRUCT, REVERT, OUT_OF_GAS, INTERNAL_ERROR, @@ -15,11 +17,12 @@ export enum ExitCode { export class Exit { public static fromRethnetSuccessReason(reason: SuccessReason): Exit { switch (reason) { + case SuccessReason.Stop: + return new Exit(ExitCode.STOP); case SuccessReason.Return: + return new Exit(ExitCode.RETURN); case SuccessReason.SelfDestruct: - case SuccessReason.Stop: - return new Exit(ExitCode.SUCCESS); - // TODO: Should we throw an error if default is hit? + return new Exit(ExitCode.SELF_DESTRUCT); } } @@ -46,9 +49,23 @@ export class Exit { } } - public static fromEthereumJSEvmError(evmError: EvmError | undefined): Exit { + public static fromEthereumJSEvmResult(result: EVMResult): Exit { + const evmError = result.execResult.exceptionError; if (evmError === undefined) { - return new Exit(ExitCode.SUCCESS); + // TODO: Figure out which of STOP | RETURN | SELF_DESTRUCT + if ( + result.execResult.selfdestruct !== undefined && + Object.keys(result.execResult.selfdestruct).length > 0 + ) { + return new Exit(ExitCode.SELF_DESTRUCT); + } else if ( + result.createdAddress !== undefined || + result.execResult.returnValue.length > 0 + ) { + return new Exit(ExitCode.RETURN); + } else { + return new Exit(ExitCode.STOP); + } } if (evmError.error === ERROR.REVERT) { @@ -82,14 +99,22 @@ export class Exit { constructor(public kind: ExitCode) {} + public isSuccess(): boolean { + return this.kind <= ExitCode.SELF_DESTRUCT; + } + public isError(): boolean { - return this.kind !== ExitCode.SUCCESS; + return this.kind > ExitCode.SELF_DESTRUCT; } public getReason(): string { switch (this.kind) { - case ExitCode.SUCCESS: - return "Success"; + case ExitCode.STOP: + return "Stopped"; + case ExitCode.RETURN: + return "Returned"; + case ExitCode.SELF_DESTRUCT: + return "Self destructed"; case ExitCode.REVERT: return "Reverted"; case ExitCode.OUT_OF_GAS: @@ -109,7 +134,9 @@ export class Exit { public getEthereumJSError(): EvmError | undefined { switch (this.kind) { - case ExitCode.SUCCESS: + case ExitCode.STOP: + case ExitCode.RETURN: + case ExitCode.SELF_DESTRUCT: return undefined; case ExitCode.REVERT: return new EvmError(ERROR.REVERT); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 6f9320670f..e9221cd6b1 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -320,7 +320,7 @@ export class RethnetAdapter implements VMAdapter { block: Block, config: RpcDebugTracingConfig ): Promise { - throw new Error("not implemented"); + throw new Error("traceTransaction not implemented for Rethnet"); } public async makeSnapshot(): Promise { diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts index 03145a65fa..4094f34716 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts @@ -79,7 +79,7 @@ export class VMTracer { code: message.data, steps: [], value: message.value, - exit: new Exit(ExitCode.SUCCESS), + exit: new Exit(ExitCode.RETURN), returnData: DUMMY_RETURN_DATA, numberOfSubtraces: 0, depth: message.depth, @@ -96,7 +96,7 @@ export class VMTracer { precompile: Number(toAsBigInt), calldata: message.data, value: message.value, - exit: new Exit(ExitCode.SUCCESS), + exit: new Exit(ExitCode.RETURN), returnData: DUMMY_RETURN_DATA, depth: message.depth, gasUsed: DUMMY_GAS_USED, @@ -122,7 +122,7 @@ export class VMTracer { calldata: message.data, steps: [], value: message.value, - exit: new Exit(ExitCode.SUCCESS), + exit: new Exit(ExitCode.RETURN), returnData: DUMMY_RETURN_DATA, address: message.to, numberOfSubtraces: 0, diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts index 9001a0e52e..a1f82d957e 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/logs.ts @@ -414,7 +414,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[9 ], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[10], ""); - assert.match(this.logger.lines[11], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[11], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[12], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[13], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[14], /^ Value:\s+0 ETH$/); @@ -458,7 +458,7 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[2 ], ""); assert.match(this.logger.lines[3 ], /^ Block #\d+:\s+0x[0-9a-f]{64}$/); assert.match(this.logger.lines[4 ], /^ Base fee: \d+$/); - assert.match(this.logger.lines[5 ], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[5 ], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[6 ], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[7 ], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); @@ -527,7 +527,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[22], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[23], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[24], ""); - assert.match(this.logger.lines[25], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[25], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[26], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[27], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[28], /^ Value:\s+0 ETH$/); @@ -570,7 +570,7 @@ describe("Provider logs", function () { assert.equal(this.logger.lines[2 ], ""); assert.match(this.logger.lines[3 ], /^ Block #\d+:\s+0x[0-9a-f]{64}$/); assert.match(this.logger.lines[4 ], /^ Base fee: \d+$/); - assert.match(this.logger.lines[5 ], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[5 ], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[6 ], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[7 ], /^ To:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[8 ], /^ Value:\s+0 ETH$/); @@ -643,7 +643,7 @@ describe("Provider logs", function () { assert.match(this.logger.lines[ 8], /^ Value:\s+0 ETH$/); assert.match(this.logger.lines[ 9], /^ Gas used:\s+21000 of 21000$/); assert.equal(this.logger.lines[10], ""); - assert.match(this.logger.lines[11], /^ Transaction:\s+[1m0x[0-9a-f]{64}/); + assert.match(this.logger.lines[11], /^ Transaction:\s+\u001b[1m0x[0-9a-f]{64}/); assert.match(this.logger.lines[12], /^ Contract call:\s+/); assert.match(this.logger.lines[13], /^ From:\s+0x[0-9a-f]{40}/); assert.match(this.logger.lines[14], /^ To:\s+0x[0-9a-f]{40}/); From 71b599ededc28857f803a50fd067d525cea04a15 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 8 Feb 2023 13:49:14 -0600 Subject: [PATCH 26/31] fix: remaining rethnet test failure --- crates/rethnet_eth/Cargo.toml | 2 +- crates/rethnet_evm/Cargo.toml | 2 +- crates/rethnet_evm/src/lib.rs | 2 +- crates/rethnet_evm/src/transaction.rs | 3 +++ crates/rethnet_evm_napi/src/runtime.rs | 14 ++++++++++++-- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index 0237653745..04bfe27168 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,7 +14,7 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -revm-primitives = { git = "https://github.com/wodann/revm", rev = "13d2f20", version = "1.0", default-features = false } +revm-primitives = { git = "https://github.com/wodann/revm", rev = "f69b5df", version = "1.0", default-features = false } # revm-primitives = { path = "../../../revm/crates/primitives", version = "1.0", default-features = false } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index d301c1c737..715c5d9c4d 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -10,7 +10,7 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth", features = ["serde"] } -revm = { git = "https://github.com/wodann/revm", rev = "13d2f20", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } +revm = { git = "https://github.com/wodann/revm", rev = "f69b5df", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } # revm = { path = "../../../revm/crates/revm", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } diff --git a/crates/rethnet_evm/src/lib.rs b/crates/rethnet_evm/src/lib.rs index 574ab0859d..0f73af3fde 100644 --- a/crates/rethnet_evm/src/lib.rs +++ b/crates/rethnet_evm/src/lib.rs @@ -23,7 +23,7 @@ pub use crate::{ block::{BlockBuilder, HeaderData}, debug::StateDebug, runtime::{AsyncDatabase, Rethnet}, - transaction::PendingTransaction, + transaction::{PendingTransaction, TransactionError}, }; /// Types for managing Ethereum blockchain diff --git a/crates/rethnet_evm/src/transaction.rs b/crates/rethnet_evm/src/transaction.rs index a341aede54..f0a936fc15 100644 --- a/crates/rethnet_evm/src/transaction.rs +++ b/crates/rethnet_evm/src/transaction.rs @@ -18,13 +18,16 @@ use revm::{ /// Invalid transaction error #[derive(Debug, thiserror::Error)] pub enum TransactionError { + /// Blockchain errors #[error(transparent)] BlockHash(BE), + /// Corrupt transaction data #[error("Invalid transaction")] InvalidTransaction(InvalidTransaction), /// The transaction is expected to have a prevrandao, as the executor's config is on a post-merge hardfork. #[error("Post-merge transaction is missing prevrandao")] MissingPrevrandao, + /// State errors #[error(transparent)] State(SE), } diff --git a/crates/rethnet_evm_napi/src/runtime.rs b/crates/rethnet_evm_napi/src/runtime.rs index aa677ea5fd..43cff7941c 100644 --- a/crates/rethnet_evm_napi/src/runtime.rs +++ b/crates/rethnet_evm_napi/src/runtime.rs @@ -1,7 +1,7 @@ use napi::Status; use napi_derive::napi; use once_cell::sync::OnceCell; -use rethnet_evm::{state::StateError, TxEnv}; +use rethnet_evm::{state::StateError, InvalidTransaction, TransactionError, TxEnv}; use crate::{ block::BlockConfig, @@ -109,7 +109,17 @@ impl Rethnet { .runtime .run(transaction, block, inspector) .await - .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))? + .map_err(|e| { + napi::Error::new( + Status::GenericFailure, + match e { + TransactionError::InvalidTransaction( + InvalidTransaction::LackOfFundForGasLimit { gas_limit, balance }, + ) => format!("sender doesn't have enough funds to send tx. The max upfront cost is: {} and the sender's account only has: {}", gas_limit, balance), + e => e.to_string(), + }, + ) + })? .into()) } } From ee7f8ac837347f93ae0b442fe529bdba3124aae2 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 8 Feb 2023 13:49:53 -0600 Subject: [PATCH 27/31] test: skip pre-byzantium test --- .../internal/hardhat-network/provider/modules/eth/hardforks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/hardforks.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/hardforks.ts index 11a170f404..2c61ad9cae 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/hardforks.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/hardforks.ts @@ -692,7 +692,7 @@ describe("Eth module - hardfork dependant tests", function () { }); describe("Receipts formatting", function () { - describe("Before byzantium", function () { + describe.skip("Before byzantium", function () { useProviderAndCommon("spuriousDragon"); it("Should have a root field, and shouldn't have a status one nor type", async function () { From 6132136022608d5bdb6c7ca4c259539ad5c2f9f5 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 8 Feb 2023 17:14:20 -0600 Subject: [PATCH 28/31] fix: returned InstructionResult in JsTracer --- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 39cbfd158d..089c5ff1e3 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -582,13 +582,16 @@ where self.validate_before_message(); - let ret = if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFund { + let safe_ret = if ret == InstructionResult::CallTooDeep + || ret == InstructionResult::OutOfFund + || ret == InstructionResult::StateChangeDuringStaticCall + { InstructionResult::Revert } else { ret }; - let result = match ret.into() { + let result = match safe_ret.into() { SuccessOrHalt::Success(reason) => rethnet_evm::ExecutionResult::Success { reason, gas_used: remaining_gas.spend(), @@ -604,7 +607,7 @@ where reason, gas_used: remaining_gas.limit(), }, - SuccessOrHalt::Internal => panic!("Internal error: {:?}", ret), + SuccessOrHalt::Internal => panic!("Internal error: {:?}", safe_ret), SuccessOrHalt::FatalExternalError => panic!("Fatal external error"), }; From 0d36449d5ad91d81af58ea647eeea0e1937ebe7a Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 8 Feb 2023 17:34:56 -0600 Subject: [PATCH 29/31] fix: revert ExitCode changes --- .../hardhat-network/provider/vm/dual.ts | 16 +++---- .../hardhat-network/provider/vm/ethereumjs.ts | 10 +++-- .../hardhat-network/provider/vm/exit.ts | 45 ++++--------------- .../hardhat-network/stack-traces/vm-tracer.ts | 6 +-- 4 files changed, 27 insertions(+), 50 deletions(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index a4155584ec..80ea092e0f 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -558,8 +558,8 @@ function assertEqualRunTxResults( throw new Error("Different totalGasSpent"); } - const exitCode = ethereumJSResult.exit; - if (exitCode.isSuccess() || exitCode.kind === ExitCode.REVERT) { + const exitCode = ethereumJSResult.exit.kind; + if (exitCode === ExitCode.SUCCESS || exitCode === ExitCode.REVERT) { // TODO: we only compare the return values when a contract was *not* created, // because sometimes ethereumjs has the created bytecode in the return value // and rethnet doesn't @@ -608,15 +608,15 @@ function assertEqualRunTxResults( assertEqualLogs(ethereumJSResult.receipt.logs, rethnetResult.receipt.logs); } - if (exitCode.isSuccess()) { + if (exitCode === ExitCode.SUCCESS) { if ( ethereumJSResult.createdAddress?.toString() !== - rethnetResult.createdAddress?.toString() + rethnetResult.createdAddress?.toString() && // ethereumjs returns a createdAddress, even when reverting - // && !( - // rethnetResult.createdAddress === undefined && - // ethereumJSResult.exit.isError() - // ) + !( + rethnetResult.createdAddress === undefined && + ethereumJSResult.exit.kind !== ExitCode.SUCCESS + ) ) { console.trace( `Different createdAddress: ${ethereumJSResult.createdAddress?.toString()} !== ${rethnetResult.createdAddress?.toString()}` diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index 48749bf5fb..ad4610f1fd 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -211,13 +211,14 @@ export class EthereumJSAdapter implements VMAdapter { "Should have a result" ); + const ethereumJSError = ethereumJSResult.execResult.exceptionError; const result: RunTxResult = { bloom: new Bloom(ethereumJSResult.bloom.bitvector), gasUsed: ethereumJSResult.totalGasSpent, receipt: ethereumJSResult.receipt, returnValue: ethereumJSResult.execResult.returnValue, createdAddress: ethereumJSResult.createdAddress, - exit: Exit.fromEthereumJSEvmResult(ethereumJSResult), + exit: Exit.fromEthereumJSEvmError(ethereumJSError), }; return [result, trace]; @@ -397,13 +398,14 @@ export class EthereumJSAdapter implements VMAdapter { "Should have a result" ); + const ethereumJSError = ethereumJSResult.execResult.exceptionError; const result: RunTxResult = { bloom: new Bloom(ethereumJSResult.bloom.bitvector), gasUsed: ethereumJSResult.totalGasSpent, receipt: ethereumJSResult.receipt, returnValue: ethereumJSResult.execResult.returnValue, createdAddress: ethereumJSResult.createdAddress, - exit: Exit.fromEthereumJSEvmResult(ethereumJSResult), + exit: Exit.fromEthereumJSEvmError(ethereumJSError), }; return [result, trace]; @@ -577,7 +579,9 @@ export class EthereumJSAdapter implements VMAdapter { output: result.execResult.returnValue, }; } else { - const vmError = Exit.fromEthereumJSEvmResult(result); + const vmError = Exit.fromEthereumJSEvmError( + result.execResult.exceptionError + ); executionResult = { reason: vmError.getRethnetExceptionalHalt(), diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts index 290e0baa94..573495a8be 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts @@ -3,9 +3,7 @@ import { ERROR } from "@nomicfoundation/ethereumjs-evm/dist/exceptions"; import { ExceptionalHalt, SuccessReason } from "rethnet-evm"; export enum ExitCode { - STOP, - RETURN, - SELF_DESTRUCT, + SUCCESS, REVERT, OUT_OF_GAS, INTERNAL_ERROR, @@ -18,11 +16,10 @@ export class Exit { public static fromRethnetSuccessReason(reason: SuccessReason): Exit { switch (reason) { case SuccessReason.Stop: - return new Exit(ExitCode.STOP); case SuccessReason.Return: - return new Exit(ExitCode.RETURN); case SuccessReason.SelfDestruct: - return new Exit(ExitCode.SELF_DESTRUCT); + return new Exit(ExitCode.SUCCESS); + // TODO: Should we throw an error if default is hit? } } @@ -49,23 +46,9 @@ export class Exit { } } - public static fromEthereumJSEvmResult(result: EVMResult): Exit { - const evmError = result.execResult.exceptionError; + public static fromEthereumJSEvmError(evmError: EvmError | undefined): Exit { if (evmError === undefined) { - // TODO: Figure out which of STOP | RETURN | SELF_DESTRUCT - if ( - result.execResult.selfdestruct !== undefined && - Object.keys(result.execResult.selfdestruct).length > 0 - ) { - return new Exit(ExitCode.SELF_DESTRUCT); - } else if ( - result.createdAddress !== undefined || - result.execResult.returnValue.length > 0 - ) { - return new Exit(ExitCode.RETURN); - } else { - return new Exit(ExitCode.STOP); - } + return new Exit(ExitCode.SUCCESS); } if (evmError.error === ERROR.REVERT) { @@ -99,22 +82,14 @@ export class Exit { constructor(public kind: ExitCode) {} - public isSuccess(): boolean { - return this.kind <= ExitCode.SELF_DESTRUCT; - } - public isError(): boolean { - return this.kind > ExitCode.SELF_DESTRUCT; + return this.kind !== ExitCode.SUCCESS; } public getReason(): string { switch (this.kind) { - case ExitCode.STOP: - return "Stopped"; - case ExitCode.RETURN: - return "Returned"; - case ExitCode.SELF_DESTRUCT: - return "Self destructed"; + case ExitCode.SUCCESS: + return "Success"; case ExitCode.REVERT: return "Reverted"; case ExitCode.OUT_OF_GAS: @@ -134,9 +109,7 @@ export class Exit { public getEthereumJSError(): EvmError | undefined { switch (this.kind) { - case ExitCode.STOP: - case ExitCode.RETURN: - case ExitCode.SELF_DESTRUCT: + case ExitCode.SUCCESS: return undefined; case ExitCode.REVERT: return new EvmError(ERROR.REVERT); diff --git a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts index 4094f34716..03145a65fa 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/stack-traces/vm-tracer.ts @@ -79,7 +79,7 @@ export class VMTracer { code: message.data, steps: [], value: message.value, - exit: new Exit(ExitCode.RETURN), + exit: new Exit(ExitCode.SUCCESS), returnData: DUMMY_RETURN_DATA, numberOfSubtraces: 0, depth: message.depth, @@ -96,7 +96,7 @@ export class VMTracer { precompile: Number(toAsBigInt), calldata: message.data, value: message.value, - exit: new Exit(ExitCode.RETURN), + exit: new Exit(ExitCode.SUCCESS), returnData: DUMMY_RETURN_DATA, depth: message.depth, gasUsed: DUMMY_GAS_USED, @@ -122,7 +122,7 @@ export class VMTracer { calldata: message.data, steps: [], value: message.value, - exit: new Exit(ExitCode.RETURN), + exit: new Exit(ExitCode.SUCCESS), returnData: DUMMY_RETURN_DATA, address: message.to, numberOfSubtraces: 0, From 114a951d20ac33b64e47484f9a5d91ba2148ef69 Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 9 Feb 2023 19:02:16 -0600 Subject: [PATCH 30/31] fix: discrepancy between trace and execution exit codes --- crates/rethnet_eth/Cargo.toml | 2 +- crates/rethnet_evm/Cargo.toml | 2 +- .../rethnet_evm_napi/src/tracer/js_tracer.rs | 50 +++++++++---------- .../internal/hardhat-network/provider/node.ts | 6 +++ .../hardhat-network/provider/vm/exit.ts | 2 +- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index 04bfe27168..7b28e36710 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,7 +14,7 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -revm-primitives = { git = "https://github.com/wodann/revm", rev = "f69b5df", version = "1.0", default-features = false } +revm-primitives = { git = "https://github.com/wodann/revm", rev = "2ab0fd5", version = "1.0", default-features = false } # revm-primitives = { path = "../../../revm/crates/primitives", version = "1.0", default-features = false } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index 715c5d9c4d..e851818d16 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -10,7 +10,7 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth", features = ["serde"] } -revm = { git = "https://github.com/wodann/revm", rev = "f69b5df", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } +revm = { git = "https://github.com/wodann/revm", rev = "2ab0fd5", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } # revm = { path = "../../../revm/crates/revm", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 089c5ff1e3..01df173ddb 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -662,45 +662,45 @@ where ) -> (InstructionResult, Option, Gas, Bytes) { self.validate_before_message(); - let ret = if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFund { - InstructionResult::Revert - } else { - ret - }; + let safe_ret = + if ret == InstructionResult::CallTooDeep || ret == InstructionResult::OutOfFund { + InstructionResult::Revert + } else { + ret + }; - let result = match ret.into() { - SuccessOrHalt::Success(reason) => Some(rethnet_evm::ExecutionResult::Success { + let result = match safe_ret.into() { + SuccessOrHalt::Success(reason) => rethnet_evm::ExecutionResult::Success { reason, gas_used: remaining_gas.spend(), gas_refunded: remaining_gas.refunded() as u64, logs: data.journaled_state.logs.clone(), output: rethnet_evm::Output::Create(out.clone(), address), - }), - SuccessOrHalt::Revert => Some(rethnet_evm::ExecutionResult::Revert { + }, + SuccessOrHalt::Revert => rethnet_evm::ExecutionResult::Revert { gas_used: remaining_gas.spend(), output: out.clone(), - }), - SuccessOrHalt::Halt(reason) => Some(rethnet_evm::ExecutionResult::Halt { + }, + SuccessOrHalt::Halt(reason) => rethnet_evm::ExecutionResult::Halt { reason, gas_used: remaining_gas.limit(), - }), - SuccessOrHalt::FatalExternalError | SuccessOrHalt::Internal => None, + }, + SuccessOrHalt::Internal => panic!("Internal error: {:?}", safe_ret), + SuccessOrHalt::FatalExternalError => panic!("Fatal external error"), }; - if let Some(result) = result { - let (sender, receiver) = channel(); + let (sender, receiver) = channel(); - let status = self.after_message_fn.call( - AfterMessageHandlerCall { result, sender }, - ThreadsafeFunctionCallMode::Blocking, - ); - assert_eq!(status, Status::Ok); + let status = self.after_message_fn.call( + AfterMessageHandlerCall { result, sender }, + ThreadsafeFunctionCallMode::Blocking, + ); + assert_eq!(status, Status::Ok); - receiver - .recv() - .unwrap() - .expect("Failed call to BeforeMessageHandler"); - } + receiver + .recv() + .unwrap() + .expect("Failed call to BeforeMessageHandler"); (ret, address, remaining_gas, out) } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts index 77a590e3a4..de821c12a1 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts @@ -1846,6 +1846,12 @@ Hardhat Network's forking functionality only works with blocks from at least spu throw exitCode; } + if (exitCode.kind !== vmTrace?.exit.kind) { + console.trace("execution:", exitCode); + console.log("trace:", vmTrace?.exit); + throw Error("Execution error does not match trace error"); + } + if (exitCode.kind === ExitCode.CODESIZE_EXCEEDS_MAXIMUM) { if (stackTrace !== undefined) { return encodeSolidityStackTrace( diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts index 573495a8be..01a03b204a 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts @@ -1,4 +1,4 @@ -import { EVMResult, EvmError } from "@nomicfoundation/ethereumjs-evm"; +import { EvmError } from "@nomicfoundation/ethereumjs-evm"; import { ERROR } from "@nomicfoundation/ethereumjs-evm/dist/exceptions"; import { ExceptionalHalt, SuccessReason } from "rethnet-evm"; From 6ddf13283532207b8a3c663674001a919123caea Mon Sep 17 00:00:00 2001 From: Wodann Date: Mon, 13 Feb 2023 23:02:00 -0600 Subject: [PATCH 31/31] bump: upgrade to latest revm --- crates/rethnet_eth/Cargo.toml | 2 +- crates/rethnet_evm/Cargo.toml | 2 +- crates/rethnet_evm/src/state/layered_db.rs | 2 -- crates/rethnet_evm_napi/src/config.rs | 11 +++++++---- crates/rethnet_evm_napi/src/tracer/js_tracer.rs | 5 +++-- crates/rethnet_evm_napi/src/transaction/result.rs | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/rethnet_eth/Cargo.toml b/crates/rethnet_eth/Cargo.toml index 7b28e36710..45594fb6b8 100644 --- a/crates/rethnet_eth/Cargo.toml +++ b/crates/rethnet_eth/Cargo.toml @@ -14,7 +14,7 @@ hex-literal = { version = "0.3", default-features = false } open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true } primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] } reqwest = { version = "0.11", features = ["blocking", "json"] } -revm-primitives = { git = "https://github.com/wodann/revm", rev = "2ab0fd5", version = "1.0", default-features = false } +revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "8e6f4f2", version = "1.0", default-features = false } # revm-primitives = { path = "../../../revm/crates/primitives", version = "1.0", default-features = false } rlp = { version = "0.5.2", default-features = false, features = ["derive"] } ruint = { version = "1.7.0", default-features = false } diff --git a/crates/rethnet_evm/Cargo.toml b/crates/rethnet_evm/Cargo.toml index e851818d16..74a83a5274 100644 --- a/crates/rethnet_evm/Cargo.toml +++ b/crates/rethnet_evm/Cargo.toml @@ -10,7 +10,7 @@ hashbrown = { version = "0.13", default-features = false, features = ["ahash", " log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", default-features = false } rethnet_eth = { version = "0.1.0-dev", path = "../rethnet_eth", features = ["serde"] } -revm = { git = "https://github.com/wodann/revm", rev = "2ab0fd5", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } +revm = { git = "https://github.com/bluealloy/revm", rev = "8e6f4f2", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } # revm = { path = "../../../revm/crates/revm", version = "3.0", default-features = false, features = ["dev", "serde", "std"] } secp256k1 = { version = "0.24.1", default-features = false, features = ["alloc"] } sha3 = { version = "0.10.4", default-features = false } diff --git a/crates/rethnet_evm/src/state/layered_db.rs b/crates/rethnet_evm/src/state/layered_db.rs index 22c5fcb940..bc71f4ad62 100644 --- a/crates/rethnet_evm/src/state/layered_db.rs +++ b/crates/rethnet_evm/src/state/layered_db.rs @@ -252,8 +252,6 @@ impl State for LayeredState { .cloned() .flatten(); - log::debug!("account with address `{}`: {:?}", address, account); - // TODO: Move this out of LayeredState when forking Ok(account.or(Some(AccountInfo { balance: U256::ZERO, diff --git a/crates/rethnet_evm_napi/src/config.rs b/crates/rethnet_evm_napi/src/config.rs index ff136b0224..760b144ad6 100644 --- a/crates/rethnet_evm_napi/src/config.rs +++ b/crates/rethnet_evm_napi/src/config.rs @@ -42,10 +42,12 @@ pub enum SpecId { GrayGlacier = 14, /// Merge Merge = 15, - /// Merge + EOF - MergeEOF = 16, + /// Shanghai + Shanghai = 16, + /// Cancun + Cancun = 17, /// Latest - Latest = 17, + Latest = 18, } impl From for rethnet_evm::SpecId { @@ -67,7 +69,8 @@ impl From for rethnet_evm::SpecId { SpecId::ArrowGlacier => rethnet_evm::SpecId::ARROW_GLACIER, SpecId::GrayGlacier => rethnet_evm::SpecId::GRAY_GLACIER, SpecId::Merge => rethnet_evm::SpecId::MERGE, - SpecId::MergeEOF => rethnet_evm::SpecId::MERGE_EOF, + SpecId::Shanghai => rethnet_evm::SpecId::SHANGHAI, + SpecId::Cancun => rethnet_evm::SpecId::CANCUN, SpecId::Latest => rethnet_evm::SpecId::LATEST, } } diff --git a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs index 01df173ddb..ae95886d7d 100644 --- a/crates/rethnet_evm_napi/src/tracer/js_tracer.rs +++ b/crates/rethnet_evm_napi/src/tracer/js_tracer.rs @@ -17,7 +17,7 @@ use crate::{ account::Account, sync::{await_void_promise, handle_error}, threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, - transaction::result::ExecutionResult, + transaction::result::{ExceptionalHalt, ExecutionResult}, }; #[napi(object)] @@ -463,8 +463,9 @@ impl JsTracer { gas_used } rethnet_evm::ExecutionResult::Halt { reason, gas_used } => { + let halt = ExceptionalHalt::from(reason); ctx.env - .create_uint32(reason as u32) + .create_uint32(halt as u32) .and_then(|reason| result.set_named_property("reason", reason))?; gas_used diff --git a/crates/rethnet_evm_napi/src/transaction/result.rs b/crates/rethnet_evm_napi/src/transaction/result.rs index 04e9301ead..3013593e78 100644 --- a/crates/rethnet_evm_napi/src/transaction/result.rs +++ b/crates/rethnet_evm_napi/src/transaction/result.rs @@ -91,7 +91,7 @@ pub enum ExceptionalHalt { impl From for ExceptionalHalt { fn from(halt: rethnet_evm::Halt) -> Self { match halt { - rethnet_evm::Halt::OutOfGas => ExceptionalHalt::OutOfGas, + rethnet_evm::Halt::OutOfGas(..) => ExceptionalHalt::OutOfGas, rethnet_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound, // rethnet_evm::Halt::CallNotAllowedInsideStatic => { // ExceptionalHalt::CallNotAllowedInsideStatic