Test smart contracts comprehensively using Hardhat

Web3 Smart Contract Testing

by wshobson · MIT license · ★ 39,857 on GitHub · updated Sep 21

Web3 Smart Contract Testing cover

Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, setting up blockchain test suites, or validating DeFi protocols.

---
name: web3-testing
description: Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, setting up blockchain test suites, or validating DeFi protocols.
---

# Web3 Smart Contract Testing

Master comprehensive testing strategies for smart contracts using Hardhat, Foundry, and advanced testing patterns.

## When to Use This Skill

- Writing unit tests for smart contracts
- Setting up integration test suites
- Performing gas optimization testing
- Fuzzing for edge cases
- Forking mainnet for realistic testing
- Automating test coverage reporting
- Verifying contracts on Etherscan

Overview

Master comprehensive testing strategies for smart contracts using Hardhat, Foundry, and advanced testing patterns.

Manual install (npx, ZIP)
Claude Codenpx skills add wshobson/agents --skill web3-testing -g -a claude-code
Claude Code (manual)mkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/wshobson/agents/tar.gz/4236bb91f839 \ | tar -xz -C ~/.claude/skills --strip-components=4 agents-4236bb91f839/plugins/blockchain-web3/skills/web3-testing
Windows (PowerShell)New-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/4b99a28398b87a30c2ff/web3-testing.zip" -OutFile "$env:TEMP\web3-testing.zip"; Expand-Archive "$env:TEMP\web3-testing.zip" -DestinationPath "$HOME\.claude\skills" -Force
Claude.ai / desktopDownload the ZIP and upload it under Settings → Capabilities → Skills.
Codex, Cursor, other agentsPoint them at SKILL.md. The other files are plain text.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • SKILL.md7.7 KB
  • references/1 file · 1.9 KB
Read the files here
wshobson/4236bb92 files
SKILL.md
Show the full text312 lines

Web3 Smart Contract Testing

Master comprehensive testing strategies for smart contracts using Hardhat, Foundry, and advanced testing patterns.

When to Use This Skill

  • Writing unit tests for smart contracts
  • Setting up integration test suites
  • Performing gas optimization testing
  • Fuzzing for edge cases
  • Forking mainnet for realistic testing
  • Automating test coverage reporting
  • Verifying contracts on Etherscan

Hardhat Testing Setup

// 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,
  },
};

Unit Testing Patterns

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);
    });
  });
});

Foundry Testing (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); // 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");
    }
}

Advanced Testing Patterns

Snapshot and Revert
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
  });
});
Mainnet Forking
describe("Mainnet Fork Tests", 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,
          },
        },
      ],
    });

    // Connect to existing mainnet contracts
    uniswapRouter = await ethers.getContractAt(
      "IUniswapV2Router",
      "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
    );

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

  it("Should swap on Uniswap", async function () {
    // Test with real Uniswap contracts
  });
});
Impersonating Accounts
it("Should impersonate whale account", async function () {
  const whaleAddress = "0x...";

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

  const whale = await ethers.getSigner(whaleAddress);

  // Use whale's tokens
  await dai
    .connect(whale)
    .transfer(addr1.address, ethers.utils.parseEther("1000"));
});

Additional patterns and templates

More detailed templates and worked examples live in references/details.md. Read that file for the full pattern library.

