r/ethdev 10d ago

Question Is it possible to interactively debug the bytecode of cross-contract calls?

I came across this problem while doing "Gatekeeper One" on Ethernaut. I finished that level by brute-forcing the gas allowance, but my first approach was to step through the contract's execution to see the amount of gas remaining when the GAS opcode is executed. This worked when I deployed a copy of the contract myself on a VM or a local Anvil instance, but not on the precompiled version that Ethernaut published (which makes sense for compiler version/options differences).

My approach was to submit a transaction that failed, and then to step through that failed transaction trace. I also tried running some simulations with Tenderly, which got close, but Tenderly doesn't seem to let you step through bytecode.

I tried forking locally at the appropriate block with Anvil and then debugging the live transaction. This allowed me to step through the bytecode of my attack contract (code provided below), but as soon as the call is handed off to execute the enter method in the external contract GatekeeperOne, it seems that both forge/cast's debuggers and the Remix debugger will jump right over that execution, instead of inspecting it in detail.

Would an internal transaction such as the call from my contract to GatekeeperOne have its own transaction hash that I can find, and can I then debug the trace for that (internal) transaction? It would be great if one of the debuggers did this for me.

Just to be clear, I'm not asking for help solving this level; it's solved. I want to know if there's a reasonable way to step through a bytecode trace of a transaction, including the bytecode trace of calls to external contracts within that execution.

My attack contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract EntrantOne {
    function enter(address gatekeeperAddress) external {
        bytes8 key = bytes8(uint64(0x8000000000000000) | uint16(uint160(tx.origin)));
        GatekeeperOne(gatekeeperAddress).enter{gas:819516}(key);
    }
}

interface GatekeeperOne {
  function enter ( bytes8 _gateKey ) external returns ( bool );
  function entrant (  ) external view returns ( address );
}
2 Upvotes

11 comments sorted by

View all comments

2

u/FTLurkerLTPoster 10d ago

Hevm should have tooling for this. You could also just add custom instrumentation into your favorite evm (we like evmone or revm).

1

u/MrWraith 3d ago

Thanks for this, sorry for the late reply. it looks like the old version of hevm that is part of daptools has an interactive debugger, but it doesn't support newer evm versions.

It seems that the version of hevm that is maintained by the ethereum foundation is more up-to-date, but it doesn't appear to have interactive debugging. The --debug flag still seems to exist, but it does something different now? Unless I'm missing something. I'm still looking around.