src/: Solidity contracts (Solc 0.8.33).test/: Forge tests (*.t.sol), plustest/fork/*for fork/integration and deployment tests.script/: Forge scripts (deploy/verify, per-chain variants).artifacts/,broadcast/,out/,cache/: build, deploy, and fork outputs.gists/: small code examples related to the module.node_modules/: dependencies; seeremappings.txt.docs/: documentation and design notes.docs/src/: generated Forge documentation output. It is not the source of truth and may be stale; do not edit it manually. Update Solidity/NatSpec or source documentation instead, then runforge docand commit the generated output.
-
just deps: install production deps;just deps-dev: dev deps + husky. -
just: clean, deps, build, and run all tests. -
just build: build the project skipping tests and scripts (preferable for faster iterations); useforge buildfor compile all files of the project. -
just test-unit: unit tests only;just test-all: unit + fork suites. -
just test-local: spins up anvil fork, deploys, runs deployment+integration tests. -
just coverage|just coverage-lcov: coverage (LCOV saved; seelcov.html). -
Linting:
yarn lint:check(prettier + solhint),yarn lint:fix,yarn lint:solhint. -
Diff:
git diff --no-ext-diff -
For fast local checks after small edits, prefer targeted compile of changed Solidity files:
forge build <changed-file-1> <changed-file-2> .... -
Keep
forge buildas the full-project compile when touching shared interfaces/libraries or before final handoff.
- Formatting: Prettier +
prettier-plugin-solidity(SolidityprintWidth=80,tabWidth=4, spaces only). - Linting: Solhint (
.solhint.json) withsolhint:recommendedandsolhint-plugin-lido-csm. - Versions: enforce
pragma solidity 0.8.33(compiler-versionrule). - Naming: contracts/libraries
CamelCase(e.g.,CSModule,AssetRecovererLib), interfacesIName(rule:interface-starts-with-i). - Inline assembly should be well documented, preferably every non-trivial line with its own comment.
- Conventions: prefer custom errors, calldata parameters, and struct packing (gas rules). Immutable vars styled as constants.
- Keep things in one function unless composable or reusable.
- Prefer short variable names where possible. Prefer same length variable names for related things.
- Prefer early returns to else statements.
- While refactoring keep comments added from existing implementations where applicable.
- Make sure external functions in contracts and interfaces have proper natspec.
- Avoid using magic numbers, prefer re-using or defining constants.
- Inline values used only once where the variable name does not add legibility.
- When last I looked, the year was 2026.
- Framework: Foundry/Forge with fuzzing (
fuzz.runs=256). - Structure: unit tests in
test/*.t.sol; fork suites undertest/fork/*(deployment/integration). - Run:
just test-unitfor fast cycles;CHAIN/RPC_URLrequired for fork tests. Example:export CHAIN=hoodi && export RPC_URL=<https-url>. - Coverage:
just coverage-lcovproduces LCOV output (commit if relevant). - After making changes to the source code make sure you've either ran build command or unit tests.
vm.prank(addr)applies to the next external call only. Avoid patterns where the next call is accidentally consumed by a getter inside arguments or call-chaining. Bad:vm.prank(admin); target.grantRole(target.ROLE(), user)orvm.prank(admin); module.PARAMETERS_REGISTRY().setX(...). Good: precompute external values before prank (bytes32 role = target.ROLE();) or usevm.startPrank/vm.stopPrankfor multi-call sequences.- Do not assert unchanged state after a reverting call.
- Order tests: happy path first, revert cases afterwards.
- Deployment test name suffixes are part of the test selection contract used by
justrecipes and encode two axes: phase and flow. - Phase semantics:
*_scratch*: checks for post-deploy, pre-vote state only.*_afterVote*: checks for post-governance state only; these should validate changes introduced by vote execution (script/fork-helpers/SimulateVote.s.sol), e.g. upgrades, finalize steps, role migrations, pause/resume transitions.- Flow semantics:
*_onlyFull*: checks that run only in full deployment flows.- Combined semantics:
*_scratch_onlyFull*: scratch-phase checks that also require full-flow context.- No suffix (
test_*): use only for invariants expected to hold in every phase/flow where the suite is executed. - Naming rule: choose suffixes based on the state transition under test; if a check depends on vote-executed effects, it must include
_afterVote.
- Protocol rollout is two-phase: deployment first, vote execution second.
- Deployment phase: deploy scripts should deploy new implementations/helpers and execute all privileged setup that is possible while deployer temporarily has admin rights.
- Deployment phase must end in post-handoff state: required admin roles are returned to the protocol agent and deployer admin rights are revoked.
- Vote phase: simulate-vote scripts should apply governance actions to already deployed protocol contracts only (proxy upgrades, role/state transitions), not deployment-only setup.
- Vote phase steps should be explicit and deterministic: encode concrete, ordered calls with known addresses; avoid generic loop-based governance steps unless explicitly required.
- Commits: Conventional Commits. Examples:
feat: add validator key rotation,fix: resolve bond calculation overflow. - PRs: clear description, linked issues, rationale, test coverage notes, storage layout impacts (if any), and gas impact (attach
GAS.mdwhen updated). Ensure CI green andyarn lint:checkpasses.
- If a feature request is underspecified, ask targeted questions before implementing. Do not invent requirements or omit important details—surface uncertainties explicitly.
- Never commit keys; use
.envfrom.env.sample. For forks:ANVIL_IP_ADDR,ANVIL_PORT,RPC_URL,CHAIN. - Pin Foundry to version in
.foundryref. Usejust make-fork/just kill-forkto manage local forks.