Debugging Contracts with Foundry
What is Foundry
Foundry is a Solidity Framework for building, testing, fuzzing, debugging and deploying Solidity smart contracts. It can manage dependencies, compile your project, run tests, deploy contracts, and lets you interact with the chain from the command-line and via Solidity scripts.Foundry comes with the following tools that can be used to debugging:
Anvil: Used to deploy a local Ethereum node
Cast: Used to send RPC calls and interact with the chain
Forge: Used to build, compile, and test smart contracts
You can install Foundry by running the following commands:
Linux/Mac:
Windows: (Requires Rust, install from https://rustup.rs/)
Docker:
More detailed instructions on installation available here:
Anvil
Anvil deploys a local Ethereum node in your environment that can be used to deploy a node. Simply run anvil to deploy one.
It has a few useful properties. It allows you to fork a network at the latest, or any specific height. You can run the following command to fork the Mantle mainnet environment for debugging your code:
Call traces are more detailed, i.e., we can see which functions are being executed in remote contracts, i.e., the functions that executed by every contract.
Note that it isn't necessary to use an Anvil node to use Foundry. You can use other local nodes like Hardhat, Ganache, etc. as well.
Cast
You can use the following cast commands to interact with the chain.
The examples below are based on Anvil. If you have Anvil up and running, try invoking the following cases in another command line window. This will allow you to debug online contracts using the mantle mainnet.
cast call / cast send
cast send can be used to sign and publish a transaction on the chain.You can use it make an arbitrary contract method call, like so:
cast send $contract_address "someFunc(unit256)" 0x... \--private-key $wallet_private_key
cast call on the other hand can be used to perform an account call without publishing it to the chain, and that's why you don't need to specify a private key.So for instance, you can make a contract method call, like so:
cast call $contract_address "someFunc(uint, args[])" $number "[arg_1, ... , arg_n]"
cast run
cast run can be used to run a published transaction in a local environment and print the trace. For example:
cast run $TX_HASH
You can also replay transactions in the debugger using the --debug
flag, like so:
cast run --debug $TX_HASH
Taking this deposit transaction as an example:
Learn more about the debugger interface here: https://book.getfoundry.sh/forge/debugger
cast block / cast tx
cast block
can be used to fetch block information from the chain. Running cast block
returns block info for the latest block.
cast tx
can be used to fetch transaction information from the chain using the transaction hash. The command structure is as follows:
cast tx $TX_HASH
cast rpc
cast rpc
can be used to send a raw JSON-RPC request to a node.Since Mantle Network supports Ethereum's JSON-RPC interface, you can use the following command to call eth_getBlockByNumber
and fetch information for the latest block:
cast rpc --rpc-url https://rpc.mantle.xyz/ eth_getBlockByNumber "latest" "false"
cast storage & cast index
cast storage
can be used to fetch the raw value of a contract's storage slot. cast-index can compute the storage slot location for an entry in a mapping.The cast index calculates the storage location based on the KEY_TYPE, KEY, and SLOT_NUMBER.For example, to fetch the value of slot 0 for a contract:
cast storage $contract_address 0
We'll take the balance of account 0x1858d52cf57c07A018171D7a1E68DC081F17144f about $WMNT as an example, which can be obtained in two ways:
Conventional function call method This has already been implemented in the aforementioned cast call.
Reading contract slot storage method First, based on the WMNT source code, it's determined that the balanceOf state variable is located at the 0th slot with KEY_TYPE being 'address'.
cast abi-decode / cast abi-encode
cast abi-decode
can be used to decode any ABI-encoded data. For example, to decode the output data for a balanceOf
call:
cast abi-decode "balanceOf(address)(uint256)" \0x000000000000000000000000000000000000000000000000000000000000000a
cast abi-encode
can be used to ABI encode any given function arguments, excluding the selector. For example, to ABI-encode the arguments for a function call:
cast abi-encode "someFunc(address,uint256)" 0x... 1
forge
5.1 Initialize the project.-forge init
forge init <dir_name>forge init —template <template_path> <dir_name>
View the current directory structure.
5.2 forge build
The corresponding compile command is
Typically, two panes are opened in tmux. The first pane is used to view real-time coding status, monitoring in real-time with the -w
option. In the second pane, code is written. After each code modification, once saved, the first pane will display in real-time whether the compilation has passed.
5.3 Automated testing-forge test
For more detailed procedures on testing, please refer to. https://book.getfoundry.sh/reference/forge/forge-test
Forge debug
Forge ships with an interactive debugger.
forge debug --debug $FILE --sig $FUNC
In the newly initialized project, we can enter the following command to enable the interactive debugger.
If you want to debug on the forked mainnet, you can enter the following command.
or
breakpoint
Places a breakpoint to jump to in the debugger view.Calling vm.breakpoint('<char>, true)
is equivalent to vm.breakpoint('<char>)
, but calling vm.breakpoint('<char, false)
will erase the breakpoint at '<char
.If the char is overwritten, only the last one that was visited in the execution steps is considered.
Example
Opening up the debugger in a test environment and pressing 'a
will then place the debugger step at the place where the breakpoint cheatcode was called.
By integrating Anvil and Cast, you can fork and test by interacting with contracts on the live network.
Last updated