testing
Smart contract testing with Foundry — unit tests, fuzz testing, fork testing, invariant testing. Use when writing tests for a smart contract.
What this skill does
# Smart Contract Testing
## What You Probably Got Wrong
**You test getters and trivial functions.** Testing that `name()` returns the name is worthless. Test edge cases, failure modes, and economic invariants — the things that lose money when they break.
**You don't fuzz.** `forge test` finds the bugs you thought of. Fuzzing finds the ones you didn't. If your contract does math, fuzz it. If it handles user input, fuzz it. If it moves value, definitely fuzz it.
**You don't fork-test.** If your contract calls Uniswap, Aave, or any external protocol (verified addresses: `addresses/SKILL.md`), test against their real deployed contracts on a fork. Mocking them hides integration bugs that only appear with real state.
**You write tests that mirror the implementation.** Testing that `deposit(100)` sets `balance[user] = 100` is tautological — you're testing that Solidity assignments work. Test properties: "after deposit and withdraw, user gets their tokens back." Test invariants: "total deposits always equals contract balance."
**You skip invariant testing for stateful protocols.** If your contract has multiple interacting functions that change state over time (vaults, AMMs, lending), you need invariant tests. Unit tests check one path; invariant tests check that properties hold across thousands of random sequences.
---
## Unit Testing with Foundry
### Test File Structure
```solidity
// test/MyContract.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken public token;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
function setUp() public {
token = new MyToken("Test", "TST", 1_000_000e18);
// Give alice some tokens for testing
token.transfer(alice, 10_000e18);
}
function test_TransferUpdatesBalances() public {
vm.prank(alice);
token.transfer(bob, 1_000e18);
assertEq(token.balanceOf(alice), 9_000e18);
assertEq(token.balanceOf(bob), 1_000e18);
}
function test_TransferEmitsEvent() public {
vm.expectEmit(true, true, false, true);
emit Transfer(alice, bob, 500e18);
vm.prank(alice);
token.transfer(bob, 500e18);
}
function test_RevertWhen_TransferExceedsBalance() public {
vm.prank(alice);
vm.expectRevert();
token.transfer(bob, 999_999e18); // More than alice has
}
function test_RevertWhen_TransferToZeroAddress() public {
vm.prank(alice);
vm.expectRevert();
token.transfer(address(0), 100e18);
}
}
```
### Key Assertion Patterns
```solidity
// Equality
assertEq(actual, expected);
assertEq(actual, expected, "descriptive error message");
// Comparisons
assertGt(a, b); // a > b
assertGe(a, b); // a >= b
assertLt(a, b); // a < b
assertLe(a, b); // a <= b
// Approximate equality (for math with rounding)
assertApproxEqAbs(actual, expected, maxDelta);
assertApproxEqRel(actual, expected, maxPercentDelta); // in WAD (1e18 = 100%)
// Revert expectations
vm.expectRevert(); // Any revert
vm.expectRevert("Insufficient balance"); // Specific message
vm.expectRevert(MyContract.CustomError.selector); // Custom error
// Event expectations
vm.expectEmit(true, true, false, true); // (topic1, topic2, topic3, data)
emit MyEvent(expectedArg1, expectedArg2);
```
### What to Actually Test
```solidity
// ✅ TEST: Edge cases that lose money
function test_TransferZeroAmount() public { /* ... */ }
function test_TransferEntireBalance() public { /* ... */ }
function test_TransferToSelf() public { /* ... */ }
function test_ApproveOverwrite() public { /* ... */ }
function test_TransferFromWithExactAllowance() public { /* ... */ }
// ✅ TEST: Access control
function test_RevertWhen_NonOwnerCallsAdminFunction() public { /* ... */ }
function test_OwnerCanPause() public { /* ... */ }
// ✅ TEST: Failure modes
function test_RevertWhen_DepositZero() public { /* ... */ }
function test_RevertWhen_WithdrawMoreThanDeposited() public { /* ... */ }
function test_RevertWhen_ContractPaused() public { /* ... */ }
// ❌ DON'T TEST: OpenZeppelin internals
// function test_NameReturnsName() — they already tested this
// function test_SymbolReturnsSymbol() — waste of time
// function test_DecimalsReturns18() — it does, trust it
```
---
## Fuzz Testing
Foundry automatically fuzzes any test function with parameters. Instead of testing one value, it tests hundreds of random values.
### Basic Fuzz Test
```solidity
// Foundry calls this with random amounts
function testFuzz_DepositWithdrawRoundtrip(uint256 amount) public {
// Bound input to valid range
amount = bound(amount, 1, token.balanceOf(alice));
uint256 balanceBefore = token.balanceOf(alice);
vm.startPrank(alice);
token.approve(address(vault), amount);
vault.deposit(amount, alice);
vault.withdraw(vault.balanceOf(alice), alice, alice);
vm.stopPrank();
// Property: user gets back what they deposited (minus any fees)
assertGe(token.balanceOf(alice), balanceBefore - 1); // Allow 1 wei rounding
}
```
### Bounding Inputs
```solidity
// bound() is preferred over vm.assume() — bound reshapes, assume discards
function testFuzz_Fee(uint256 amount, uint256 feeBps) public {
amount = bound(amount, 1e6, 1e30); // Reasonable token amounts
feeBps = bound(feeBps, 1, 10_000); // 0.01% to 100%
uint256 fee = (amount * feeBps) / 10_000;
uint256 afterFee = amount - fee;
// Property: fee + remainder always equals original
assertEq(fee + afterFee, amount);
}
// vm.assume() discards inputs — use sparingly
function testFuzz_Division(uint256 a, uint256 b) public {
vm.assume(b > 0); // Skip zero (would revert)
// ...
}
```
### Run with More Iterations
```bash
# Default: 256 runs
forge test
# More thorough: 10,000 runs
forge test --fuzz-runs 10000
# Set in foundry.toml for CI
# [fuzz]
# runs = 1000
```
---
## Fork Testing
Test your contract against real deployed protocols on a mainnet fork. This catches integration bugs that mocks can't.
### Basic Fork Test
```solidity
contract SwapTest is Test {
// Real mainnet addresses — full verified list: addresses/SKILL.md
address constant UNISWAP_ROUTER = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
function setUp() public {
// Fork mainnet at a specific block for reproducibility
vm.createSelectFork("mainnet", 19_000_000);
}
function test_SwapETHForUSDC() public {
address user = makeAddr("user");
vm.deal(user, 1 ether);
vm.startPrank(user);
// Build swap path
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: WETH,
tokenOut: USDC,
fee: 3000,
recipient: user,
amountIn: 0.1 ether,
amountOutMinimum: 0, // In production, NEVER set to 0
sqrtPriceLimitX96: 0
});
// Execute swap
uint256 amountOut = ISwapRouter(UNISWAP_ROUTER).exactInputSingle{value: 0.1 ether}(params);
vm.stopPrank();
// Verify we got USDC back
assertGt(amountOut, 0, "Should receive USDC");
assertGt(IERC20(USDC).balanceOf(user), 0);
}
}
```
### When to Fork-Test
- **Always:** Any contract that calls an external protocol (Uniswap, Aave, Chainlink)
- **Always:** Any contract that handles tokens with quirks (USDT, fee-on-transfer, rebasing)
- **Always:** Any contract that reads oracle prices
- **Never:** Pure logic contracts with no external calls — use unit tests
### Running Fork Tests
```bash
# Fork from RPC URelated in Security
mac-ops
IncludedComprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.
a11y-audit
IncludedRun accessibility audits on web projects combining automated scanning (axe-core, Lighthouse) with WCAG 2.1 AA compliance mapping, manual check guidance, and structured reporting. Output is configurable: markdown report only, markdown plus machine-readable JSON, or markdown plus issue tracker integration. Use this skill whenever the user mentions "accessibility audit", "a11y audit", "WCAG audit", "accessibility check", "compliance scan", or asks to check a web project for accessibility issues. Also trigger when the user wants to verify WCAG conformance or map findings to a specific standard (CAN-ASC-6.2, EN 301 549, ADA/AODA).
erpclaw
IncludedAI-native ERP system with self-extending OS. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting. 413 actions across 14 domains, 43 expansion modules. Constitutional guardrails, adversarial audit, schema migration. Double-entry GL, immutable audit trail, US GAAP.
assess
IncludedAssesses and rates quality 0-10 across multiple dimensions (correctness, maintainability, security, performance, testability, simplicity) with pros/cons analysis. Compares against project conventions and prior decisions from memory. Produces structured evaluation reports with actionable improvement suggestions. Use when evaluating code, designs, architectures, or comparing alternative approaches.
spring-boot-security-jwt
IncludedProvides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
code-hardcode-audit
IncludedDetect hardcoded values, magic numbers, and leaked secrets. TRIGGERS - hardcode audit, magic numbers, PLR2004, secret scanning.