Documentation

Debug Solidity Transactions with Simbolik on BuildBear

Step-by-step guide to debug smart contract transactions using Simbolik’s symbolic execution debugger within BuildBear.

Introduction

Simbolik enables step-through debugging and variable inspection via symbolic execution, allowing developers to:

  • Explore edge cases with full symbolic path coverage
  • Debug up-to-date Solidity contracts with the latest EVM versions
  • Enjoy a clean and intuitive developer experience directly from the browser

What You Will Learn

In this tutorial, you'll learn how to:

  • Set up a Foundry-based smart contract project with BuildBear
  • Deploy and verify contracts using Sourcify on BuildBear
  • Use the Simbolik Plugin on BuildBear to symbolically debug your Solidity transactions in a cloud-hosted VS Code environment

1. Clone the Project

git clone https://github.com/BuildBearLabs/tutorial-simbolik-debugger-ERC-4337
cd foundry-erc-4337-account-abstraction
cp .env.example .env
forge build --via-ir

2. Set Up Your BuildBear Sandbox

Once the Sandbox is created, populate the .env and Makefile with your BuildBear Sandbox RPC and other required details.


3. Install the Sourcify & Simbolik Plugins

Simbolik relies on Sourcify-verified source code to debug contracts.

Before running any transactions:

  • Go to your Sandbox's Plugins tab
  • Look for Sourcify & Simbolik
  • Click Install Plugin

Sourcify Debugger Simbolik Debugger


4. Deploy and Verify Contracts

Inside scripts/HelperConfig.s.sol, you will need to update your BURNER_WALLET based on the mnemonic or private-key used. This wallet is used for deploying a smart account and sending userOps.

You can now deploy the contracts and submit the source code for verification using the make command:

make deploy-sourcify

This command does two things:

  • Deploys the contracts to your Sandbox
  • Automatically verifies them on Sourcify using the plugin

Once complete, you'll be able to debug these contracts with Simbolik.

Sourcify Verification Terminal


5. View and Debug the Transaction

Navigate to your Sandbox's Explorer tab:

  • Find your transaction in the list (e.g., deployment or test interaction) Placeholder :: tx-list image

  • Click on the Tx Hash to open transaction details, where you will now see a “Debug with Simbolik” button Placeholder :: tx-details image

Clicking this will:

  • Open a VS Code Cloud IDE
  • Launch Simbolik’s symbolic debugger view for that transaction

Placeholder :: tx-vscode-debug image


6. Use the Simbolik Debugger

The Simbolik debugger allows you to:

  • Step through each instruction of the transaction Placeholder :: simbolik-stepthrough image

  • Inspect variables and stack values Placeholder :: simbolik-vars image

  • Explore alternative code paths symbolically, and choose breakpoints for debugging like contract calls, events, jumps, etc.

This helps uncover edge cases that may not be hit during normal test execution.


7. Capturing a Failing Transaction with Hardhat

In addition to debugging a successful ERC-4337 transaction, you can also use Simbolik to inspect and understand a transaction that reverts. We’ll use a Hardhat project to deploy two contracts (Sender and Receiver) where a transfer call will fail due to the 2300-gas stipend limit. We use hardhat to prevent foundry from catching the reverts during the transaction simulation phase.

7.1 Project Setup

Install Hardhat and required packages:

npm i -D hardhat @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-verify dotenv

.env file:

SANDBOX_ID=<your-sandbox-id>
PRIVATE_KEY=0x<funded-private-key>

7.2 Hardhat Config Setup

require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-verify");
require("dotenv").config();

const { SANDBOX_ID, PRIVATE_KEY } = process.env;

module.exports = {
  solidity: {
    version: "0.8.24",
    settings: { optimizer: { enabled: false, runs: 200 } },
  },
  networks: {
    buildbear: {
      url: `https://rpc.buildbear.io/${SANDBOX_ID}`,
      accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
    },
  },
  sourcify: {
    enabled: true,
    apiUrl: `https://rpc.buildbear.io/verify/sourcify/server/${SANDBOX_ID}`,
  },
  etherscan: { enabled: false },
};

