[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-18-debugging-evm-bytecode-traces":3},{"article":4,"author":55},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":35,"related_articles":36},"d0000000-0000-0000-0000-000000000118","a0000000-0000-0000-0000-000000000002","Deep EVM #18: Debugging EVM Bytecode — Traces, Stack Dumps, and cast run","deep-evm-18-debugging-evm-bytecode-traces","Master EVM bytecode debugging with cast run for transaction replay, forge debug for step-through analysis, and techniques for reading raw opcode traces.","## The Debugging Challenge with Low-Level EVM Code\n\nWhen a Solidity transaction reverts, you typically get a descriptive error message like `ERC20: transfer amount exceeds balance`. When a Huff or Yul transaction reverts, you get `0x` — an empty revert payload with zero context. The contract simply hit a `REVERT` opcode, and it is up to you to figure out why.\n\nDebugging at the bytecode level requires different tools and mental models. You need to think in terms of the stack machine, track memory and storage changes opcode by opcode, and understand how the EVM executes control flow through `JUMP` and `JUMPI` instructions.\n\nThis article covers the essential debugging toolkit: `cast run` for replaying historical transactions, `forge debug` for interactive step-through debugging, and manual trace analysis for understanding exactly what happened inside the EVM.\n\n## cast run: Replaying Transactions\n\n`cast run` is the fastest way to debug a failed transaction. It replays the transaction against the historical state and shows you exactly what happened:\n\n```bash\ncast run 0xYOUR_TX_HASH --rpc-url https:\u002F\u002Feth-mainnet.g.alchemy.com\u002Fv2\u002FKEY\n```\n\nThe output shows a structured trace with call depth, gas usage, and return data:\n\n```\nTraces:\n  [328439] 0xContractAddr::transfer(0xRecipient, 1000000000000000000)\n    +- [2604] 0xContractAddr::balanceOf(0xSender) [staticcall]\n    |   +- \u003C- 500000000000000000\n    +- \u003C- revert: EvmError: Revert\n```\n\nThis immediately tells you the transfer failed because the sender had 0.5 ETH but tried to transfer 1.0 ETH. For Huff contracts, the function names will not be decoded (they appear as raw selectors), but the call structure and revert points are still visible.\n\n### Decoding Raw Selectors\n\nWhen working with Huff contracts, `cast run` shows raw function selectors. Decode them manually:\n\n```bash\n# Compute selector for balanceOf(address)\ncast sig \"balanceOf(address)\"\n# Output: 0x70a08231\n\n# Or decode calldata\ncast 4byte-decode 0x70a0823100000000000000000000000042069abcdef\n# Output: balanceOf(address)(0x42069abcdef)\n```\n\nKeep a reference table of your contract's selectors when debugging Huff:\n\n```\n0x70a08231 -> balanceOf(address)\n0xa9059cbb -> transfer(address,uint256)\n0x23b872dd -> transferFrom(address,address,uint256)\n0x095ea7b3 -> approve(address,uint256)\n```\n\n## forge debug: Interactive Step-Through\n\n`forge debug` provides a TUI (terminal user interface) for stepping through EVM execution opcode by opcode:\n\n```bash\nforge debug --debug test\u002FSimpleToken.t.sol \\\n  --sig \"test_transfer()\" -vvvv\n```\n\nThe interface shows four panels:\n\n1. **Opcodes** — The current instruction with a cursor, showing the bytecode being executed\n2. **Stack** — The current stack state with all 32-byte words\n3. **Memory** — Raw memory contents in hex\n4. **Storage** — Storage slot changes during execution\n\nNavigation keys:\n- `j\u002Fk` — Step forward\u002Fbackward\n- `g\u002FG` — Jump to start\u002Fend\n- `c` — Continue to next call boundary\n- `C` — Continue to next test\n- `q` — Quit\n\n### Reading the Stack During Debugging\n\nThe EVM stack is last-in-first-out with a maximum depth of 1024. When debugging Huff, you must track the stack mentally to understand what each opcode consumes and produces.\n\nConsider this Huff snippet:\n\n```huff\n0x04 calldataload   \u002F\u002F Stack: [address]\nBALANCES_SLOT       \u002F\u002F Stack: [slot, address]\n```\n\nAfter `calldataload`, the stack has the address parameter. After pushing the storage pointer, we have `[slot, address]`. If you see the wrong value at position 0 on the stack, you know the bug is in how the storage slot is computed.\n\n## Understanding Opcode Traces\n\nFor production debugging (when you cannot reproduce the issue locally), raw opcode traces from archive nodes are your primary tool. Services like Tenderly, Etherscan, and Alchemy provide trace APIs:\n\n```bash\n# Get trace via cast\ncast run TX_HASH --rpc-url $RPC -vvvvv 2>&1 | head -200\n```\n\nThe verbose trace format shows each opcode with gas cost and stack state:\n\n```\n[0] PUSH1 0x00          gas: 29234  stack: []\n[2] CALLDATALOAD        gas: 29231  stack: [0x00]\n[3] PUSH1 0xe0          gas: 29228  stack: [0xa9059cbb...]\n[5] SHR                 gas: 29225  stack: [0xa9059cbb..., 0xe0]\n[6] DUP1                gas: 29222  stack: [0xa9059cbb]\n[7] PUSH4 0x70a08231    gas: 29219  stack: [0xa9059cbb, 0xa9059cbb]\n[12] EQ                 gas: 29216  stack: [0xa9059cbb, 0xa9059cbb, 0x70a08231]\n[13] PUSH2 0x0040       gas: 29213  stack: [0xa9059cbb, 0x00]\n[16] JUMPI              gas: 29210  stack: [0xa9059cbb, 0x00, 0x0040]\n```\n\nThis trace shows the function dispatcher checking if the selector matches `balanceOf(address)`. The `EQ` produces `0x00` (false) because the actual selector is `0xa9059cbb` (transfer), so `JUMPI` does not jump.\n\n## Common Debugging Patterns for Huff and Yul\n\n### Pattern 1: Stack Underflow\n\nIf execution reverts with an out-of-gas error at a seemingly cheap opcode, you likely have a stack underflow. The EVM does not have a dedicated \"stack underflow\" error — it just consumes all gas.\n\n```huff\n\u002F\u002F Bug: pop when stack is empty\n#define macro BROKEN() = takes (0) returns (0) {\n    pop  \u002F\u002F Stack underflow! No items to pop\n}\n```\n\nDetection: In `forge debug`, watch the stack panel. If it shows 0 items before a consuming opcode, that is your bug.\n\n### Pattern 2: Incorrect JUMP Destination\n\nHuff uses labels for jump destinations. If a label resolves to a non-`JUMPDEST` opcode, the transaction reverts:\n\n```huff\n#define macro MAIN() = takes (0) returns (0) {\n    0x01 success jumpi\n    0x00 0x00 revert\n    success:                    \u002F\u002F Must be JUMPDEST\n        0x00 0x00 return\n}\n```\n\nDetection: In the trace, look for `JUMP` or `JUMPI` followed by immediate gas exhaustion. The target PC is on top of the stack before the jump.\n\n### Pattern 3: Incorrect ABI Encoding\n\nHuff does not auto-encode return values. If you return raw bytes without proper ABI encoding, the calling contract's decoder will revert:\n\n```huff\n\u002F\u002F Wrong: returning raw uint256 without offset\n0x00 mstore\n0x20 0x00 return\n\n\u002F\u002F Correct for dynamic types: include offset\n0x20 0x00 mstore     \u002F\u002F offset\n0x05 0x20 mstore     \u002F\u002F length\n\u002F\u002F ... data at 0x40\n```\n\nDetection: The calling contract's `abi.decode` reverts. The trace shows a successful return from your contract but a revert in the parent context.\n\n### Pattern 4: Storage Collision\n\nHuff uses `FREE_STORAGE_POINTER()` to allocate storage slots. If two macros accidentally use the same slot, they overwrite each other:\n\n```huff\n#define constant BALANCES_SLOT = FREE_STORAGE_POINTER()  \u002F\u002F slot 0\n#define constant ALLOWANCES_SLOT = FREE_STORAGE_POINTER() \u002F\u002F slot 1\n#define constant TOTAL_SUPPLY_SLOT = FREE_STORAGE_POINTER() \u002F\u002F slot 2\n```\n\nDetection: In `forge debug`, watch the storage panel. If writing to one mapping changes another variable, you have a collision.\n\n## Building a Debugging Workflow\n\nHere is a systematic approach to debugging Huff contracts:\n\n1. **Reproduce** — Write a failing test in Foundry that triggers the bug\n2. **Trace** — Run with `-vvvvv` to get the full opcode trace\n3. **Narrow** — Identify the exact opcode where behavior diverges from expectation\n4. **Compare** — Run the same scenario against your Solidity reference implementation\n5. **Fix** — Correct the Huff macro and verify the differential test passes\n6. **Regress** — Add the failing case to your permanent test suite\n\n```bash\n# Step 1: Run the failing test\nforge test --match-test test_brokenTransfer -vvvvv\n\n# Step 2: Interactive debugging\nforge debug --debug test\u002FToken.t.sol --sig \"test_brokenTransfer()\"\n\n# Step 3: After fix, verify\nforge test --match-contract DifferentialTest\nforge snapshot --check\n```\n\n## Production Debugging with Tenderly\n\nFor contracts already deployed, Tenderly provides a visual debugger that shows the execution trace with decoded function calls, state changes, and gas usage:\n\n```bash\n# Export transaction for analysis\ncast run TX_HASH --rpc-url $RPC --json > trace.json\n\n# Or use Tenderly's API directly\ncurl -X POST \"https:\u002F\u002Fapi.tenderly.co\u002Fapi\u002Fv1\u002Faccount\u002FYOU\u002Fproject\u002FPROJ\u002Fsimulate\" \\\n  -H \"X-Access-Key: $TENDERLY_KEY\" \\\n  -d '{ \"network_id\": \"1\", \"from\": \"0x...\", \"to\": \"0x...\", \"input\": \"0x...\" }'\n```\n\nTenderly's visual debugger is especially useful for Huff because it annotates each opcode with its effect on the stack, letting you spot errors without manually tracking stack state.\n\n## Conclusion\n\nDebugging EVM bytecode is a skill that separates hobbyist Huff developers from production-ready ones. Master `cast run` for quick transaction replay, `forge debug` for interactive analysis, and manual trace reading for production incidents. Build a systematic workflow: reproduce, trace, narrow, compare, fix, regress. The lower you go in the EVM stack, the more disciplined your debugging process must be.","\u003Ch2 id=\"the-debugging-challenge-with-low-level-evm-code\">The Debugging Challenge with Low-Level EVM Code\u003C\u002Fh2>\n\u003Cp>When a Solidity transaction reverts, you typically get a descriptive error message like \u003Ccode>ERC20: transfer amount exceeds balance\u003C\u002Fcode>. When a Huff or Yul transaction reverts, you get \u003Ccode>0x\u003C\u002Fcode> — an empty revert payload with zero context. The contract simply hit a \u003Ccode>REVERT\u003C\u002Fcode> opcode, and it is up to you to figure out why.\u003C\u002Fp>\n\u003Cp>Debugging at the bytecode level requires different tools and mental models. You need to think in terms of the stack machine, track memory and storage changes opcode by opcode, and understand how the EVM executes control flow through \u003Ccode>JUMP\u003C\u002Fcode> and \u003Ccode>JUMPI\u003C\u002Fcode> instructions.\u003C\u002Fp>\n\u003Cp>This article covers the essential debugging toolkit: \u003Ccode>cast run\u003C\u002Fcode> for replaying historical transactions, \u003Ccode>forge debug\u003C\u002Fcode> for interactive step-through debugging, and manual trace analysis for understanding exactly what happened inside the EVM.\u003C\u002Fp>\n\u003Ch2 id=\"cast-run-replaying-transactions\">cast run: Replaying Transactions\u003C\u002Fh2>\n\u003Cp>\u003Ccode>cast run\u003C\u002Fcode> is the fastest way to debug a failed transaction. It replays the transaction against the historical state and shows you exactly what happened:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">cast run 0xYOUR_TX_HASH --rpc-url https:\u002F\u002Feth-mainnet.g.alchemy.com\u002Fv2\u002FKEY\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The output shows a structured trace with call depth, gas usage, and return data:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>Traces:\n  [328439] 0xContractAddr::transfer(0xRecipient, 1000000000000000000)\n    +- [2604] 0xContractAddr::balanceOf(0xSender) [staticcall]\n    |   +- &lt;- 500000000000000000\n    +- &lt;- revert: EvmError: Revert\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This immediately tells you the transfer failed because the sender had 0.5 ETH but tried to transfer 1.0 ETH. For Huff contracts, the function names will not be decoded (they appear as raw selectors), but the call structure and revert points are still visible.\u003C\u002Fp>\n\u003Ch3>Decoding Raw Selectors\u003C\u002Fh3>\n\u003Cp>When working with Huff contracts, \u003Ccode>cast run\u003C\u002Fcode> shows raw function selectors. Decode them manually:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># Compute selector for balanceOf(address)\ncast sig \"balanceOf(address)\"\n# Output: 0x70a08231\n\n# Or decode calldata\ncast 4byte-decode 0x70a0823100000000000000000000000042069abcdef\n# Output: balanceOf(address)(0x42069abcdef)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Keep a reference table of your contract’s selectors when debugging Huff:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>0x70a08231 -&gt; balanceOf(address)\n0xa9059cbb -&gt; transfer(address,uint256)\n0x23b872dd -&gt; transferFrom(address,address,uint256)\n0x095ea7b3 -&gt; approve(address,uint256)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"forge-debug-interactive-step-through\">forge debug: Interactive Step-Through\u003C\u002Fh2>\n\u003Cp>\u003Ccode>forge debug\u003C\u002Fcode> provides a TUI (terminal user interface) for stepping through EVM execution opcode by opcode:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">forge debug --debug test\u002FSimpleToken.t.sol \\\n  --sig \"test_transfer()\" -vvvv\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The interface shows four panels:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Opcodes\u003C\u002Fstrong> — The current instruction with a cursor, showing the bytecode being executed\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Stack\u003C\u002Fstrong> — The current stack state with all 32-byte words\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Memory\u003C\u002Fstrong> — Raw memory contents in hex\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Storage\u003C\u002Fstrong> — Storage slot changes during execution\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Navigation keys:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>j\u002Fk\u003C\u002Fcode> — Step forward\u002Fbackward\u003C\u002Fli>\n\u003Cli>\u003Ccode>g\u002FG\u003C\u002Fcode> — Jump to start\u002Fend\u003C\u002Fli>\n\u003Cli>\u003Ccode>c\u003C\u002Fcode> — Continue to next call boundary\u003C\u002Fli>\n\u003Cli>\u003Ccode>C\u003C\u002Fcode> — Continue to next test\u003C\u002Fli>\n\u003Cli>\u003Ccode>q\u003C\u002Fcode> — Quit\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Reading the Stack During Debugging\u003C\u002Fh3>\n\u003Cp>The EVM stack is last-in-first-out with a maximum depth of 1024. When debugging Huff, you must track the stack mentally to understand what each opcode consumes and produces.\u003C\u002Fp>\n\u003Cp>Consider this Huff snippet:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">0x04 calldataload   \u002F\u002F Stack: [address]\nBALANCES_SLOT       \u002F\u002F Stack: [slot, address]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>After \u003Ccode>calldataload\u003C\u002Fcode>, the stack has the address parameter. After pushing the storage pointer, we have \u003Ccode>[slot, address]\u003C\u002Fcode>. If you see the wrong value at position 0 on the stack, you know the bug is in how the storage slot is computed.\u003C\u002Fp>\n\u003Ch2 id=\"understanding-opcode-traces\">Understanding Opcode Traces\u003C\u002Fh2>\n\u003Cp>For production debugging (when you cannot reproduce the issue locally), raw opcode traces from archive nodes are your primary tool. Services like Tenderly, Etherscan, and Alchemy provide trace APIs:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># Get trace via cast\ncast run TX_HASH --rpc-url $RPC -vvvvv 2&gt;&amp;1 | head -200\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The verbose trace format shows each opcode with gas cost and stack state:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>[0] PUSH1 0x00          gas: 29234  stack: []\n[2] CALLDATALOAD        gas: 29231  stack: [0x00]\n[3] PUSH1 0xe0          gas: 29228  stack: [0xa9059cbb...]\n[5] SHR                 gas: 29225  stack: [0xa9059cbb..., 0xe0]\n[6] DUP1                gas: 29222  stack: [0xa9059cbb]\n[7] PUSH4 0x70a08231    gas: 29219  stack: [0xa9059cbb, 0xa9059cbb]\n[12] EQ                 gas: 29216  stack: [0xa9059cbb, 0xa9059cbb, 0x70a08231]\n[13] PUSH2 0x0040       gas: 29213  stack: [0xa9059cbb, 0x00]\n[16] JUMPI              gas: 29210  stack: [0xa9059cbb, 0x00, 0x0040]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This trace shows the function dispatcher checking if the selector matches \u003Ccode>balanceOf(address)\u003C\u002Fcode>. The \u003Ccode>EQ\u003C\u002Fcode> produces \u003Ccode>0x00\u003C\u002Fcode> (false) because the actual selector is \u003Ccode>0xa9059cbb\u003C\u002Fcode> (transfer), so \u003Ccode>JUMPI\u003C\u002Fcode> does not jump.\u003C\u002Fp>\n\u003Ch2 id=\"common-debugging-patterns-for-huff-and-yul\">Common Debugging Patterns for Huff and Yul\u003C\u002Fh2>\n\u003Ch3>Pattern 1: Stack Underflow\u003C\u002Fh3>\n\u003Cp>If execution reverts with an out-of-gas error at a seemingly cheap opcode, you likely have a stack underflow. The EVM does not have a dedicated “stack underflow” error — it just consumes all gas.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">\u002F\u002F Bug: pop when stack is empty\n#define macro BROKEN() = takes (0) returns (0) {\n    pop  \u002F\u002F Stack underflow! No items to pop\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Detection: In \u003Ccode>forge debug\u003C\u002Fcode>, watch the stack panel. If it shows 0 items before a consuming opcode, that is your bug.\u003C\u002Fp>\n\u003Ch3>Pattern 2: Incorrect JUMP Destination\u003C\u002Fh3>\n\u003Cp>Huff uses labels for jump destinations. If a label resolves to a non-\u003Ccode>JUMPDEST\u003C\u002Fcode> opcode, the transaction reverts:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">#define macro MAIN() = takes (0) returns (0) {\n    0x01 success jumpi\n    0x00 0x00 revert\n    success:                    \u002F\u002F Must be JUMPDEST\n        0x00 0x00 return\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Detection: In the trace, look for \u003Ccode>JUMP\u003C\u002Fcode> or \u003Ccode>JUMPI\u003C\u002Fcode> followed by immediate gas exhaustion. The target PC is on top of the stack before the jump.\u003C\u002Fp>\n\u003Ch3>Pattern 3: Incorrect ABI Encoding\u003C\u002Fh3>\n\u003Cp>Huff does not auto-encode return values. If you return raw bytes without proper ABI encoding, the calling contract’s decoder will revert:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">\u002F\u002F Wrong: returning raw uint256 without offset\n0x00 mstore\n0x20 0x00 return\n\n\u002F\u002F Correct for dynamic types: include offset\n0x20 0x00 mstore     \u002F\u002F offset\n0x05 0x20 mstore     \u002F\u002F length\n\u002F\u002F ... data at 0x40\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Detection: The calling contract’s \u003Ccode>abi.decode\u003C\u002Fcode> reverts. The trace shows a successful return from your contract but a revert in the parent context.\u003C\u002Fp>\n\u003Ch3>Pattern 4: Storage Collision\u003C\u002Fh3>\n\u003Cp>Huff uses \u003Ccode>FREE_STORAGE_POINTER()\u003C\u002Fcode> to allocate storage slots. If two macros accidentally use the same slot, they overwrite each other:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-huff\">#define constant BALANCES_SLOT = FREE_STORAGE_POINTER()  \u002F\u002F slot 0\n#define constant ALLOWANCES_SLOT = FREE_STORAGE_POINTER() \u002F\u002F slot 1\n#define constant TOTAL_SUPPLY_SLOT = FREE_STORAGE_POINTER() \u002F\u002F slot 2\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Detection: In \u003Ccode>forge debug\u003C\u002Fcode>, watch the storage panel. If writing to one mapping changes another variable, you have a collision.\u003C\u002Fp>\n\u003Ch2 id=\"building-a-debugging-workflow\">Building a Debugging Workflow\u003C\u002Fh2>\n\u003Cp>Here is a systematic approach to debugging Huff contracts:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Reproduce\u003C\u002Fstrong> — Write a failing test in Foundry that triggers the bug\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Trace\u003C\u002Fstrong> — Run with \u003Ccode>-vvvvv\u003C\u002Fcode> to get the full opcode trace\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Narrow\u003C\u002Fstrong> — Identify the exact opcode where behavior diverges from expectation\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Compare\u003C\u002Fstrong> — Run the same scenario against your Solidity reference implementation\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Fix\u003C\u002Fstrong> — Correct the Huff macro and verify the differential test passes\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Regress\u003C\u002Fstrong> — Add the failing case to your permanent test suite\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode class=\"language-bash\"># Step 1: Run the failing test\nforge test --match-test test_brokenTransfer -vvvvv\n\n# Step 2: Interactive debugging\nforge debug --debug test\u002FToken.t.sol --sig \"test_brokenTransfer()\"\n\n# Step 3: After fix, verify\nforge test --match-contract DifferentialTest\nforge snapshot --check\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"production-debugging-with-tenderly\">Production Debugging with Tenderly\u003C\u002Fh2>\n\u003Cp>For contracts already deployed, Tenderly provides a visual debugger that shows the execution trace with decoded function calls, state changes, and gas usage:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># Export transaction for analysis\ncast run TX_HASH --rpc-url $RPC --json &gt; trace.json\n\n# Or use Tenderly's API directly\ncurl -X POST \"https:\u002F\u002Fapi.tenderly.co\u002Fapi\u002Fv1\u002Faccount\u002FYOU\u002Fproject\u002FPROJ\u002Fsimulate\" \\\n  -H \"X-Access-Key: $TENDERLY_KEY\" \\\n  -d '{ \"network_id\": \"1\", \"from\": \"0x...\", \"to\": \"0x...\", \"input\": \"0x...\" }'\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Tenderly’s visual debugger is especially useful for Huff because it annotates each opcode with its effect on the stack, letting you spot errors without manually tracking stack state.\u003C\u002Fp>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>Debugging EVM bytecode is a skill that separates hobbyist Huff developers from production-ready ones. Master \u003Ccode>cast run\u003C\u002Fcode> for quick transaction replay, \u003Ccode>forge debug\u003C\u002Fcode> for interactive analysis, and manual trace reading for production incidents. Build a systematic workflow: reproduce, trace, narrow, compare, fix, regress. The lower you go in the EVM stack, the more disciplined your debugging process must be.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.119048Z","Debugging EVM Bytecode — Traces, Stack Dumps, and cast run","Master EVM bytecode debugging with cast run, forge debug, and raw opcode trace analysis for Huff and Yul smart contracts.","debugging evm bytecode",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000021","Foundry","foundry",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000017","Huff","huff","Blockchain",[37,43,49],{"id":38,"title":39,"slug":40,"excerpt":41,"locale":12,"category_name":35,"published_at":42},"de000000-0000-0000-0000-000000000003","The Ethereum Interoperability Layer: How 55+ L2s Become One Chain","ethereum-interoperability-layer-how-55-l2s-become-one-chain","Ethereum has 55+ Layer 2 rollups, fragmenting liquidity and user experience. The Ethereum Interoperability Layer — combining cross-rollup messaging, shared sequencers, and based rollups — aims to unify them into a single composable network.","2026-03-28T10:44:35.632478Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":35,"published_at":48},"de000000-0000-0000-0000-000000000002","ZK Proofs Beyond Rollups: Verifiable AI Inference on Ethereum","zk-proofs-beyond-rollups-verifiable-ai-inference-ethereum","Zero-knowledge proofs are no longer just a scaling tool. In 2026, zkML enables verifiable AI inference on-chain, ZK coprocessors move heavy computation off-chain with on-chain verification, and new proving systems like SP1 and Jolt make it practical.","2026-03-28T10:44:35.618408Z",{"id":50,"title":51,"slug":52,"excerpt":53,"locale":12,"category_name":35,"published_at":54},"dd000000-0000-0000-0000-000000000003","EIP-7702 in Practice: Building Smart Account Flows After Pectra","eip-7702-in-practice-building-smart-account-flows-after-pectra","EIP-7702 lets any Ethereum EOA temporarily act as a smart contract within a single transaction. Here is how to implement batch transactions, gas sponsorship, and social recovery using the new account abstraction primitive.","2026-03-28T10:44:35.031290Z",{"id":13,"name":56,"slug":57,"bio":58,"photo_url":19,"linkedin":19,"role":59,"created_at":60,"updated_at":60},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]