Web3智能合约测试Skill web3-testing

这个技能专注于使用Hardhat和Foundry工具对Web3智能合约进行全面的测试,包括单元测试、集成测试、气体优化、模糊测试和主网分叉测试。适用于Solidity合约开发、区块链应用测试和DeFi协议验证,关键词包括Web3、智能合约测试、Hardhat、Foundry、单元测试、集成测试、气体优化、模糊测试、主网分叉、Solidity、DeFi、Etherscan验证。

智能合约 0 次安装 0 次浏览 更新于 3/16/2026

name: web3-testing description: 使用Hardhat和Foundry全面测试智能合约,包括单元测试、集成测试和主网分叉。适用于测试Solidity合约、设置区块链测试套件或验证DeFi协议。

Web3智能合约测试

掌握使用Hardhat、Foundry和高级测试模式进行智能合约全面测试的策略。

何时使用此技能

  • 为智能合约编写单元测试
  • 设置集成测试套件
  • 执行气体优化测试
  • 对边缘情况进行模糊测试
  • 分叉主网进行现实测试
  • 自动化测试覆盖率报告
  • 在Etherscan上验证合约

Hardhat测试设置

// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");
require("hardhat-gas-reporter");
require("solidity-coverage");

module.exports = {
  solidity: {
    version: "0.8.19",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    hardhat: {
      forking: {
        url: process.env.MAINNET_RPC_URL,
        blockNumber: 15000000
      }
    },
    goerli: {
      url: process.env.GOERLI_RPC_URL,
      accounts: [process.env.PRIVATE_KEY]
    }
  },
  gasReporter: {
    enabled: true,
    currency: 'USD',
    coinmarketcap: process.env.COINMARKETCAP_API_KEY
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY
  }
};

单元测试模式

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

describe("Token Contract", function () {
  // 测试设置的夹具
  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("部署", function () {
    it("应设置正确的所有者", async function () {
      const { token, owner } = await loadFixture(deployTokenFixture);
      expect(await token.owner()).to.equal(owner.address);
    });

    it("应将总供应量分配给所有者", async function () {
      const { token, owner } = await loadFixture(deployTokenFixture);
      const ownerBalance = await token.balanceOf(owner.address);
      expect(await token.totalSupply()).to.equal(ownerBalance);
    });
  });

  describe("交易", function () {
    it("应在账户间转移代币", async function () {
      const { token, owner, addr1 } = await loadFixture(deployTokenFixture);

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

    it("如果发送者没有足够的代币应失败", 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("余额不足");
    });

    it("应发出Transfer事件", 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("基于时间的测试", function () {
    it("应处理时间锁定操作", async function () {
      const { token } = await loadFixture(deployTokenFixture);

      // 增加时间1天
      await time.increase(86400);

      // 测试时间相关功能
    });
  });

  describe("气体优化", function () {
    it("应高效使用气体", 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);
    });
  });
});

Foundry测试 (Forge)

// 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); // 应失败
    }

    function testCannotTransferToZeroAddress() public {
        vm.prank(owner);
        vm.expectRevert("无效的接收者");
        token.transfer(address(0), 100);
    }

    // 模糊测试
    function testFuzzTransfer(uint256 amount) public {
        vm.assume(amount > 0 && amount <= token.totalSupply());

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

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

    // 使用作弊码测试
    function testDealAndPrank() public {
        // 向地址提供ETH
        vm.deal(user1, 10 ether);

        // 模拟地址
        vm.prank(user1);

        // 测试功能
        assertEq(user1.balance, 10 ether);
    }

    // 主网分叉测试
    function testForkMainnet() public {
        vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/...");

        // 与主网合约交互
        address dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
        assertEq(IERC20(dai).symbol(), "DAI");
    }
}

高级测试模式

快照和恢复

describe("复杂状态变化", function () {
  let snapshotId;

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

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

  it("测试1", async function () {
    // 进行状态变化
  });

  it("测试2", async function () {
    // 状态已恢复,干净状态
  });
});

主网分叉

describe("主网分叉测试", function () {
  let uniswapRouter, dai, usdc;

  before(async function () {
    await network.provider.request({
      method: "hardhat_reset",
      params: [{
        forking: {
          jsonRpcUrl: process.env.MAINNET_RPC_URL,
          blockNumber: 15000000
        }
      }]
    });

    // 连接到现有的主网合约
    uniswapRouter = await ethers.getContractAt(
      "IUniswapV2Router",
      "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
    );

    dai = await ethers.getContractAt(
      "IERC20",
      "0x6B175474E89094C44Da98b954EedeAC495271d0F"
    );
  });

  it("应在Uniswap上交换", async function () {
    // 使用真实的Uniswap合约测试
  });
});

模拟账户

it("应模拟鲸鱼账户", async function () {
  const whaleAddress = "0x...";

  await network.provider.request({
    method: "hardhat_impersonateAccount",
    params: [whaleAddress]
  });

  const whale = await ethers.getSigner(whaleAddress);

  // 使用鲸鱼的代币
  await dai.connect(whale).transfer(addr1.address, ethers.utils.parseEther("1000"));
});

气体优化测试

const { expect } = require("chai");

describe("气体优化", function () {
  it("比较不同实现的燃气使用情况", async function () {
    const Implementation1 = await ethers.getContractFactory("OptimizedContract");
    const Implementation2 = await ethers.getContractFactory("UnoptimizedContract");

    const contract1 = await Implementation1.deploy();
    const contract2 = await Implementation2.deploy();

    const tx1 = await contract1.doSomething();
    const receipt1 = await tx1.wait();

    const tx2 = await contract2.doSomething();
    const receipt2 = await tx2.wait();

    console.log("优化燃气:", receipt1.gasUsed.toString());
    console.log("未优化燃气:", receipt2.gasUsed.toString());

    expect(receipt1.gasUsed).to.be.lessThan(receipt2.gasUsed);
  });
});

覆盖率报告

# 生成覆盖率报告
npx hardhat coverage

# 输出显示:
# 文件                | % 语句 | % 分支 | % 函数 | % 行数 |
# -------------------|---------|----------|---------|---------|
# contracts/Token.sol |   100   |   90     |   100   |   95    |

合约验证

// 在Etherscan上验证
await hre.run("verify:verify", {
  address: contractAddress,
  constructorArguments: [arg1, arg2]
});
# 或通过CLI
npx hardhat verify --network mainnet CONTRACT_ADDRESS "构造函数参数1" "参数2"

CI/CD集成

# .github/workflows/test.yml
name: 测试

on: [push, pull_request]

jobs:
  测试:
    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: 上传覆盖率到Codecov
        uses: codecov/codecov-action@v2

资源

  • references/hardhat-setup.md: Hardhat配置指南
  • references/foundry-setup.md: Foundry测试框架
  • references/test-patterns.md: 测试最佳实践
  • references/mainnet-forking.md: 分叉测试策略
  • references/contract-verification.md: Etherscan验证
  • assets/hardhat-config.js: 完整Hardhat配置
  • assets/test-suite.js: 综合测试示例
  • assets/foundry.toml: Foundry配置
  • scripts/test-contract.sh: 自动化测试脚本

最佳实践

  1. 测试覆盖率: 目标>90%
  2. 边缘情况: 测试边界条件
  3. 气体限制: 验证函数不达到区块气体限制
  4. 重入: 测试重入漏洞
  5. 访问控制: 测试未授权访问尝试
  6. 事件: 验证事件发射
  7. 夹具: 使用夹具避免代码重复
  8. 主网分叉: 使用真实合约测试
  9. 模糊测试: 使用基于属性的测试
  10. CI/CD: 每次提交自动化测试