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.
Add this skill
npx mdskills install sickn33/web3-testingComprehensive smart contract testing guide with strong examples for Hardhat and Foundry frameworks
1---2name: web3-testing3description: 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---56# Web3 Smart Contract Testing78Master comprehensive testing strategies for smart contracts using Hardhat, Foundry, and advanced testing patterns.910## Do not use this skill when1112- The task is unrelated to web3 smart contract testing13- You need a different domain or tool outside this scope1415## Instructions1617- Clarify goals, constraints, and required inputs.18- Apply relevant best practices and validate outcomes.19- Provide actionable steps and verification.20- If detailed examples are required, open `resources/implementation-playbook.md`.2122## Use this skill when2324- Writing unit tests for smart contracts25- Setting up integration test suites26- Performing gas optimization testing27- Fuzzing for edge cases28- Forking mainnet for realistic testing29- Automating test coverage reporting30- Verifying contracts on Etherscan3132## Hardhat Testing Setup3334```javascript35// hardhat.config.js36require("@nomicfoundation/hardhat-toolbox");37require("@nomiclabs/hardhat-etherscan");38require("hardhat-gas-reporter");39require("solidity-coverage");4041module.exports = {42 solidity: {43 version: "0.8.19",44 settings: {45 optimizer: {46 enabled: true,47 runs: 200,48 },49 },50 },51 networks: {52 hardhat: {53 forking: {54 url: process.env.MAINNET_RPC_URL,55 blockNumber: 15000000,56 },57 },58 goerli: {59 url: process.env.GOERLI_RPC_URL,60 accounts: [process.env.PRIVATE_KEY],61 },62 },63 gasReporter: {64 enabled: true,65 currency: "USD",66 coinmarketcap: process.env.COINMARKETCAP_API_KEY,67 },68 etherscan: {69 apiKey: process.env.ETHERSCAN_API_KEY,70 },71};72```7374## Unit Testing Patterns7576```javascript77const { expect } = require("chai");78const { ethers } = require("hardhat");79const {80 loadFixture,81 time,82} = require("@nomicfoundation/hardhat-network-helpers");8384describe("Token Contract", function () {85 // Fixture for test setup86 async function deployTokenFixture() {87 const [owner, addr1, addr2] = await ethers.getSigners();8889 const Token = await ethers.getContractFactory("Token");90 const token = await Token.deploy();9192 return { token, owner, addr1, addr2 };93 }9495 describe("Deployment", function () {96 it("Should set the right owner", async function () {97 const { token, owner } = await loadFixture(deployTokenFixture);98 expect(await token.owner()).to.equal(owner.address);99 });100101 it("Should assign total supply to owner", async function () {102 const { token, owner } = await loadFixture(deployTokenFixture);103 const ownerBalance = await token.balanceOf(owner.address);104 expect(await token.totalSupply()).to.equal(ownerBalance);105 });106 });107108 describe("Transactions", function () {109 it("Should transfer tokens between accounts", async function () {110 const { token, owner, addr1 } = await loadFixture(deployTokenFixture);111112 await expect(token.transfer(addr1.address, 50)).to.changeTokenBalances(113 token,114 [owner, addr1],115 [-50, 50],116 );117 });118119 it("Should fail if sender doesn't have enough tokens", async function () {120 const { token, addr1 } = await loadFixture(deployTokenFixture);121 const initialBalance = await token.balanceOf(addr1.address);122123 await expect(124 token.connect(addr1).transfer(owner.address, 1),125 ).to.be.revertedWith("Insufficient balance");126 });127128 it("Should emit Transfer event", async function () {129 const { token, owner, addr1 } = await loadFixture(deployTokenFixture);130131 await expect(token.transfer(addr1.address, 50))132 .to.emit(token, "Transfer")133 .withArgs(owner.address, addr1.address, 50);134 });135 });136137 describe("Time-based tests", function () {138 it("Should handle time-locked operations", async function () {139 const { token } = await loadFixture(deployTokenFixture);140141 // Increase time by 1 day142 await time.increase(86400);143144 // Test time-dependent functionality145 });146 });147148 describe("Gas optimization", function () {149 it("Should use gas efficiently", async function () {150 const { token } = await loadFixture(deployTokenFixture);151152 const tx = await token.transfer(addr1.address, 100);153 const receipt = await tx.wait();154155 expect(receipt.gasUsed).to.be.lessThan(50000);156 });157 });158});159```160161## Foundry Testing (Forge)162163```solidity164// SPDX-License-Identifier: MIT165pragma solidity ^0.8.0;166167import "forge-std/Test.sol";168import "../src/Token.sol";169170contract TokenTest is Test {171 Token token;172 address owner = address(1);173 address user1 = address(2);174 address user2 = address(3);175176 function setUp() public {177 vm.prank(owner);178 token = new Token();179 }180181 function testInitialSupply() public {182 assertEq(token.totalSupply(), 1000000 * 10**18);183 }184185 function testTransfer() public {186 vm.prank(owner);187 token.transfer(user1, 100);188189 assertEq(token.balanceOf(user1), 100);190 assertEq(token.balanceOf(owner), token.totalSupply() - 100);191 }192193 function testFailTransferInsufficientBalance() public {194 vm.prank(user1);195 token.transfer(user2, 100); // Should fail196 }197198 function testCannotTransferToZeroAddress() public {199 vm.prank(owner);200 vm.expectRevert("Invalid recipient");201 token.transfer(address(0), 100);202 }203204 // Fuzzing test205 function testFuzzTransfer(uint256 amount) public {206 vm.assume(amount > 0 && amount <= token.totalSupply());207208 vm.prank(owner);209 token.transfer(user1, amount);210211 assertEq(token.balanceOf(user1), amount);212 }213214 // Test with cheatcodes215 function testDealAndPrank() public {216 // Give ETH to address217 vm.deal(user1, 10 ether);218219 // Impersonate address220 vm.prank(user1);221222 // Test functionality223 assertEq(user1.balance, 10 ether);224 }225226 // Mainnet fork test227 function testForkMainnet() public {228 vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/...");229230 // Interact with mainnet contracts231 address dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;232 assertEq(IERC20(dai).symbol(), "DAI");233 }234}235```236237## Advanced Testing Patterns238239### Snapshot and Revert240241```javascript242describe("Complex State Changes", function () {243 let snapshotId;244245 beforeEach(async function () {246 snapshotId = await network.provider.send("evm_snapshot");247 });248249 afterEach(async function () {250 await network.provider.send("evm_revert", [snapshotId]);251 });252253 it("Test 1", async function () {254 // Make state changes255 });256257 it("Test 2", async function () {258 // State reverted, clean slate259 });260});261```262263### Mainnet Forking264265```javascript266describe("Mainnet Fork Tests", function () {267 let uniswapRouter, dai, usdc;268269 before(async function () {270 await network.provider.request({271 method: "hardhat_reset",272 params: [273 {274 forking: {275 jsonRpcUrl: process.env.MAINNET_RPC_URL,276 blockNumber: 15000000,277 },278 },279 ],280 });281282 // Connect to existing mainnet contracts283 uniswapRouter = await ethers.getContractAt(284 "IUniswapV2Router",285 "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",286 );287288 dai = await ethers.getContractAt(289 "IERC20",290 "0x6B175474E89094C44Da98b954EedeAC495271d0F",291 );292 });293294 it("Should swap on Uniswap", async function () {295 // Test with real Uniswap contracts296 });297});298```299300### Impersonating Accounts301302```javascript303it("Should impersonate whale account", async function () {304 const whaleAddress = "0x...";305306 await network.provider.request({307 method: "hardhat_impersonateAccount",308 params: [whaleAddress],309 });310311 const whale = await ethers.getSigner(whaleAddress);312313 // Use whale's tokens314 await dai315 .connect(whale)316 .transfer(addr1.address, ethers.utils.parseEther("1000"));317});318```319320## Gas Optimization Testing321322```javascript323const { expect } = require("chai");324325describe("Gas Optimization", function () {326 it("Compare gas usage between implementations", async function () {327 const Implementation1 =328 await ethers.getContractFactory("OptimizedContract");329 const Implementation2 = await ethers.getContractFactory(330 "UnoptimizedContract",331 );332333 const contract1 = await Implementation1.deploy();334 const contract2 = await Implementation2.deploy();335336 const tx1 = await contract1.doSomething();337 const receipt1 = await tx1.wait();338339 const tx2 = await contract2.doSomething();340 const receipt2 = await tx2.wait();341342 console.log("Optimized gas:", receipt1.gasUsed.toString());343 console.log("Unoptimized gas:", receipt2.gasUsed.toString());344345 expect(receipt1.gasUsed).to.be.lessThan(receipt2.gasUsed);346 });347});348```349350## Coverage Reporting351352```bash353# Generate coverage report354npx hardhat coverage355356# Output shows:357# File | % Stmts | % Branch | % Funcs | % Lines |358# -------------------|---------|----------|---------|---------|359# contracts/Token.sol | 100 | 90 | 100 | 95 |360```361362## Contract Verification363364```javascript365// Verify on Etherscan366await hre.run("verify:verify", {367 address: contractAddress,368 constructorArguments: [arg1, arg2],369});370```371372```bash373# Or via CLI374npx hardhat verify --network mainnet CONTRACT_ADDRESS "Constructor arg1" "arg2"375```376377## CI/CD Integration378379```yaml380# .github/workflows/test.yml381name: Tests382383on: [push, pull_request]384385jobs:386 test:387 runs-on: ubuntu-latest388389 steps:390 - uses: actions/checkout@v2391 - uses: actions/setup-node@v2392 with:393 node-version: "16"394395 - run: npm install396 - run: npx hardhat compile397 - run: npx hardhat test398 - run: npx hardhat coverage399400 - name: Upload coverage to Codecov401 uses: codecov/codecov-action@v2402```403404## Resources405406- **references/hardhat-setup.md**: Hardhat configuration guide407- **references/foundry-setup.md**: Foundry testing framework408- **references/test-patterns.md**: Testing best practices409- **references/mainnet-forking.md**: Fork testing strategies410- **references/contract-verification.md**: Etherscan verification411- **assets/hardhat-config.js**: Complete Hardhat configuration412- **assets/test-suite.js**: Comprehensive test examples413- **assets/foundry.toml**: Foundry configuration414- **scripts/test-contract.sh**: Automated testing script415416## Best Practices4174181. **Test Coverage**: Aim for >90% coverage4192. **Edge Cases**: Test boundary conditions4203. **Gas Limits**: Verify functions don't hit block gas limit4214. **Reentrancy**: Test for reentrancy vulnerabilities4225. **Access Control**: Test unauthorized access attempts4236. **Events**: Verify event emissions4247. **Fixtures**: Use fixtures to avoid code duplication4258. **Mainnet Fork**: Test with real contracts4269. **Fuzzing**: Use property-based testing42710. **CI/CD**: Automate testing on every commit428
Full transparency — inspect the skill content before installing.