From aa136dbddcea44f4694ec5f515c075ccb63a6c2b Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 9 Jul 2024 09:01:24 +0200 Subject: [PATCH 1/4] feat: showing low-level VM errors --- Cargo.lock | 1 + Cargo.toml | 1 + src/node/call_error_tracer.rs | 62 +++++++++++++++++++++++++++++++++++ src/node/in_memory.rs | 13 +++++--- src/node/mod.rs | 1 + 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/node/call_error_tracer.rs diff --git a/Cargo.lock b/Cargo.lock index dd183253..d17e9c20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1786,6 +1786,7 @@ dependencies = [ "toml 0.8.13", "tracing", "tracing-subscriber", + "zk_evm 1.5.0", "zkevm_opcode_defs 1.5.0", "zksync-web3-rs", "zksync_basic_types", diff --git a/Cargo.toml b/Cargo.toml index aa8b8317..70ef5517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["cryptography"] publish = false # We don't want to publish our binaries. [dependencies] +zk_evm = { git = "https://github.com/matter-labs/era-zk_evm.git", branch = "v1.5.0" } zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs.git", branch = "v1.5.0" } zksync_basic_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } zksync_node_fee_model = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } diff --git a/src/node/call_error_tracer.rs b/src/node/call_error_tracer.rs new file mode 100644 index 00000000..b2b40acc --- /dev/null +++ b/src/node/call_error_tracer.rs @@ -0,0 +1,62 @@ +use multivm::{ + interface::dyn_tracers::vm_1_5_0::DynTracer, + vm_latest::{HistoryMode, SimpleMemory, VmTracer}, +}; +use zk_evm::{ + tracing::{AfterDecodingData, VmLocalStateData}, + vm_state::ErrorFlags, +}; +use zksync_state::WriteStorage; + +pub struct CallErrorTracer {} + +impl CallErrorTracer { + pub fn new() -> Self { + Self {} + } +} + +impl DynTracer> for CallErrorTracer { + fn after_decoding( + &mut self, + _state: VmLocalStateData<'_>, + data: AfterDecodingData, + _memory: &SimpleMemory, + ) { + if !data.error_flags_accumulated.is_empty() { + tracing::error!("!! Got error flags: "); + if data + .error_flags_accumulated + .contains(ErrorFlags::INVALID_OPCODE) + { + tracing::error!("INVALID OPCODE"); + } + if data + .error_flags_accumulated + .contains(ErrorFlags::NOT_ENOUGH_ERGS) + { + tracing::error!("NOT ENOUGH ERGS"); + } + if data + .error_flags_accumulated + .contains(ErrorFlags::PRIVILAGED_ACCESS_NOT_FROM_KERNEL) + { + tracing::error!("PRIVILEGED ACCESS"); + } + if data + .error_flags_accumulated + .contains(ErrorFlags::WRITE_IN_STATIC_CONTEXT) + { + tracing::error!("WRITE IN STATIC"); + } + if data + .error_flags_accumulated + .contains(ErrorFlags::CALLSTACK_IS_FULL) + { + tracing::error!("CALLSTACK IS FULL"); + } + } + } +} + +impl VmTracer for CallErrorTracer {} diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs index 1fcfe114..722e76de 100644 --- a/src/node/in_memory.rs +++ b/src/node/in_memory.rs @@ -11,7 +11,10 @@ use crate::{ filters::EthFilters, fork::{block_on, ForkDetails, ForkSource, ForkStorage}, formatter, - node::{fee_model::TestNodeFeeInputProvider, storage_logs::print_storage_logs_details}, + node::{ + call_error_tracer::CallErrorTracer, fee_model::TestNodeFeeInputProvider, + storage_logs::print_storage_logs_details, + }, observability::Observability, system_contracts::{self, SystemContracts}, utils::{bytecode_to_factory_dep, create_debug_output, into_jsrpc_error, to_human_size}, @@ -1006,9 +1009,10 @@ impl InMemoryNode { let call_tracer_result = Arc::new(OnceCell::default()); - let custom_tracer = CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(); - - let tx_result = vm.inspect(custom_tracer.into(), VmExecutionMode::OneTx); + let mut tracers = Vec::new(); + tracers.push(CallErrorTracer::new().into_tracer_pointer()); + tracers.push(CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()); + let tx_result = vm.inspect(tracers.into(), VmExecutionMode::OneTx); let call_traces = Arc::try_unwrap(call_tracer_result) .unwrap() @@ -1317,6 +1321,7 @@ impl InMemoryNode { let call_tracer_result = Arc::new(OnceCell::default()); let bootloader_debug_result = Arc::new(OnceCell::default()); + tracers.push(CallErrorTracer::new().into_tracer_pointer()); tracers.push(CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()); tracers.push( BootloaderDebugTracer { diff --git a/src/node/mod.rs b/src/node/mod.rs index c0e434f5..e7478ed5 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,5 +1,6 @@ //! In-memory node, that supports forking other networks. +mod call_error_tracer; mod config_api; mod debug; mod eth; From 3083c3857201c9b90dc044177d72771604b02c88 Mon Sep 17 00:00:00 2001 From: Vaclav Barta Date: Tue, 9 Jul 2024 14:08:43 +0200 Subject: [PATCH 2/4] silenced clippy warning --- src/node/in_memory.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs index 722e76de..1758a6e4 100644 --- a/src/node/in_memory.rs +++ b/src/node/in_memory.rs @@ -1009,9 +1009,10 @@ impl InMemoryNode { let call_tracer_result = Arc::new(OnceCell::default()); - let mut tracers = Vec::new(); - tracers.push(CallErrorTracer::new().into_tracer_pointer()); - tracers.push(CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()); + let tracers = vec![ + CallErrorTracer::new().into_tracer_pointer(), + CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(), + ]; let tx_result = vm.inspect(tracers.into(), VmExecutionMode::OneTx); let call_traces = Arc::try_unwrap(call_tracer_result) From 90cd4b4564ae1d81839793b2abab7ed1db8e02c7 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 26 Sep 2024 17:47:45 +0100 Subject: [PATCH 3/4] Update to latest version and check-in e2e test that displays console errors --- Cargo.lock | 3 +-- Cargo.toml | 3 +-- e2e-tests/contracts/Fib.sol | 9 +++++++++ e2e-tests/test/fib.test.ts | 25 +++++++++++++++++++++++++ src/node/call_error_tracer.rs | 12 ++++-------- 5 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 e2e-tests/contracts/Fib.sol create mode 100644 e2e-tests/test/fib.test.ts diff --git a/Cargo.lock b/Cargo.lock index 8e0019e8..9e7520b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1810,7 +1810,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "era_test_node" -version = "0.1.0-alpha.27" +version = "0.1.0-alpha.28" dependencies = [ "anyhow", "bigdecimal 0.3.1", @@ -1844,7 +1844,6 @@ dependencies = [ "toml 0.8.13", "tracing", "tracing-subscriber", - "zk_evm 1.5.0", "zkevm_opcode_defs 1.5.0", "zksync-web3-rs", "zksync_basic_types", diff --git a/Cargo.toml b/Cargo.toml index 3f5e1222..4af27db2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "era_test_node" -version = "0.1.0-alpha.27" +version = "0.1.0-alpha.28" edition = "2018" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -11,7 +11,6 @@ categories = ["cryptography"] publish = false # We don't want to publish our binaries. [dependencies] -zk_evm = { git = "https://github.com/matter-labs/era-zk_evm.git", branch = "v1.5.0" } zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs.git", branch = "v1.5.0" } zksync_basic_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e214dd094457f196712722e084010a7ef94ee475" } zksync_node_fee_model = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e214dd094457f196712722e084010a7ef94ee475" } diff --git a/e2e-tests/contracts/Fib.sol b/e2e-tests/contracts/Fib.sol new file mode 100644 index 00000000..d6053f1b --- /dev/null +++ b/e2e-tests/contracts/Fib.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +contract Fib { + function fib(uint256 n) public pure returns (uint256) { + return n <= 1 ? 1 : fib(n - 1) + fib(n - 2); + } +} diff --git a/e2e-tests/test/fib.test.ts b/e2e-tests/test/fib.test.ts new file mode 100644 index 00000000..cb5bd7fc --- /dev/null +++ b/e2e-tests/test/fib.test.ts @@ -0,0 +1,25 @@ +import { ethers } from "ethers"; +import { Wallet } from "zksync-web3"; +import * as hre from "hardhat"; +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; +import { RichAccounts } from "../helpers/constants"; +import { deployContract, expectThrowsAsync, getTestProvider } from "../helpers/utils"; + +const provider = getTestProvider(); + +describe("Test Fib error flags", function () { + it("Should print to the console NOT ENOUGH ERGS", async function () { + const action = async () => { + const wallet = new Wallet(RichAccounts[0].PrivateKey, provider); + + const deployer = new Deployer(hre, wallet); + const fib = await deployContract(deployer, "Fib"); + const n = await fib.fib(100); + }; + + // This is expected to throw and the console is expected to show: + // XX:YY:ZZ ERROR !! Got error flags: + // XX:YY:ZZ ERROR NOT ENOUGH ERGS + await expectThrowsAsync(action, "call revert exception"); + }); +}); diff --git a/src/node/call_error_tracer.rs b/src/node/call_error_tracer.rs index b2b40acc..226f12f5 100644 --- a/src/node/call_error_tracer.rs +++ b/src/node/call_error_tracer.rs @@ -1,12 +1,8 @@ -use multivm::{ - interface::dyn_tracers::vm_1_5_0::DynTracer, - vm_latest::{HistoryMode, SimpleMemory, VmTracer}, +use zksync_multivm::{ + tracers::dynamic::vm_1_5_0::DynTracer, + vm_latest::{HistoryMode, SimpleMemory, VmTracer}, zk_evm_latest::{tracing::{AfterDecodingData, VmLocalStateData}, vm_state::ErrorFlags}, }; -use zk_evm::{ - tracing::{AfterDecodingData, VmLocalStateData}, - vm_state::ErrorFlags, -}; -use zksync_state::WriteStorage; +use zksync_state::interface::WriteStorage; pub struct CallErrorTracer {} From 949a4a8a3a142f191d980565721a9cfdbfb9856f Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 26 Sep 2024 17:51:18 +0100 Subject: [PATCH 4/4] fix lint --- e2e-tests/test/fib.test.ts | 7 +++---- src/node/call_error_tracer.rs | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/e2e-tests/test/fib.test.ts b/e2e-tests/test/fib.test.ts index cb5bd7fc..0673d065 100644 --- a/e2e-tests/test/fib.test.ts +++ b/e2e-tests/test/fib.test.ts @@ -1,4 +1,3 @@ -import { ethers } from "ethers"; import { Wallet } from "zksync-web3"; import * as hre from "hardhat"; import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; @@ -11,14 +10,14 @@ describe("Test Fib error flags", function () { it("Should print to the console NOT ENOUGH ERGS", async function () { const action = async () => { const wallet = new Wallet(RichAccounts[0].PrivateKey, provider); - + const deployer = new Deployer(hre, wallet); const fib = await deployContract(deployer, "Fib"); - const n = await fib.fib(100); + await fib.fib(100); }; // This is expected to throw and the console is expected to show: - // XX:YY:ZZ ERROR !! Got error flags: + // XX:YY:ZZ ERROR !! Got error flags: // XX:YY:ZZ ERROR NOT ENOUGH ERGS await expectThrowsAsync(action, "call revert exception"); }); diff --git a/src/node/call_error_tracer.rs b/src/node/call_error_tracer.rs index 226f12f5..8af50257 100644 --- a/src/node/call_error_tracer.rs +++ b/src/node/call_error_tracer.rs @@ -1,6 +1,10 @@ use zksync_multivm::{ tracers::dynamic::vm_1_5_0::DynTracer, - vm_latest::{HistoryMode, SimpleMemory, VmTracer}, zk_evm_latest::{tracing::{AfterDecodingData, VmLocalStateData}, vm_state::ErrorFlags}, + vm_latest::{HistoryMode, SimpleMemory, VmTracer}, + zk_evm_latest::{ + tracing::{AfterDecodingData, VmLocalStateData}, + vm_state::ErrorFlags, + }, }; use zksync_state::interface::WriteStorage;