1---
2name: web3-testing
3description: Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, setting up blockchain test suites, or validating DeFi protocols.
4---
5 
6# Web3 Smart Contract Testing
7 
8Master comprehensive testing strategies for smart contracts using Hardhat, Foundry, and advanced testing patterns.
9 
10## When to Use This Skill
11 
12- Writing unit tests for smart contracts
13- Setting up integration test suites
14- Performing gas optimization testing
15- Fuzzing for edge cases
16- Forking mainnet for realistic testing
17- Automating test coverage reporting
18- Verifying contracts on Etherscan
19 
20## Hardhat Testing Setup
21 
22```javascript
23// hardhat.config.js
24require("@nomicfoundation/hardhat-toolbox");
25require("@nomiclabs/hardhat-etherscan");
26require("hardhat-gas-reporter");
27require("solidity-coverage");
28 
29module.exports = {
30 solidity: {
31 version: "0.8.19",
32 settings: {
33 optimizer: {
34 enabled: true,
35 runs: 200,
36 },
37 },
38 },
39 networks: {
40 hardhat: {
41 forking: {
42 url: process.env.MAINNET_RPC_URL,
43 blockNumber: 15000000,
44 },
45 },
46 goerli: {
47 url: process.env.GOERLI_RPC_URL,
48 accounts: [process.env.PRIVATE_KEY],
49 },
50 },
51 gasReporter: {
52 enabled: true,
53 currency: "USD",
54 coinmarketcap: process.env.COINMARKETCAP_API_KEY,
55 },
56 etherscan: {
57 apiKey: process.env.ETHERSCAN_API_KEY,
58 },
59};
60```
61 
62## Unit Testing Patterns
63 
64```javascript
65const { expect } = require("chai");
66const { ethers } = require("hardhat");
67const {
68 loadFixture,
69 time,
70} = require("@nomicfoundation/hardhat-network-helpers");
71 
72describe("Token Contract", function () {
73 // Fixture for test setup
74 async function deployTokenFixture() {
75 const [owner, addr1, addr2] = await ethers.getSigners();
76 
77 const Token = await ethers.getContractFactory("Token");
78 const token = await Token.deploy();
79 
80 return { token, owner, addr1, addr2 };
81 }
82 
83 describe("Deployment", function () {
84 it("Should set the right owner", async function () {
85 const { token, owner } = await loadFixture(deployTokenFixture);
86 expect(await token.owner()).to.equal(owner.address);
87 });
88 
89 it("Should assign total supply to owner", async function () {
90 const { token, owner } = await loadFixture(deployTokenFixture);
91 const ownerBalance = await token.balanceOf(owner.address);
92 expect(await token.totalSupply()).to.equal(ownerBalance);
93 });
94 });
95 
96 describe("Transactions", function () {
97 it("Should transfer tokens between accounts", async function () {
98 const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
99 
100 await expect(token.transfer(addr1.address, 50)).to.changeTokenBalances(
101 token,
102 [owner, addr1],
103 [-50, 50],
104 );
105 });
106 
107 it("Should fail if sender doesn't have enough tokens", async function () {
108 const { token, addr1 } = await loadFixture(deployTokenFixture);
109 const initialBalance = await token.balanceOf(addr1.address);
110 
111 await expect(
112 token.connect(addr1).transfer(owner.address, 1),
113 ).to.be.revertedWith("Insufficient balance");
114 });
115 
116 it("Should emit Transfer event", async function () {
117 const { token, owner, addr1 } = await loadFixture(deployTokenFixture);
118 
119 await expect(token.transfer(addr1.address, 50))
120 .to.emit(token, "Transfer")
121 .withArgs(owner.address, addr1.address, 50);
122 });
123 });
124 
125 describe("Time-based tests", function () {
126 it("Should handle time-locked operations", async function () {
127 const { token } = await loadFixture(deployTokenFixture);
128 
129 // Increase time by 1 day
130 await time.increase(86400);
131 
132 // Test time-dependent functionality
133 });
134 });
135 
136 describe("Gas optimization", function () {
137 it("Should use gas efficiently", async function () {
138 const { token } = await loadFixture(deployTokenFixture);
139 
140 const tx = await token.transfer(addr1.address, 100);
141 const receipt = await tx.wait();
142 
143 expect(receipt.gasUsed).to.be.lessThan(50000);
144 });
145 });
146});
147```
148 
149## Foundry Testing (Forge)
150 
151```solidity
152// SPDX-License-Identifier: MIT
153pragma solidity ^0.8.0;
154 
155import "forge-std/Test.sol";
156import "../src/Token.sol";
157 
158contract TokenTest is Test {
159 Token token;
160 address owner = address(1);
161 address user1 = address(2);
162 address user2 = address(3);
163 
164 function setUp() public {
165 vm.prank(owner);
166 token = new Token();
167 }
168 
169 function testInitialSupply() public {
170 assertEq(token.totalSupply(), 1000000 * 10**18);
171 }
172 
173 function testTransfer() public {
174 vm.prank(owner);
175 token.transfer(user1, 100);
176 
177 assertEq(token.balanceOf(user1), 100);
178 assertEq(token.balanceOf(owner), token.totalSupply() - 100);
179 }
180 
181 function testFailTransferInsufficientBalance() public {
182 vm.prank(user1);
183 token.transfer(user2, 100); // Should fail
184 }
185 
186 function testCannotTransferToZeroAddress() public {
187 vm.prank(owner);
188 vm.expectRevert("Invalid recipient");
189 token.transfer(address(0), 100);
190 }
191 
192 // Fuzzing test
193 function testFuzzTransfer(uint256 amount) public {
194 vm.assume(amount > 0 && amount <= token.totalSupply());
195 
196 vm.prank(owner);
197 token.transfer(user1, amount);
198 
199 assertEq(token.balanceOf(user1), amount);
200 }
201 
202 // Test with cheatcodes
203 function testDealAndPrank() public {
204 // Give ETH to address
205 vm.deal(user1, 10 ether);
206 
207 // Impersonate address
208 vm.prank(user1);
209 
210 // Test functionality
211 assertEq(user1.balance, 10 ether);
212 }
213 
214 // Mainnet fork test
215 function testForkMainnet() public {
216 vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/...");
217 
218 // Interact with mainnet contracts
219 address dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
220 assertEq(IERC20(dai).symbol(), "DAI");
221 }
222}
223```
224 
225## Advanced Testing Patterns
226 
227### Snapshot and Revert
228 
229```javascript
230describe("Complex State Changes", function () {
231 let snapshotId;
232 
233 beforeEach(async function () {
234 snapshotId = await network.provider.send("evm_snapshot");
235 });
236 
237 afterEach(async function () {
238 await network.provider.send("evm_revert", [snapshotId]);
239 });
240 
241 it("Test 1", async function () {
242 // Make state changes
243 });
244 
245 it("Test 2", async function () {
246 // State reverted, clean slate
247 });
248});
249```
250 
251### Mainnet Forking
252 
253```javascript
254describe("Mainnet Fork Tests", function () {
255 let uniswapRouter, dai, usdc;
256 
257 before(async function () {
258 await network.provider.request({
259 method: "hardhat_reset",
260 params: [
261 {
262 forking: {
263 jsonRpcUrl: process.env.MAINNET_RPC_URL,
264 blockNumber: 15000000,
265 },
266 },
267 ],
268 });
269 
270 // Connect to existing mainnet contracts
271 uniswapRouter = await ethers.getContractAt(
272 "IUniswapV2Router",
273 "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
274 );
275 
276 dai = await ethers.getContractAt(
277 "IERC20",
278 "0x6B175474E89094C44Da98b954EedeAC495271d0F",
279 );
280 });
281 
282 it("Should swap on Uniswap", async function () {
283 // Test with real Uniswap contracts
284 });
285});
286```
287 
288### Impersonating Accounts
289 
290```javascript
291it("Should impersonate whale account", async function () {
292 const whaleAddress = "0x...";
293 
294 await network.provider.request({
295 method: "hardhat_impersonateAccount",
296 params: [whaleAddress],
297 });
298 
299 const whale = await ethers.getSigner(whaleAddress);
300 
301 // Use whale's tokens
302 await dai
303 .connect(whale)
304 .transfer(addr1.address, ethers.utils.parseEther("1000"));
305});
306```
307 
308## Additional patterns and templates
309 
310More detailed templates and worked examples live in `references/details.md`. Read that file for the full pattern library.
311 
312 
View all files on GitHub →

