Default Stack
If the repo doesn't define its own tooling, use:
- Foundry — build, test, scripts (
forge, cast, anvil, chisel)
- forge fmt — formatting (
sort_imports = true, contract_new_lines = true, bracket_spacing = true)
- solhint
^6.0.0 — linting, run with --max-warnings=0 --noPoster
- GitHub Actions — CI/CD, separate
build_and_test and linter jobs
- Husky —
pre-commit hook runs npm run lint:check
Testing
- Forge tests only —
forge test -vvv. No Hardhat/Truffle unless the repo already uses them.
- Test files —
test/**/*.t.sol, one contract under test per file, named <ContractOrFeature>.t.sol.
- Fuzz tests — use Foundry's built-in fuzzing: parameterize test inputs (
function testFoo(uint256 x) public) and let forge generate random inputs. Add vm.assume(...) for invariants, bound inputs with bound(x, min, max) rather than if-guards.
- Invariant tests — for stateful properties, use
forge-std/StdInvariant.sol with handler contracts.
- Gas snapshots —
forge snapshot for regression tracking when gas matters.
- Coverage —
forge coverage --report lcov in CI.
Linter Rules (solhint)
Default is the strictest available. Extend solhint:all (every rule enabled) and run with --max-warnings=0 so warnings block CI.
Logic (.solhint.json):
{
"extends": "solhint:all",
"rules": {
"compiler-version": ["error", "^0.8.22"]
}
}
Tests (test/.solhint.json) — minimum opt-outs for practical Foundry patterns:
{
"extends": "solhint:all",
"rules": {
"compiler-version": ["error", "^0.8.22"],
"one-contract-per-file": "off",
"no-console": "off",
"func-name-mixedcase": "off",
"reason-string": "off"
}
}
Relax individual rules in-project only when you hit a real conflict — never pre-emptively.
Scripts
Add to package.json:
"lint:check": "yarn lint:version && yarn lint:sol-logic && yarn lint:sol-tests && forge fmt --check",
"lint:fix": "forge fmt && yarn lint:sol-tests --fix && yarn lint:sol-logic --fix",
"lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' --max-warnings=0 --noPoster",
"lint:sol-tests": "solhint -c test/.solhint.json 'test/**/*.sol' --max-warnings=0 --noPoster",
"lint:version": "solhint --version"
(Swap src/ ↔ contracts/ to match the repo's foundry.toml src =.)
Scratch Testing
NEVER use inline Solidity via forge script --via-stdin or heredocs.
For ad-hoc exploration:
- Chisel (
chisel) — REPL for one-off expressions and cheatcode checks.
script/Scratch.s.sol — throwaway forge script in repo root's script/, run with forge script script/Scratch.s.sol. Comment out previous blocks to keep history. Gitignored — never commit; move to a real script if the code should persist.
Reference
- solhint.json.example — copy-paste starter.
- foundry.toml.example — copy-paste starter.
1---2name: solidity-conventions3description: Apply Solidity project conventions — Foundry only (forge, cast, anvil, chisel; no Hardhat or Truffle), forge fmt with sort_imports, solhint:all strict (--max-warnings=0 --noPoster) extending compiler-version ^0.8.22, fuzz tests via Foundry's built-in fuzzing, invariant tests via forge-std/StdInvariant.sol with handlers, gas snapshots via forge snapshot, coverage via forge coverage --report lcov, and script/Scratch.s.sol or chisel for scratch (never inline forge script heredocs). Use when starting a Solidity project, writing or reviewing Solidity contracts and tests, configuring Foundry or solhint, or evaluating compliance with these defaults. Co-activates with running-tdd-cycles, reviewing-changes, and engineering-philosophy.4---5
6## Default Stack
7
8If the repo doesn't define its own tooling, use:
9
10- **Foundry** — build, test, scripts (`forge`, `cast`, `anvil`, `chisel`)
11- **forge fmt** — formatting (`sort_imports = true`, `contract_new_lines = true`, `bracket_spacing = true`)
12- **solhint** `^6.0.0` — linting, run with `--max-warnings=0 --noPoster`
13- **GitHub Actions** — CI/CD, separate `build_and_test` and `linter` jobs
14- **Husky** — `pre-commit` hook runs `npm run lint:check`
15
16## Testing
17
18- **Forge tests only** — `forge test -vvv`. No Hardhat/Truffle unless the repo already uses them.
19- **Test files** — `test/**/*.t.sol`, one contract under test per file, named `<ContractOrFeature>.t.sol`.
20- **Fuzz tests** — use Foundry's built-in fuzzing: parameterize test inputs (`function testFoo(uint256 x) public`) and let `forge` generate random inputs. Add `vm.assume(...)` for invariants, bound inputs with `bound(x, min, max)` rather than `if`-guards.
21- **Invariant tests** — for stateful properties, use `forge-std/StdInvariant.sol` with handler contracts.
22- **Gas snapshots** — `forge snapshot` for regression tracking when gas matters.
23- **Coverage** — `forge coverage --report lcov` in CI.
24
25## Linter Rules (solhint)
26
27**Default is the strictest available.** Extend `solhint:all` (every rule enabled) and run with `--max-warnings=0` so warnings block CI.
28
29Logic (`.solhint.json`):
30
31```json
32{
33 "extends": "solhint:all",
34 "rules": {
35 "compiler-version": ["error", "^0.8.22"]
36 }
37}
38```
39
40Tests (`test/.solhint.json`) — minimum opt-outs for practical Foundry patterns:
41
42```json
43{
44 "extends": "solhint:all",
45 "rules": {
46 "compiler-version": ["error", "^0.8.22"],
47 "one-contract-per-file": "off",
48 "no-console": "off",
49 "func-name-mixedcase": "off",
50 "reason-string": "off"
51 }
52}
53```
54
55Relax individual rules in-project only when you hit a real conflict — never pre-emptively.
56
57## Scripts
58
59Add to `package.json`:
60
61```json
62"lint:check": "yarn lint:version && yarn lint:sol-logic && yarn lint:sol-tests && forge fmt --check",
63"lint:fix": "forge fmt && yarn lint:sol-tests --fix && yarn lint:sol-logic --fix",
64"lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' --max-warnings=0 --noPoster",
65"lint:sol-tests": "solhint -c test/.solhint.json 'test/**/*.sol' --max-warnings=0 --noPoster",
66"lint:version": "solhint --version"
67```
68
69(Swap `src/` ↔ `contracts/` to match the repo's `foundry.toml` `src =`.)
70
71## Scratch Testing
72
73**NEVER use inline Solidity via `forge script --via-stdin` or heredocs.**
74
75For ad-hoc exploration:
76- **Chisel** (`chisel`) — REPL for one-off expressions and cheatcode checks.
77- **`script/Scratch.s.sol`** — throwaway forge script in repo root's `script/`, run with `forge script script/Scratch.s.sol`. Comment out previous blocks to keep history. Gitignored — never commit; move to a real script if the code should persist.
78
79## Reference
80
81- [solhint.json.example](reference/solhint.json.example) — copy-paste starter.
82- [foundry.toml.example](reference/foundry.toml.example) — copy-paste starter.