web3-testing
wshobson/agents
Test smart contracts comprehensively with Hardhat and Foundry—unit tests, integration tests, gas optimization, fuzzing, and mainnet forking.
What is web3-testing?
Comprehensive testing framework for Solidity smart contracts using Hardhat and Foundry. Use this skill when writing unit tests, setting up integration test suites, performing gas optimization testing, fuzzing for edge cases, forking mainnet for realistic testing, or validating DeFi protocols.
- Write unit tests with Chai assertions and Hardhat fixtures
- Set up Foundry (Forge) tests in Solidity with fuzzing and cheatcodes
- Fork mainnet for realistic testing against live contract state
- Perform gas optimization testing and reporting
- Impersonate accounts and manipulate blockchain state (time, balances)
- Generate test coverage reports and verify contracts on Etherscan
How to install web3-testing
npx skills add https://github.com/wshobson/agents --skill web3-testing- Node.js and npm installed
- Hardhat or Foundry project initialized
- Environment variables configured (MAINNET_RPC_URL, ETHERSCAN_API_KEY, etc.)
- Solidity contracts to test
How to use web3-testing
- 1.Install dependencies: npm install --save-dev @nomicfoundation/hardhat-toolbox hardhat-gas-reporter solidity-coverage
- 2.Configure hardhat.config.js with network settings, gas reporter, and Etherscan API key
- 3.Write unit tests in test/ directory using Chai assertions and Hardhat helpers (loadFixture, time, etc.)
- 4.For Foundry, write Solidity tests in test/ using forge-std/Test.sol and vm cheatcodes
- 5.Run tests with npx hardhat test or forge test
- 6.Enable mainnet forking by setting MAINNET_RPC_URL and configuring hardhat.config.js forking block number
- 7.Use snapshot/revert pattern (evm_snapshot, evm_revert) for test isolation
- 8.Generate gas reports and coverage with npx hardhat test --reporter-options coinmarketcap=<key>
Use cases
- Testing ERC20 token transfers and balance changes with event verification
- Fuzzing smart contract functions with randomized inputs to find edge cases
- Forking mainnet to test interactions with real Uniswap or other DeFi protocols
- Validating time-locked operations and state transitions in complex contracts
- Measuring gas consumption and optimizing contract efficiency
- Smart contract developers
- DeFi protocol engineers
- Blockchain security auditors
- Web3 QA engineers
web3-testing FAQ
Hardhat tests are written in JavaScript using Chai assertions and ethers.js, better for integration testing and complex scenarios. Foundry tests are written in Solidity using vm cheatcodes, faster execution and better for fuzzing and low-level contract testing.
Use mainnet forking by setting the MAINNET_RPC_URL in your config and specifying a blockNumber. Then use ethers.getContractAt() or Foundry's vm.createSelectFork() to interact with real contract addresses.
Use hardhat_impersonateAccount RPC method or Foundry's vm.prank() cheatcode to execute transactions as another address, useful for testing with large token balances.
Use Hardhat's loadFixture() helper to create reusable deployment setups. Define an async fixture function that deploys contracts and returns signers/instances, then call it in each test for a clean state.
Enable gasReporter in hardhat.config.js, run tests, and review the gas report. Use Foundry's gas snapshots or check receipt.gasUsed in Hardhat tests to identify expensive operations.
Full instructions (SKILL.md)
Source of truth, from wshobson/agents.
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
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.
Related skills
More from wshobson/agents and the wider catalog.

workflow-orchestration-patterns
Reference guide for designing reliable, deterministic Temporal workflows and idempotent activities.

workflow-patterns
Implement tasks using Conductor's TDD workflow with phase checkpoints, git management, and verification protocols.

accessibility-compliance
Build WCAG 2.2 compliant interfaces with keyboard navigation, screen reader support, and inclusive design patterns.

ai-debt-detector
Audit AI-generated code for hidden debt: missing error handling, orphaned resources, and hallucinated dependencies.

remotion-video
|

translate-pdf
|