Details

Updated
Sep 21, 2026
License
MIT
Stars on the repo
★ 39,857
Checked
Source
wshobson/agents ↗
Creator
wshobson · 179 listings
View creator

Similar skills

See all →
MCPAgent-native x402 pay-per-call data & security APIs on BaseX402 data APIrezearcher/x402-data-api · by rezearcherAgent-native x402 pay-per-call data & security APIs on Base (crypto, DeFi, on-chain, Polymarket, MCP security scan)New listingSkillImplement DeFi protocols with production-ready templatesDefi protocol templateswshobson/agents · by wshobsonImplement DeFi protocols with production-ready templates for staking, AMMs, governance, and flash loans. Use when building decentralized finance applications or smart contract protocols.★ 39.9k GitHub starsSkillImplement NFT standards with proper metadata handlingwshobson/agents · by wshobsonImplement NFT standards (ERC-721, ERC-1155) with proper metadata handling, minting strategies, and marketplace integration. Use when creating NFT contracts, building NFT marketplaces, or implementing digital asset systems.★ 39.9k GitHub starsSkillMaster smart contract security best practices to preventSolidity securitywshobson/agents · by wshobsonMaster smart contract security best practices to prevent common vulnerabilities and implement secure Solidity patterns. Use when writing smart contracts, auditing existing contracts, or implementing security measures for blockchain applications.★ 39.9k GitHub starsMCPBuilding smart contracts, DAppsBlockchain developerVoltAgent/awesome-claude-code-subagents · by VoltAgentUse this agent when building smart contracts, DApps, and blockchain protocols that require expertise in Solidity, gas optimization, security auditing, and Web3 integration.★ 25.2k GitHub starsSkillRequires technical rigor and verificationCode Review Receptionobra/superpowers · by obraUse when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation★ 290k GitHub stars

