精通 Hardhat 与 Foundry 的智能合约全面测试策略

掌握基于 Hardhat 和 Foundry 的完整测试工作流,涵盖单元测试、集成测试、Gas 优化验证、主网分叉仿真及 CI/CD 自动化部署流程,确保智能合约安全上线。

为什么需要这个技能

智能合约一旦部署到主网,修改成本极高且不可逆转。在发布前,仅仅通过静态代码审查无法发现逻辑漏洞或边界条件错误。专业的测试策略能够模拟真实用户操作、极端 Gas 费用环境以及潜在的攻击向量。本技能指导开发者利用 Hardhat 和 Foundry 生态,构建从单元测试到主网分叉仿真的完整验证体系,显著降低资金损失风险。

适用场景

  • 智能合约开发与审计:编写单元测试,确保逻辑符合预期,并验证复杂的状态变更。
  • Gas 费用优化:通过对比不同实现的 Gas 消耗,寻找优化空间,减少用户调用成本。
  • 极端场景验证:利用 Fuzzing 和边界条件测试,发现常规用例难以触发的异常路径。
  • 真实环境仿真:分叉主网数据,在不消耗真实资金的情况下模拟真实的交易环境和网络延迟。
  • CI/CD 流水线集成:将测试用例自动接入 GitHub Actions,每次提交即触发全量验证。

核心工作流

1. 基础配置与工具链选择

根据团队偏好选择 Hardhat(JS/TS,生态丰富)或 Foundry(Rust,速度快)。配置好 hardhat.config.jsfoundry.toml,接入 Etherscan 接口以验证合约,并开启 Gas 报告功能。

2. 单元测试与 Fixture 模式

利用 loadFixture 避免重复部署代码。在 describe 块中编写针对部署、转账失败、事件触发等场景的测试。

const { expect } = require("chai");
const { ethers } = require("hardhat");
const {
  loadFixture,
  time,
} = require("@nomicfoundation/hardhat-network-helpers");

describe("Token Contract", function () {
  // Fixture for test setup
  async function deployTokenFixture() {
    const [owner, addr1, addr2] = await ethers.getSigners();

    const Token = await ethers.getContractFactory("Token");
    const token = await Token.deploy();

    return { token, owner, addr1, addr2 };
  }

  describe("Deployment", function () {
    it("Should set the right owner", async function () {
      const { token, owner } = await loadFixture(deployTokenFixture);
      expect(await token.owner()).to.equal(owner.address);
    });

    it("Should assign total supply to owner", async function () {
      const { token, owner } = await loadFixture(deployTokenFixture);
      const ownerBalance = await token.balanceOf(owner.address);
      expect(await token.totalSupply()).to.equal(ownerBalance);
    });
  });

  describe("Transactions", function () {
    it("Should transfer tokens between accounts", async function () {
      const { token, owner, addr1 } = await loadFixture(deployTokenFixture);

      await expect(token.transfer(addr1.address, 50)).to.changeTokenBalances(
        token,
        [owner, addr1],
        [-50, 50],
      );
    });

    it("Should fail if sender doesn't have enough tokens", async function () {
      const { token, addr1 } = await loadFixture(deployTokenFixture);
      const initialBalance = await token.balanceOf(addr1.address);

      await expect(
        token.connect(addr1).transfer(owner.address, 1),
      ).to.be.revertedWith("Insufficient balance");
    });

    it("Should emit Transfer event", async function () {
      const { token, owner, addr1 } = await loadFixture(deployTokenFixture);

      await expect(token.transfer(addr1.address, 50))
        .to.emit(token, "Transfer")
        .withArgs(owner.address, addr1.address, 50);
    });
  });

  describe("Time-based tests", function () {
    it("Should handle time-locked operations", async function () {
      const { token } = await loadFixture(deployTokenFixture);

      // Increase time by 1 day
      await time.increase(86400);

      // Test time-dependent functionality
    });
  });

  describe("Gas optimization", function () {
    it("Should use gas efficiently", async function () {
      const { token } = await loadFixture(deployTokenFixture);

      const tx = await token.transfer(addr1.address, 100);
      const receipt = await tx.wait();

      expect(receipt.gasUsed).to.be.lessThan(50000);
    });
  });
});

3. Foundry 进阶测试与主网分叉

利用 Foundry 的 vm cheatcodes 进行作弊码测试,如模拟账户、增加余额。结合 vm.createSelectFork 分叉主网,连接真实的 Uniswap 等合约进行集成测试。

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

import "forge-std/Test.sol";
import "../src/Token.sol";

contract TokenTest is Test {
    Token token;
    address owner = address(1);
    address user1 = address(2);
    address user2 = address(3);

    function setUp() public {
        vm.prank(owner);
        token = new Token();
    }

    function testInitialSupply() public {
        assertEq(token.totalSupply(), 1000000 * 10**18);
    }

    function testTransfer() public {
        vm.prank(owner);
        token.transfer(user1, 100);

        assertEq(token.balanceOf(user1), 100);
        assertEq(token.balanceOf(owner), token.totalSupply() - 100);
    }

    function testFailTransferInsufficientBalance() public {
        vm.prank(user1);
        token.transfer(user2, 100); // Should fail
    }

    function testCannotTransferToZeroAddress() public {
        vm.prank(owner);
        vm.expectRevert("Invalid recipient");
        token.transfer(address(0), 100);
    }

    // Fuzzing test
    function testFuzzTransfer(uint256 amount) public {
        vm.assume(amount > 0 && amount <= token.totalSupply());

        vm.prank(owner);
        token.transfer(user1, amount);

        assertEq(token.balanceOf(user1), amount);
    }

    // Test with cheatcodes
    function testDealAndPrank() public {
        // Give ETH to address
        vm.deal(user1, 10 ether);

        // Impersonate address
        vm.prank(user1);

        // Test functionality
        assertEq(user1.balance, 10 ether);
    }

    // Mainnet fork test
    function testForkMainnet() public {
        vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/...");

        // Interact with mainnet contracts
        address dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
        assertEq(IERC20(dai).symbol(), "DAI");
    }
}

4. 快照机制与状态恢复

利用 Hardhat 的 evm_snapshotevm_revert 实现复杂状态变更测试。在 beforeEach 保存快照,afterEach 恢复状态,确保各测试用例独立干净,无需手动清理。

describe("Complex State Changes", function () {
  let snapshotId;

  beforeEach(async function () {
    snapshotId = await network.provider.send("evm_snapshot");
  });

  afterEach(async function () {
    await network.provider.send("evm_revert", [snapshotId]);
  });

  it("Test 1", async function () {
    // Make state changes
  });

  it("Test 2", async function () {
    // State reverted, clean slate
  });
});

5. 自动化测试报告与 CI/CD

生成覆盖报告并集成到 GitHub Actions。配置 Workflow 在每次 push 或 PR 时自动编译、运行测试并上传结果到 Codecov,确保代码质量可控。

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: "16"

      - run: npm install
      - run: npx hardhat compile
      - run: npx hardhat test
      - run: npx hardhat coverage

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v2

下载和安装

下载 web3-testing 中文版 Skill ZIP

解压后将目录放入你的 AI 工具 skills 文件夹,重启工具后即可使用。

你可能还需要

暂无推荐