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进行日志输出:

  1. 合约中引入,import "forge-std/console.sol";
  2. foundry.toml 配置文件中启用 ffi = true ,允许 Foundry 运行本地命令和输出日志。
  3. 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号区块进行测试。

  • 添加流动性:
  1. 先是用vm.dealUniswapV2AddLiquidityTest打了1000000 USDT和1000000 WETH
  2. 经过两次approvetransferFrom的授权转移组合操作,使得2435 USDT和1 WETH资金,从UniswapV2AddLiquidityTest -> UniswapV2AddLiquidity -> router这样一个地址路径,最终进入到了uni v2的池子里,完成了添加流动性,并兑换出了19108473809684个LP Token
  • 删除流动性:
  1. 用vm.deal往UniswapV2AddLiquidity打了1910*1e10个LP Token
  2. removeLiquidity函数,销毁上述数量的LP Token,完成删除流动性,兑回了2433 USDT和0.996767 WETH

posted on 2024-09-27 16:37  肥兔子爱豆畜子  阅读(23)  评论(0编辑  收藏  举报

导航