More from wshobson

SkillMaster C/.NET backend development patterns for buildingwshobson/agents · by wshobsonMaster C#/.NET backend development patterns for building robust APIs, MCP servers, and enterprise applications. Covers async/await, dependency injection, Entity Framework Core, Dapper, configuration, caching, and testing with xUnit. Use when developing .NET backends, reviewing C# code, or designing API architectures.★ 39.9k GitHub starsSkillImplement WCAG 2.2 compliant interfaces with mobileAccessibility compliancewshobson/agents · by wshobsonImplement WCAG 2.2 compliant interfaces with mobile accessibility, inclusive design patterns, and assistive technology support. Use when auditing accessibility, implementing ARIA patterns, building for screen readers, or ensuring inclusive user experiences.★ 39.9k GitHub starsSkillMaster Material Design 3 and Jetpack Compose patternsAndroid Mobile Designwshobson/agents · by wshobsonMaster Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.★ 39.9k GitHub starsSkillMigrate from AngularJS to Angular using hybrid modeAngular migrationwshobson/agents · by wshobsonMigrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applications, planning framework migrations, or modernizing legacy Angular code.★ 39.9k GitHub starsSkillUnderstand anti-reversing, obfuscationAnti reversing techniqueswshobson/agents · by wshobsonUnderstand anti-reversing, obfuscation, and protection techniques encountered during software analysis. Use this skill when analyzing malware evasion techniques, when implementing anti-debugging protections for CTF challenges, when reverse engineering packed binaries, or when building security research tools that need to detect virtualized environments.★ 39.9k GitHub starsSkillBuild production Apache Airflow DAGs with best practicesApache Airflow DAG Patternswshobson/agents · by wshobsonBuild production Apache Airflow DAGs with best practices for operators, sensors, testing, and deployment. Use when creating data pipelines, orchestrating workflows, or scheduling batch jobs.★ 39.9k GitHub stars