Software Engineer
Generating Hardhat Test Suites β 50 Edge Cases the Developer Missed
Key Takeaway
An AI agent generated 50 comprehensive test cases for a Solidity contract in 10 minutes β including 8 edge cases that revealed undocumented behavior no developer had caught.
The Problem
Developers write tests for the code they meant to write. Not for the code they actually wrote.
I see it constantly: happy path tests, a few obvious revert cases, and a "good enough" coverage number. But smart contracts aren't web apps β you can't hotfix a deployed contract. Every edge case you miss is a potential exploit sitting on an immutable blockchain holding real money.
We had a staking contract. The developer wrote 12 tests. All green. Coverage report said 78%. The developer said "ship it." I said "let the agent look first."
The Solution
Upload the contract to our Web3 agent and ask for a comprehensive test suite. The agent reads every function, every modifier, every state transition β then generates tests for happy paths, edge cases, access control violations, reentrancy attempts, overflow conditions, and timing-dependent behavior.
The Process
The agent analyzes the contract's interface and state machine:
solidityShow code
// Contract under test: StakingPool.sol
contract StakingPool is Ownable, ReentrancyGuard {
function stake() external payable;
function unstake(uint256 amount) external;
function claimRewards() external;
function setRewardRate(uint256 rate) external onlyOwner;
function emergencyWithdraw() external onlyOwner;
}
Then generates a complete test file:
javascriptShow code
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { time, loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
describe("StakingPool", function () {
async function deployFixture() {
const [owner, alice, bob, attacker] = await ethers.getSigners();
const StakingPool = await ethers.getContractFactory("StakingPool");
const pool = await StakingPool.deploy();
return { pool, owner, alice, bob, attacker };
}
describe("Staking - Happy Paths", function () {
it("should accept ETH stakes and update balance", async function () {
const { pool, alice } = await loadFixture(deployFixture);
await pool.connect(alice).stake({ value: ethers.parseEther("1.0") });
expect(await pool.stakedBalance(alice.address)).to.equal(ethers.parseEther("1.0"));
});
// ... 6 more happy path tests
});
describe("Staking - Edge Cases", function () {
it("should handle stake of 0 ETH", async function () {
const { pool, alice } = await loadFixture(deployFixture);
await expect(
pool.connect(alice).stake({ value: 0 })
).to.be.revertedWith("Must stake > 0");
});
it("should handle stake at uint256 max boundary", async function () {
// Tests near-overflow behavior
});
it("should handle rapid stake/unstake in same block", async function () {
// Revealed: rewards calculated incorrectly for same-block operations
});
});
describe("Access Control", function () {
it("should prevent non-owner from setting reward rate", async function () {
const { pool, attacker } = await loadFixture(deployFixture);
await expect(
pool.connect(attacker).setRewardRate(999)
).to.be.revertedWithCustomError(pool, "OwnableUnauthorizedAccount");
});
it("should prevent non-owner from emergency withdraw", async function () {
// ...
});
});
describe("Reentrancy Protection", function () {
it("should block reentrancy on unstake", async function () {
// Deploy malicious contract that calls unstake in receive()
const Attacker = await ethers.getContractFactory("ReentrancyAttacker");
const attacker = await Attacker.deploy(pool.target);
// ... attempt reentrancy, expect revert
});
it("should block reentrancy on claimRewards", async function () {
// Same pattern for rewards claim
});
});
describe("Timing-Dependent Behavior", function () {
it("should calculate rewards correctly after 365 days", async function () {
const { pool, alice } = await loadFixture(deployFixture);
await pool.connect(alice).stake({ value: ethers.parseEther("10.0") });
await time.increase(365 * 24 * 60 * 60);
// Revealed: reward calculation overflows after ~290 days with high rates
});
});
// Gas snapshots for every function
describe("Gas Benchmarks", function () {
it("stake gas snapshot", async function () {
const { pool, alice } = await loadFixture(deployFixture);
const tx = await pool.connect(alice).stake({ value: ethers.parseEther("1.0") });
const receipt = await tx.wait();
expect(receipt.gasUsed).to.be.lessThan(80000);
});
});
});
The 8 unexpected findings:
- Staking 0 ETH didn't revert β it silently succeeded
- Rewards math overflowed after ~290 days at maximum reward rate
- Same-block stake+unstake bypassed minimum lock period
- Emergency withdraw didn't pause the contract β users could stake into a drained pool
- Reward rate change applied retroactively to existing stakers (likely unintended)
- No event emitted on emergency withdraw β off-chain indexers would miss it
unstakewith amount > staked balance reverted with generic error, not descriptive- Multiple claims in same block doubled rewards due to checkpoint timing
None of these were exploitable bugs in the traditional sense. But 3 of them would have caused real user confusion, and 2 could have led to incorrect reward distributions.
The Results
| Metric | Developer Tests | Agent Tests |
|---|---|---|
| Test cases | 12 | 50 |
| Coverage | 78% | 97% |
| Edge cases found | 0 | 8 undocumented behaviors |
| Reentrancy tests | 0 | 4 |
| Gas snapshots | 0 | 5 |
| Time to write | 1 day | 10 minutes |
| Ready to run | Yes | Yes (copy, paste, npx hardhat test) |
Try It Yourself
Feed your contract to an agent with the instruction: "Generate a comprehensive test suite covering happy paths, edge cases, access control, reentrancy, overflow, and timing-dependent behavior. Include gas snapshots." The agent doesn't get bored, doesn't skip the tedious cases, and doesn't assume the code works as intended.
Your tests should be adversarial. Your developer probably isn't.
Related case studies
Software Engineer
Solidity Gas Optimization β Agent Saved 40% on Contract Deployment
How an AI agent analyzed a Solidity smart contract function-by-function and cut deployment gas costs by 40% in 15 minutes. Storage packing, calldata optimization, and loop unrolling explained.
Software Engineer
Smart Contract Audit β Found 3 Vulnerabilities Before Deployment
An AI agent audited a Solidity smart contract in 5 minutes and found 3 vulnerabilities including a critical reentrancy bug β work that would cost $5-50K from a professional auditor.
Full-Stack Developer
Auto-Generated API Documentation β From Code to Docs in 3 Minutes
How an AI agent reads Django views, serializers, and models to generate complete OpenAPI specs and markdown API docs in 3 minutes. Auto-updates on every merge.
Want results like these?
Start free with your own AI team. No credit card required.