算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
blockchain-testing【设计与创意】Use this skill when asked about testing smart contracts, Foundry tests, Hardhat tests, fuzz testing, invariant testing, property-based testing, formal verification, audit preparation, integration testing for dApps, and blockchain testing patterns. Covers Foundry cheatcodes, Echidna fuzzing, Certora verification, Hardhat network forking, mainnet simul…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Guide blockchain smart contract testing covering the full testing pyramid: unit tests, integration tests, fuzz tests, invariant tests, formal verification, and audit preparation. Ensures contract correctness before deployment.
"smart contract test", "foundry test", "forge test", "hardhat test", "truffle test", "fuzz testing", "invariant testing", "property-based testing", "echidna", "certora", "formal verification", "audit preparation", "blockchain testing", "web3 testing", "dapp testing", "contract audit", "gas benchmark", "mainnet fork test", "integration test blockchain", "e2e blockchain test", "foundry cheatcode", "forge snapshot"
Testing strategy specification: framework setup, test organization, fuzz/invariant properties, gas benchmarking, and audit preparation checklist.
4000 tokens
Blockchain platform:
├── EVM (Ethereum, Polygon, Arbitrum, etc.)
│ ├── Solidity → Foundry (default — fastest, most ergonomic)
│ │ ├── forge test: unit + integration + fuzz
│ │ ├── forge coverage: line/branch coverage
│ │ ├── forge snapshot: gas benchmarking
│ │ └── Foundry fuzz: built-in parameterized + stateful fuzzing
│ ├── JavaScript/TypeScript → Hardhat
│ │ ├── Hardhat network: mainnet forking, mining control
│ │ ├── Hardhat chai matchers: ethers-native assertions
│ │ └── Hardhat console.log: debug in Solidity
│ └── Legacy → Truffle (not recommended)
├── Solana → Anchor test framework
│ ├── TypeScript SDK for integration tests
│ ├── Local validator (solana-test-validator)
│ └── Mainnet fork via bankrun
├── Cosmos → Cosmos SDK test suite
│ ├── Go unit tests for modules
│ ├── Integration tests with simapp
│ └── E2E with test network
└── Cairo → StarkNet Foundry / Protostar
What to test:
├── Individual function correctness?
│ ├── Unit test: specific input → expected output
│ ├── Boundary test: edge cases (0, max, negative, empty)
│ └── Revert test: expected failure conditions
├── Parameter-based correctness?
│ ├── Fuzz test: random inputs for numeric parameters
│ ├── Stateless fuzz: each call independent (forge fuzz)
│ └── Stateful fuzz: sequence of calls with state (foundry invariant, echidna)
├── Protocol-level properties?
│ ├── Invariant test: properties that must always hold
│ └── Examples: totalSupply = sum of balances, solvency, access control
├── Real-world interaction?
│ ├── Mainnet fork test: interact with deployed protocols
│ ├── Integration test: multi-contract flows
│ └── E2E test: complete user journey
└── Security?
├── Static analysis (Slither, Aderyn)
├── Symbolic execution (Mythril, Halmos)
├── Formal verification (Certora CVL)
└── Manual review (line-by-line)
forge test options:
├── Verbosity: -vv (debug), -vvv (stack traces), -vvvv (all traces)
├── Match test: --match-test testTransfer -vv
├── Match contract: --match-contract TokenTest
├── Fuzz runs: --fuzz-runs 10000 (default 256)
├── Fuzz seed: --fuzz-seed 42 (reproducible)
├── Gas reporting: --gas-report
├── Coverage: forge coverage --report lcov
├── Fork: --fork-url $RPC_URL --fork-block-number 18000000
└── Multi-fork: --chain-id 1,137,42161 (simulate cross-chain)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
Token public token;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
function setUp() public {
token = new Token("Test", "TST", 18);
token.mint(alice, 1000 ether);
}
function testTransfer() public {
vm.prank(alice);
token.transfer(bob, 100 ether);
assertEq(token.balanceOf(alice), 900 ether);
assertEq(token.balanceOf(bob), 100 ether);
}
function testTransferInsufficientBalance() public {
vm.prank(bob);
vm.expectRevert();
token.transfer(alice, 1 ether);
}
}
contract FuzzTest is Test {
Token public token;
function setUp() public {
token = new Token("Test", "TST", 18);
}
// Stateless fuzz: random inputs, single call
function testFuzzTransfer(uint256 amount) public {
address alice = makeAddr("alice");
token.mint(alice, type(uint96).max); // bound to reasonable max
vm.assume(amount > 0);
vm.assume(amount <= token.balanceOf(alice));
vm.prank(alice);
token.transfer(makeAddr("bob"), amount);
}
// Handler-based stateful fuzz
function testStatefulTransfer(StatefulHandler handler) public {
// handler runs random sequences of operations
// invariant checked after each sequence
}
}
// Invariant: totalSupply always equals sum of all balances
contract TokenInvariants is Test {
Token public token;
TokenHandler public handler;
function setUp() public {
token = new Token("Test", "TST", 18);
handler = new TokenHandler(token);
targetContract(address(handler));
}
function invariant_totalSupply_equals_sum_of_balances() public {
uint256 totalSupply = token.totalSupply();
uint256 sumBalances = handler.sumAllBalances();
assertEq(totalSupply, sumBalances);
}
function invariant_no_zero_address_balance() public {
assertEq(token.balanceOf(address(0)), 0);
}
}
contract ForkTest is Test {
using stdStorage for StdStorage;
IUniswapV2Router router;
IERC20 token;
function setUp() public {
// Fork mainnet at specific block
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"), 18000000);
router = IUniswapV2Router(0x7a250d5646B4cF539739dF2C5dAcb4c659F2488D);
token = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // USDC
// Deal tokens to test account
deal(address(token), address(this), 100_000e6);
}
function testSwapOnMainnet() public {
token.approve(address(router), 1000e6);
address[] memory path = new address[](2);
path[0] = address(token);
path[1] = router.WETH();
uint256[] memory amounts = router.swapExactTokensForETH(
1000e6, 0, path, address(this), block.timestamp
);
assertGt(amounts[1], 0); // Got some ETH
}
}
// Core cheatcodes for testing
contract CheatcodeDemo is Test {
// State manipulation
// vm.store(address, bytes32 slot, bytes32 value) — Write storage directly
// vm.load(address, bytes32 slot) → bytes32 — Read storage directly
// vm.etch(address, bytes code) — Set contract code
// vm.deal(address, uint256 amount) — Set ETH balance
// vm.prank(address) — Set msg.sender for next call
// vm.startPrank(address) — Set msg.sender for all calls
// vm.stopPrank() — Stop prank
// vm.roll(uint256 blockNumber) — Set block.number
// vm.warp(uint256 timestamp) — Set block.timestamp
// vm.fee(uint256 basefee) — Set basefee
// vm.difficulty(uint256 difficulty) — Set block.difficulty
// vm.label(address, string label) — Label address in traces
// Forking
// vm.createFork(url, blockNumber) → uint256 — Create fork
// vm.selectFork(uint256 forkId) — Switch active fork
// vm.activeFork() → uint256 — Get active fork
// vm.rollFork(uint256 blockNumber, uint256 forkId) — Roll fork to block
// vm.makePersistent(address) — Keep across fork switches
// Testing utilities
// vm.expectRevert(bytes) — Expect next call to revert
// vm.expectEmit(...) — Expect event emission
// vm.expectCall(address, bytes) — Expect a call
// vm.record() — Start recording storage reads
// vm.accesses(address) → (bytes32[], bytes32[]) — Get accessed storage slots
// Assertions
// assertEq(a, b) — Assert equality
// assertTrue(condition) — Assert true
// assertGt(a, b) — Assert greater than
// assertApproxEqAbs(a, b, delta) — Assert approx equality
// assertEqDecimal(a, b, decimals) — Assert with decimals
}
// forge snapshot — captures gas costs in .gas-snapshot
// forge diff-check — compares against previous snapshot
// forge snapshot --diff — shows gas cost changes
contract GasBenchmark is Test {
using stdStorage for StdStorage;
Token public token;
function setUp() public {
token = new Token("Test", "TST", 18);
}
function testGasTransfer() public {
vm.prank(makeAddr("alice"));
token.transfer(makeAddr("bob"), 100 ether);
// forge snapshot captures this call's gas
}
}
// Gas snapshot output format:
// testGasTransfer() (gas: ~48723)
Phase 1: Static Analysis
├── slither src/ — detect vulnerabilities
├── aderyn src/ — Solidity spec violations
└── semgrep — custom rule patterns
Phase 2: Fuzz Testing
├── Foundry fuzz — parameterized fuzzing (10K+ runs)
├── Echidna — property-based fuzzing (stateful)
└── Medusa — parallel fuzzing (multi-core)
Phase 3: Formal Verification
├── Certora CVL — key invariants (solvency, access)
├── Halmos — symbolic testing for complex assertions
└── Scribble — annotation-based formal specs
Phase 4: Integration
├── Mainnet fork test — real protocol interactions
├── Slither upgrade check — UUPS upgrade safety
└── Gas snapshot — cost regression check
Pre-Audit Checklist:
- [ ] All functions have unit tests with boundary conditions
- [ ] Fuzz tests written for all numeric inputs (>10K runs each)
- [ ] Invariant tests cover protocol-level properties
- [ ] Full test suite passes with forge coverage >80%
- [ ] Slither passes with no high-severity findings
- [ ] Aderyn passes with no spec violations
- [ ] Mainnet fork tests validate real-world interactions
- [ ] Gas snapshot shows no unexpected regressions
- [ ] Upgrade tests validate proxy storage compatibility
- [ ] Access control tests verify all role/permission paths
- [ ] Emergency pause tested in all relevant scenarios
- [ ] Reentrancy guard tested with malicious contract callback
- [ ] Flash loan resistance tested with price manipulation simulation
import {Test} from "forge-std/Test.sol";
import {MyVault} from "../src/MyVault.sol";
contract MyVaultTest is Test {
MyVault vault;
address alice = makeAddr("alice");
uint256 constant BAL = 1000e18;
function setUp() public {
vault = new MyVault();
deal(alice, BAL);
}
// Unit test
function test_Deposit() public {
vm.prank(alice);
uint256 shares = vault.deposit{value: 100e18}(100e18, alice);
assertEq(shares, 100e18, "Shares == assets at 1:1");
}
// Fuzz test
function testFuzz_Deposit_RoundTrip(uint256 amount) public {
vm.assume(amount > 0 && amount <= BAL);
vm.prank(alice);
uint256 shares = vault.deposit{value: amount}(amount, alice);
vm.prank(alice);
uint256 returned = vault.withdraw(shares, alice, alice);
assertEq(returned, amount, "Roundtrip preserves value");
}
// Invariant test
function invariant_Solvency() public {
assertGe(address(vault).balance, vault.totalSupply());
}
}
contract VaultHandler is Test {
address[] users;
function deposit(uint256 amount, uint256 seed) public {
address u = users[seed % users.length];
amount = bound(amount, 1, u.balance);
vm.prank(u);
vault.deposit{value: amount}(amount, u);
}
}
Blockchain Testing Strategy
├── Testing level?
│ ├── Unit → Foundry (forge test, fastest)
│ ├── Integration → Foundry fork testing / Hardhat mainnet fork
│ ├── Fuzz → Foundry fuzz (property-based)
│ └── E2E → Hardhat + tenderly / local node (Ganache, Anvil)
├── Coverage target?
│ ├── 100% line coverage → Unit + integration tests
│ ├── 100% invariant coverage → Fuzz + symbolic execution
│ └── Critical path only → Integration tests for high-value functions
├── Fork testing?
│ ├── Yes → Forge fork (anvil + fork URL)
│ ├── Yes (extensive) → Tenderly virtual testnet
│ └── No → Mock external contracts
└── CI integration?
├── Yes → Forge test in GitHub Actions / CircleCI
└── No → Local testing only (risky)
Decision criteria: Evaluate contract complexity, value at risk, team testing maturity, and CI infrastructure.
// blockchain-testing/test/Token.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
Token public token;
function setUp() public {
token = new Token("Test", "TST", 1e18);
}
function testTransfer() public {
address alice = makeAddr("alice");
token.transfer(alice, 100);
assertEq(token.balanceOf(alice), 100);
vm.expectRevert("ERC20: insufficient balance");
token.transfer(alice, 1e30);
}
function testFuzzTransfer(uint256 amount) public {
address alice = makeAddr("alice");
amount = bound(amount, 1, token.totalSupply());
token.transfer(alice, amount);
assertEq(token.balanceOf(alice), amount);
}
}
// blockchain-testing/test/Invariant.t.sol
contract InvariantTest is Test {
ERC20 public token;
address[] public holders;
function invariant_total_supply() public {
uint256 circulating = 0;
for (uint256 i = 0; i < holders.length; i++) {
circulating += token.balanceOf(holders[i]);
}
assertEq(circulating, token.totalSupply());
}
}
forge coverage to measure.forge snapshot for gas cost regression; CI checks gas changes vs baseline.| Anti-Pattern | Consequence | Solution |
|---|---|---|
| No fork testing | Miss integration bugs with existing protocols | Fork test at matching protocol versions |
| Low fuzz runs (< 1000) | Miss edge cases | Minimum 5000 fuzz runs per property |
| Ignoring invariant tests | Protocol invariant violated in production | Define 5+ invariants per protocol |
| No gas snapshot | Unexpected gas cost increase | CI fails if gas > baseline + 10% |
| Testing only happy path | Miss revert conditions | Test all require/assert failure paths |
forge test --no-match-contract "Fork" for fast unit tests; parallel CI runners.bound() to reduce search space; use targeted fuzzing for critical funcs.vm.prank and vm.startPrank for isolated test contexts; reset state between tests.forge test --no-match-contract "Fork" --timeout 300) to prevent CI hangs.blockchain → blockchain-testing