Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: showing low-level VM errors #311

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <hello@matterlabs.dev>"]
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
Loading