Foundry分叉测试实践
主要是测试https://solidity-by-example.org/defi/uniswap-v2-add-remove-liquidity/中的这个例子,向Uniswap V2的WETH/USDT流动性池添加或删除流动性。
分叉测试方法
使用foundry新建一个项目: forge init AddRemoveLiquidity
合约源代码放在src目录,命名为UniswapV2Liquidity.sol,测试代码放在test目录,命名为UniswapV2Liquidity.t.sol ,
另外,为了使得调试信息更为直观,使用了console.sol
进行日志输出:
- 合约中引入,
import "forge-std/console.sol"
; foundry.toml
配置文件中启用 ffi = true ,允许 Foundry 运行本地命令和输出日志。- forge test
-vvvv
选项来启用更高的日志级别,这样调试信息才能被打印出来。
最后,运行forge test -vvvv --fork-url https://eth-mainnet.g.alchemy.com/v2/API_KEY --fork-block-number 20623798
,fork主网,设置指定区块号,执行分叉测试。
测试sol文件
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/UniswapV2Liquidity.sol";
IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 constant USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
IERC20 constant PAIR = IERC20(0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852);
contract UniswapV2AddLiquidityTest is Test {
UniswapV2AddLiquidity private uni = new UniswapV2AddLiquidity();
// Add WETH/USDT Liquidity to Uniswap
function testAddLiquidity() public {
// Deal test USDT and WETH to this contract
deal(address(USDT), address(this), 1e6 * 1e6);
assertEq(USDT.balanceOf(address(this)), 1e6 * 1e6, "USDT balance incorrect");
deal(address(WETH), address(this), 1e6 * 1e18);
assertEq(WETH.balanceOf(address(this)), 1e6 * 1e18, "WETH balance incorrect");
console.log("\u6dfb\u52a0\u6d41\u52a8\u6027\u6d4b\u8bd5...");
console.log("UniswapV2AddLiquidityTest USDT balance: %s" , USDT.balanceOf(address(this)) / 1e6);
console.log("UniswapV2AddLiquidityTest WETH balance: %s" , WETH.balanceOf(address(this)) / 1e18);
// Approve uni for transferring
safeApprove(WETH, address(uni), 1e64);
safeApprove(USDT, address(uni), 1e64);
uni.addLiquidity(address(WETH), address(USDT), 1 * 1e18, 2435 * 1e6);
console.log("addLiquidity...");
assertGt(PAIR.balanceOf(address(uni)), 0, "pair balance 0");
console.log("UniswapV2Liquidity WETH/USDT LP Token: %s", PAIR.balanceOf(address(uni)));
}
// Remove WETH/USDT Liquidity from Uniswap
function testRemoveLiquidity() public {
// Deal LP tokens to uni
deal(address(PAIR), address(uni), 1910 * 1e10); //大约是testAddLiquidity()中从池子里得到的LP Token的数量
assertEq(PAIR.balanceOf(address(uni)), 1910 * 1e10, "LP tokens balance = 0");
assertEq(USDT.balanceOf(address(uni)), 0, "USDT balance non-zero");
assertEq(WETH.balanceOf(address(uni)), 0, "WETH balance non-zero");
console.log("\u5220\u9664\u6d41\u52a8\u6027\u6d4b\u8bd5...");
console.log("UniswapV2Liquidity WETH/USDT LP Token: %s", PAIR.balanceOf(address(uni)));
console.log("UniswapV2Liquidity USDT balance: %s", USDT.balanceOf(address(uni)) / 1e6);
console.log("UniswapV2Liquidity WETH balance: %s", WETH.balanceOf(address(uni)) / 1e18);
uni.removeLiquidity(address(WETH), address(USDT));
console.log("removeLiquidity...");
assertEq(PAIR.balanceOf(address(uni)), 0, "LP tokens balance != 0");
assertGt(USDT.balanceOf(address(uni)), 0, "USDT balance = 0");
assertGt(WETH.balanceOf(address(uni)), 0, "WETH balance = 0");
console.log("UniswapV2Liquidity WETH/USDT LP Token: %s", PAIR.balanceOf(address(uni)));
console.log("UniswapV2Liquidity USDT balance: %s", USDT.balanceOf(address(uni)) / 1e6);
console.log("UniswapV2Liquidity WETH balance: %s", WETH.balanceOf(address(uni)) );
}
/**
* @dev The transferFrom function may or may not return a bool.
* The ERC-20 spec returns a bool, but some tokens don't follow the spec.
* Need to check if data is empty or true.
*/
function safeTransferFrom(
IERC20 token,
address sender,
address recipient,
uint256 amount
) internal {
(bool success, bytes memory returnData) = address(token).call(
abi.encodeCall(IERC20.transferFrom, (sender, recipient, amount))
);
require(
success
&& (returnData.length == 0 || abi.decode(returnData, (bool))),
"Transfer from fail"
);
}
/**
* @dev The approve function may or may not return a bool.
* The ERC-20 spec returns a bool, but some tokens don't follow the spec.
* Need to check if data is empty or true.
*/
function safeApprove(IERC20 token, address spender, uint256 amount)
internal
{
(bool success, bytes memory returnData) = address(token).call(
abi.encodeCall(IERC20.approve, (spender, amount))
);
require(
success
&& (returnData.length == 0 || abi.decode(returnData, (bool))),
"Approve fail"
);
}
}
运行结果:
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.27
[⠑] Solc 0.8.27 finished in 1.51s
Ran 2 tests for test/UniswapV2Liquidity.t.sol:UniswapV2AddLiquidityTest
[PASS] testAddLiquidity() (gas: 575384)
Logs:
添加流动性测试...
UniswapV2AddLiquidityTest USDT balance: 1000000
UniswapV2AddLiquidityTest WETH balance: 1000000
addLiquidity...
UniswapV2Liquidity WETH/USDT LP Token: 19108473809684
Traces:
[724496] UniswapV2AddLiquidityTest::testAddLiquidity()
......
[PASS] testRemoveLiquidity() (gas: 353196)
Logs:
删除流动性测试...
UniswapV2Liquidity WETH/USDT LP Token: 19100000000000
UniswapV2Liquidity USDT balance: 0
UniswapV2Liquidity WETH balance: 0
removeLiquidity...
UniswapV2Liquidity WETH/USDT LP Token: 0
UniswapV2Liquidity USDT balance: 2433
UniswapV2Liquidity WETH balance: 996767067571357036
Traces:
[446760] UniswapV2AddLiquidityTest::testRemoveLiquidity()
......
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.24ms (12.78ms CPU time)
Ran 1 test suite in 1.07s (9.24ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)
测试结果说明与分析
分叉以太坊主网20623798号区块进行测试。
- 添加流动性:
- 先是用
vm.deal
往UniswapV2AddLiquidityTest
打了1000000USDT
和1000000WETH
- 经过两次
approve
和transferFrom
的授权转移组合操作,使得2435USDT
和1WETH
资金,从UniswapV2AddLiquidityTest
->UniswapV2AddLiquidity
->router
这样一个地址路径,最终进入到了uni v2的池子里,完成了添加流动性,并兑换出了19108473809684个LP Token
- 删除流动性:
- 用vm.deal往UniswapV2AddLiquidity打了1910*1e10个
LP Token
- 用
removeLiquidity
函数,销毁上述数量的LP Token
,完成删除流动性,兑回了2433USDT
和0.996767WETH