7.3 Contracts (contracts/Contracts.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Sender {
    Receiver public target;

    constructor() {
        target = new Receiver();
    }

    function call_receiver() external payable {
        payable(address(target)).transfer(msg.value);
    }
}

contract Receiver {
    uint256 private sink;

    event Fallback();
    event Receive();

    fallback() external payable { emit Fallback(); }

    receive() external payable {
        sink = block.number; // will cause revert due to 2300 gas stipend
        emit Receive();
    }
}

7.4 Hardhat Script: Deployment + Verification + Failing Tx

// scripts/deploy-verify-revert.js
const { ethers, run } = require("hardhat");
const { getCreateAddress } = require("ethers"); // v6
const sleep = ms => new Promise(r => setTimeout(r, ms));

async function main() {
  const [deployer] = await ethers.getSigners();
  const net = await deployer.provider.getNetwork();
  console.log("Deployer:", await deployer.getAddress());
  console.log("Network:", net.name, "chainId:", net.chainId.toString());

  // 1) Deploy Sender (constructor deploys Receiver)
  const Sender = await ethers.getContractFactory("Sender");
  console.log("Deploying Sender...");
  const sender = await Sender.deploy();
  await sender.waitForDeployment();
  const senderAddr = await sender.getAddress();
  const deployTx = sender.deploymentTransaction().hash;
  console.log("Sender:", senderAddr, "| deploy tx:", deployTx);

  // 2) Get Receiver address
  let receiverAddr;
  try {
    receiverAddr = await sender.target(); // works if 'Receiver public target;' and fresh compile
  } catch {
    // Fallback: first CREATE from Sender has nonce = 1
    receiverAddr = getCreateAddress({ from: senderAddr, nonce: 1 });
  }
  console.log("Receiver:", receiverAddr);

  // Give Sourcify a moment to see bytecode
  await sleep(4000);

  // 3) Verify on Sourcify via hardhat-verify (points to BuildBear proxy in hardhat.config.js)
  console.log("\nVerifying Sender on Sourcify…");
  await run("verify:verify", {
    address: senderAddr,
    contract: "contracts/Contracts.sol:Sender",
    constructorArguments: [],
  });

  console.log("Verifying Receiver on Sourcify…");
  await run("verify:verify", {
    address: receiverAddr,
    contract: "contracts/Contracts.sol:Receiver",
    constructorArguments: [],
  });

  // 4) Fire the tx that MUST revert (requires your Receiver.receive() to do an SSTORE)
  try {
    console.log("\nCalling call_receiver() with 1 wei (expected REVERT)...");
    const tx = await sender.call_receiver({ value: 1n });
    console.log("Sent:", tx.hash, "— waiting…");
    const rcpt = await tx.wait();
    console.log("Unexpected success:", rcpt.status);
  } catch (e) {
    // console.error(e);
    console.error("\n💥 Revert captured (expected).");
    if (e.transaction) console.error("tx:", e.receipt.hash);
    console.error("message:", e.shortMessage || e.message || e);
  }
}

main().catch(e => {
  console.error(e);
  process.exit(1);
});

Execute it using Hardhat:

npx hardhat compile
npx hardhat run scripts/deploy-verify-revert.js --network buildbear

tx-revert-hardhat

Both can be opened in Simbolik from the BuildBear Explorer.

tx-revert-hardhat

We can open this in Simbolik Debugger to inspect the tx and the reason for the revert.

simbolik-details

The transaction debug halts at the line as shown in the image below, because the sink variable is being set, which requires more than 2300 gas:

We can do a debug_traceTransaction on the Tx-Hash to see the trace of the transaction and the reason for the revert.

Alternatively you can also use cURL to get the trace of the transaction:

curl -X POST https://rpc.buildbear.io/SANDBOX_ID \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "debug_traceTransaction",
    "params": [
      "TX_HASH",
      {
        "disableStorage": false,
        "disableMemory": false,
        "disableStack": false
      }
    ]
  }'

Conclusion

You've now set up Simbolik on a BuildBear Sandbox and used it to debug Solidity transactions with full symbolic execution.

Simbolik helps you breakdown complex transactions that are difficult to understand and also allows you to explore edge cases that may cause reverts or transaction failures during transaction execution.

For questions or help, reach out to [email protected] or visit our Plugin Marketplace to explore more debugging tools.