Skip to content

Commit

Permalink
feat: showing low-level VM errors (#311)
Browse files Browse the repository at this point in the history
* feat: showing low-level VM errors

* silenced clippy warning

* Update to latest version and check-in e2e test that displays console errors

* fix lint

---------

Co-authored-by: Nicolas Villanueva <[email protected]>
Co-authored-by: MexicanAce <[email protected]>
  • Loading branch information
3 people authored Sep 26, 2024
1 parent 3a6451d commit d35eb77
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
homepage = "https://zksync.io/"
Expand Down
9 changes: 9 additions & 0 deletions e2e-tests/contracts/Fib.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
24 changes: 24 additions & 0 deletions e2e-tests/test/fib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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");
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");
});
});
62 changes: 62 additions & 0 deletions src/node/call_error_tracer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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 zksync_state::interface::WriteStorage;

pub struct CallErrorTracer {}

impl CallErrorTracer {
pub fn new() -> Self {
Self {}
}
}

impl<S, H: HistoryMode> DynTracer<S, SimpleMemory<H>> for CallErrorTracer {
fn after_decoding(
&mut self,
_state: VmLocalStateData<'_>,
data: AfterDecodingData,
_memory: &SimpleMemory<H>,
) {
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<S: WriteStorage, H: HistoryMode> VmTracer<S, H> for CallErrorTracer {}
14 changes: 10 additions & 4 deletions src/node/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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},
Expand Down Expand Up @@ -1036,9 +1039,11 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {

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(&mut custom_tracer.into(), VmExecutionMode::OneTx);
let tracers = vec![
CallErrorTracer::new().into_tracer_pointer(),
CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(),
];
let tx_result = vm.inspect(&mut tracers.into(), VmExecutionMode::OneTx);

let call_traces = Arc::try_unwrap(call_tracer_result)
.unwrap()
Expand Down Expand Up @@ -1345,6 +1350,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
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 {
Expand Down
1 change: 1 addition & 0 deletions src/node/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! In-memory node, that supports forking other networks.

mod anvil;
mod call_error_tracer;
mod config_api;
mod debug;
mod eth;
Expand Down

0 comments on commit d35eb77

Please sign in to comment.