Walkthrough: Debugging a Uniswap V3 Transaction on Mainnet
This short tutorial will walk you through the steps of debugging a Uniswap V3 transaction on Ethereum Mainnet. It requires access to a public Ethereum RPC endpoint which the debugger will fork the blockchain state from. You can find an RPC URL from Chainlist.
In this tutorial, we will write a test that will:
- Convert ETH to WETH using the WETH contract's
deposit()
method - Swap WETH for DAI using Uniswap V3 protocol
- Use the debugger to step into Uniswap V3's code to observe its inner workings
All while using the actual up-to-date Ethereum mainnet state. Let's begin!
Clone Uniswap from GitHub
To call Uniswap we'll need its Solidity interface files. We can fetch these from the Uniswap/v3-periphery
repository on GitHub.
$ git clone https://github.com/Uniswap/v3-periphery.git
$ cd v3-periphery
$ npm install # install dependencies
In order to be able to step into the Uniswap code, we need to compile its contracts and generate an artifacts
folder. The Solidity Debuger reads necessary metadata from these artifacts that helps it match deployed bytecode to source code. Run:
$ npx hardhat compile
Create a Debug Target for 'v3-periphery'
Open the v3-periphery
folder in VS Code. Then, from the Solidity Debugger menu click New Target
and select Create from currently-open Hardhat project
.
Choose a Solidity compiler for the test contract you are about to create. Uniswap was written using Solidity 0.7.6
, so that version is automatically selected. However, in this tutorial we will use the latest version. Select compiler version 0.8.17
, then click Create
.
Once target creation is complete, Test1.t.sol
will automatically open in the editor.
Configure Forking
Open the Settings Window and select the Test1
contract, then change the following settings under Debugging Test1
:
Setting | Value |
---|---|
Forking→Enabled | enabled |
Forking→Ethereum RPC URL | https://mainnet.infura.io/v3/YOUR-API-KEY |
You can get a URL for a free public node at chainlist.org.
Click OK
when you are done.
Performing a Swap
Replace source code of Test1.t.sol
with:
// SPDX-License-Identifier: MIT
pragma solidity >= 0.4.21 < 0.9.0;
import "contracts/interfaces/ISwapRouter.sol";
interface IERC20x {
function balanceOf(address account) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
interface IWETHx is IERC20x {
function deposit() external payable;
}
contract DbgEntry {
ISwapRouter public constant uniswapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
IWETHx private constant WETH = IWETHx(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20x DAI = IERC20x(0x6B175474E89094C44Da98b954EedeAC495271d0F);
// solidity debugger cheat code
event EvmSetBalance(address, uint256);
constructor() {
emit EvmSetBalance(address(this), 1000 ether);
// convert ETH to WETH
WETH.deposit{value: 2 ether}();
WETH.approve(address(uniswapRouter), type(uint256).max);
// get balances before swap
uint256 startBalanceETH = address(this).balance;
uint256 startBalanceDAI = DAI.balanceOf(address(this));
uint256 startBalanceWETH = WETH.balanceOf(address(this));
// swap WETH for DAI
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams(
address(WETH), // tokenIn
address(DAI), // tokenOut
3000, // fee
address(this), // recipient
block.timestamp, // deadline
1 ether, // amountIn
100, // amountOutMinimum
0 // sqrtPriceLimitX96
);
uint256 amountOut = uniswapRouter.exactInputSingle(params);
// get balances after swap
uint256 endBalanceETH = address(this).balance;
uint256 endBalanceWETH = WETH.balanceOf(address(this));
uint256 endBalanceDAI = DAI.balanceOf(address(this));
uint256 done = 1;
}
}
Put a breakpoint on the first line of the constructor (emit EvmSetBalance(address(this), 1000 ether);
) and click the Debug
button. You should be able to step through the transaction until it is done.
Stepping into Uniswap
Put a breakpoint on the call to uniswapRouter.exactInputSingle(params)
and start the debugger. When the breakpoint hits, try stepping into the call and you will find that you're able to step into Uniswap's internals!