@zigrivers/scaffold 3.25.1 → 3.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -7
- package/content/knowledge/web3/web3-access-control.md +189 -0
- package/content/knowledge/web3/web3-architecture.md +162 -0
- package/content/knowledge/web3/web3-audit-workflow.md +151 -0
- package/content/knowledge/web3/web3-common-vulnerabilities.md +171 -0
- package/content/knowledge/web3/web3-conventions.md +162 -0
- package/content/knowledge/web3/web3-deployment-and-verification.md +216 -0
- package/content/knowledge/web3/web3-dev-environment.md +150 -0
- package/content/knowledge/web3/web3-gas-optimization.md +165 -0
- package/content/knowledge/web3/web3-oracles-and-external-data.md +155 -0
- package/content/knowledge/web3/web3-project-structure.md +212 -0
- package/content/knowledge/web3/web3-requirements.md +152 -0
- package/content/knowledge/web3/web3-security.md +163 -0
- package/content/knowledge/web3/web3-testing.md +180 -0
- package/content/knowledge/web3/web3-upgradeability.md +189 -0
- package/content/methodology/web3-overlay.yml +40 -0
- package/dist/cli/commands/complete.d.ts.map +1 -1
- package/dist/cli/commands/complete.js +6 -13
- package/dist/cli/commands/complete.js.map +1 -1
- package/dist/cli/commands/complete.test.js +18 -0
- package/dist/cli/commands/complete.test.js.map +1 -1
- package/dist/cli/commands/knowledge.test.js +4 -4
- package/dist/cli/commands/knowledge.test.js.map +1 -1
- package/dist/cli/commands/observe.d.ts +58 -0
- package/dist/cli/commands/observe.d.ts.map +1 -0
- package/dist/cli/commands/observe.js +430 -0
- package/dist/cli/commands/observe.js.map +1 -0
- package/dist/cli/commands/observe.test.d.ts +2 -0
- package/dist/cli/commands/observe.test.d.ts.map +1 -0
- package/dist/cli/commands/observe.test.js +452 -0
- package/dist/cli/commands/observe.test.js.map +1 -0
- package/dist/cli/commands/run.js +3 -3
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/run.test.js +1 -1
- package/dist/cli/commands/run.test.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/schema.d.ts +672 -126
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +8 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +2 -2
- package/dist/config/schema.test.js.map +1 -1
- package/dist/config/validators/index.d.ts.map +1 -1
- package/dist/config/validators/index.js +2 -0
- package/dist/config/validators/index.js.map +1 -1
- package/dist/config/validators/web3.d.ts +4 -0
- package/dist/config/validators/web3.d.ts.map +1 -0
- package/dist/config/validators/web3.js +15 -0
- package/dist/config/validators/web3.js.map +1 -0
- package/dist/e2e/project-type-overlays.test.js +76 -0
- package/dist/e2e/project-type-overlays.test.js.map +1 -1
- package/dist/observability/adapters/audit-history.d.ts +17 -0
- package/dist/observability/adapters/audit-history.d.ts.map +1 -0
- package/dist/observability/adapters/audit-history.js +113 -0
- package/dist/observability/adapters/audit-history.js.map +1 -0
- package/dist/observability/adapters/audit-history.test.d.ts +2 -0
- package/dist/observability/adapters/audit-history.test.d.ts.map +1 -0
- package/dist/observability/adapters/audit-history.test.js +137 -0
- package/dist/observability/adapters/audit-history.test.js.map +1 -0
- package/dist/observability/adapters/beads.d.ts +9 -0
- package/dist/observability/adapters/beads.d.ts.map +1 -0
- package/dist/observability/adapters/beads.js +40 -0
- package/dist/observability/adapters/beads.js.map +1 -0
- package/dist/observability/adapters/beads.test.d.ts +2 -0
- package/dist/observability/adapters/beads.test.d.ts.map +1 -0
- package/dist/observability/adapters/beads.test.js +25 -0
- package/dist/observability/adapters/beads.test.js.map +1 -0
- package/dist/observability/adapters/gh.d.ts +27 -0
- package/dist/observability/adapters/gh.d.ts.map +1 -0
- package/dist/observability/adapters/gh.js +118 -0
- package/dist/observability/adapters/gh.js.map +1 -0
- package/dist/observability/adapters/gh.test.d.ts +2 -0
- package/dist/observability/adapters/gh.test.d.ts.map +1 -0
- package/dist/observability/adapters/gh.test.js +79 -0
- package/dist/observability/adapters/gh.test.js.map +1 -0
- package/dist/observability/adapters/git.d.ts +24 -0
- package/dist/observability/adapters/git.d.ts.map +1 -0
- package/dist/observability/adapters/git.js +110 -0
- package/dist/observability/adapters/git.js.map +1 -0
- package/dist/observability/adapters/git.test.d.ts +2 -0
- package/dist/observability/adapters/git.test.d.ts.map +1 -0
- package/dist/observability/adapters/git.test.js +66 -0
- package/dist/observability/adapters/git.test.js.map +1 -0
- package/dist/observability/adapters/mmr.d.ts +15 -0
- package/dist/observability/adapters/mmr.d.ts.map +1 -0
- package/dist/observability/adapters/mmr.js +85 -0
- package/dist/observability/adapters/mmr.js.map +1 -0
- package/dist/observability/adapters/mmr.test.d.ts +2 -0
- package/dist/observability/adapters/mmr.test.d.ts.map +1 -0
- package/dist/observability/adapters/mmr.test.js +55 -0
- package/dist/observability/adapters/mmr.test.js.map +1 -0
- package/dist/observability/adapters/pipeline-docs.d.ts +8 -0
- package/dist/observability/adapters/pipeline-docs.d.ts.map +1 -0
- package/dist/observability/adapters/pipeline-docs.js +68 -0
- package/dist/observability/adapters/pipeline-docs.js.map +1 -0
- package/dist/observability/adapters/pipeline-docs.test.d.ts +2 -0
- package/dist/observability/adapters/pipeline-docs.test.d.ts.map +1 -0
- package/dist/observability/adapters/pipeline-docs.test.js +58 -0
- package/dist/observability/adapters/pipeline-docs.test.js.map +1 -0
- package/dist/observability/adapters/state.d.ts +21 -0
- package/dist/observability/adapters/state.d.ts.map +1 -0
- package/dist/observability/adapters/state.js +87 -0
- package/dist/observability/adapters/state.js.map +1 -0
- package/dist/observability/adapters/state.test.d.ts +2 -0
- package/dist/observability/adapters/state.test.d.ts.map +1 -0
- package/dist/observability/adapters/state.test.js +92 -0
- package/dist/observability/adapters/state.test.js.map +1 -0
- package/dist/observability/adapters/tests.d.ts +20 -0
- package/dist/observability/adapters/tests.d.ts.map +1 -0
- package/dist/observability/adapters/tests.js +52 -0
- package/dist/observability/adapters/tests.js.map +1 -0
- package/dist/observability/adapters/tests.test.d.ts +2 -0
- package/dist/observability/adapters/tests.test.d.ts.map +1 -0
- package/dist/observability/adapters/tests.test.js +66 -0
- package/dist/observability/adapters/tests.test.js.map +1 -0
- package/dist/observability/adapters/types.d.ts +7 -0
- package/dist/observability/adapters/types.d.ts.map +1 -0
- package/dist/observability/adapters/types.js +2 -0
- package/dist/observability/adapters/types.js.map +1 -0
- package/dist/observability/checks/lens-a-tdd.d.ts +3 -0
- package/dist/observability/checks/lens-a-tdd.d.ts.map +1 -0
- package/dist/observability/checks/lens-a-tdd.js +34 -0
- package/dist/observability/checks/lens-a-tdd.js.map +1 -0
- package/dist/observability/checks/lens-a-tdd.test.d.ts +2 -0
- package/dist/observability/checks/lens-a-tdd.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-a-tdd.test.js +62 -0
- package/dist/observability/checks/lens-a-tdd.test.js.map +1 -0
- package/dist/observability/checks/lens-b-ac-coverage.d.ts +3 -0
- package/dist/observability/checks/lens-b-ac-coverage.d.ts.map +1 -0
- package/dist/observability/checks/lens-b-ac-coverage.js +63 -0
- package/dist/observability/checks/lens-b-ac-coverage.js.map +1 -0
- package/dist/observability/checks/lens-b-ac-coverage.test.d.ts +2 -0
- package/dist/observability/checks/lens-b-ac-coverage.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-b-ac-coverage.test.js +59 -0
- package/dist/observability/checks/lens-b-ac-coverage.test.js.map +1 -0
- package/dist/observability/checks/lens-c-standards.d.ts +3 -0
- package/dist/observability/checks/lens-c-standards.d.ts.map +1 -0
- package/dist/observability/checks/lens-c-standards.js +104 -0
- package/dist/observability/checks/lens-c-standards.js.map +1 -0
- package/dist/observability/checks/lens-c-standards.test.d.ts +2 -0
- package/dist/observability/checks/lens-c-standards.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-c-standards.test.js +79 -0
- package/dist/observability/checks/lens-c-standards.test.js.map +1 -0
- package/dist/observability/checks/lens-d-stack.d.ts +3 -0
- package/dist/observability/checks/lens-d-stack.d.ts.map +1 -0
- package/dist/observability/checks/lens-d-stack.js +108 -0
- package/dist/observability/checks/lens-d-stack.js.map +1 -0
- package/dist/observability/checks/lens-d-stack.test.d.ts +2 -0
- package/dist/observability/checks/lens-d-stack.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-d-stack.test.js +60 -0
- package/dist/observability/checks/lens-d-stack.test.js.map +1 -0
- package/dist/observability/checks/lens-e-design.d.ts +3 -0
- package/dist/observability/checks/lens-e-design.d.ts.map +1 -0
- package/dist/observability/checks/lens-e-design.js +76 -0
- package/dist/observability/checks/lens-e-design.js.map +1 -0
- package/dist/observability/checks/lens-e-design.test.d.ts +2 -0
- package/dist/observability/checks/lens-e-design.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-e-design.test.js +85 -0
- package/dist/observability/checks/lens-e-design.test.js.map +1 -0
- package/dist/observability/checks/lens-f-scope.d.ts +3 -0
- package/dist/observability/checks/lens-f-scope.d.ts.map +1 -0
- package/dist/observability/checks/lens-f-scope.js +92 -0
- package/dist/observability/checks/lens-f-scope.js.map +1 -0
- package/dist/observability/checks/lens-f-scope.test.d.ts +2 -0
- package/dist/observability/checks/lens-f-scope.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-f-scope.test.js +63 -0
- package/dist/observability/checks/lens-f-scope.test.js.map +1 -0
- package/dist/observability/checks/lens-g-decisions.d.ts +3 -0
- package/dist/observability/checks/lens-g-decisions.d.ts.map +1 -0
- package/dist/observability/checks/lens-g-decisions.js +139 -0
- package/dist/observability/checks/lens-g-decisions.js.map +1 -0
- package/dist/observability/checks/lens-g-decisions.test.d.ts +2 -0
- package/dist/observability/checks/lens-g-decisions.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-g-decisions.test.js +118 -0
- package/dist/observability/checks/lens-g-decisions.test.js.map +1 -0
- package/dist/observability/checks/lens-h-cross-doc.d.ts +3 -0
- package/dist/observability/checks/lens-h-cross-doc.d.ts.map +1 -0
- package/dist/observability/checks/lens-h-cross-doc.js +322 -0
- package/dist/observability/checks/lens-h-cross-doc.js.map +1 -0
- package/dist/observability/checks/lens-h-cross-doc.test.d.ts +2 -0
- package/dist/observability/checks/lens-h-cross-doc.test.d.ts.map +1 -0
- package/dist/observability/checks/lens-h-cross-doc.test.js +174 -0
- package/dist/observability/checks/lens-h-cross-doc.test.js.map +1 -0
- package/dist/observability/engine/abort-snapshot.d.ts +10 -0
- package/dist/observability/engine/abort-snapshot.d.ts.map +1 -0
- package/dist/observability/engine/abort-snapshot.js +36 -0
- package/dist/observability/engine/abort-snapshot.js.map +1 -0
- package/dist/observability/engine/abort-snapshot.test.d.ts +2 -0
- package/dist/observability/engine/abort-snapshot.test.d.ts.map +1 -0
- package/dist/observability/engine/abort-snapshot.test.js +66 -0
- package/dist/observability/engine/abort-snapshot.test.js.map +1 -0
- package/dist/observability/engine/api.d.ts +24 -0
- package/dist/observability/engine/api.d.ts.map +1 -0
- package/dist/observability/engine/api.js +203 -0
- package/dist/observability/engine/api.js.map +1 -0
- package/dist/observability/engine/api.test.d.ts +2 -0
- package/dist/observability/engine/api.test.d.ts.map +1 -0
- package/dist/observability/engine/api.test.js +174 -0
- package/dist/observability/engine/api.test.js.map +1 -0
- package/dist/observability/engine/checks/findings-aggregator.d.ts +6 -0
- package/dist/observability/engine/checks/findings-aggregator.d.ts.map +1 -0
- package/dist/observability/engine/checks/findings-aggregator.js +56 -0
- package/dist/observability/engine/checks/findings-aggregator.js.map +1 -0
- package/dist/observability/engine/checks/findings-aggregator.test.d.ts +2 -0
- package/dist/observability/engine/checks/findings-aggregator.test.d.ts.map +1 -0
- package/dist/observability/engine/checks/findings-aggregator.test.js +63 -0
- package/dist/observability/engine/checks/findings-aggregator.test.js.map +1 -0
- package/dist/observability/engine/checks/fix-threshold.d.ts +3 -0
- package/dist/observability/engine/checks/fix-threshold.d.ts.map +1 -0
- package/dist/observability/engine/checks/fix-threshold.js +24 -0
- package/dist/observability/engine/checks/fix-threshold.js.map +1 -0
- package/dist/observability/engine/checks/fix-threshold.test.d.ts +2 -0
- package/dist/observability/engine/checks/fix-threshold.test.d.ts.map +1 -0
- package/dist/observability/engine/checks/fix-threshold.test.js +29 -0
- package/dist/observability/engine/checks/fix-threshold.test.js.map +1 -0
- package/dist/observability/engine/checks/observability-config.d.ts +64 -0
- package/dist/observability/engine/checks/observability-config.d.ts.map +1 -0
- package/dist/observability/engine/checks/observability-config.js +56 -0
- package/dist/observability/engine/checks/observability-config.js.map +1 -0
- package/dist/observability/engine/checks/observability-config.test.d.ts +2 -0
- package/dist/observability/engine/checks/observability-config.test.d.ts.map +1 -0
- package/dist/observability/engine/checks/observability-config.test.js +39 -0
- package/dist/observability/engine/checks/observability-config.test.js.map +1 -0
- package/dist/observability/engine/checks/registry.d.ts +19 -0
- package/dist/observability/engine/checks/registry.d.ts.map +1 -0
- package/dist/observability/engine/checks/registry.js +44 -0
- package/dist/observability/engine/checks/registry.js.map +1 -0
- package/dist/observability/engine/checks/registry.test.d.ts +2 -0
- package/dist/observability/engine/checks/registry.test.d.ts.map +1 -0
- package/dist/observability/engine/checks/registry.test.js +23 -0
- package/dist/observability/engine/checks/registry.test.js.map +1 -0
- package/dist/observability/engine/checks/runner.d.ts +23 -0
- package/dist/observability/engine/checks/runner.d.ts.map +1 -0
- package/dist/observability/engine/checks/runner.js +66 -0
- package/dist/observability/engine/checks/runner.js.map +1 -0
- package/dist/observability/engine/checks/runner.test.d.ts +2 -0
- package/dist/observability/engine/checks/runner.test.d.ts.map +1 -0
- package/dist/observability/engine/checks/runner.test.js +95 -0
- package/dist/observability/engine/checks/runner.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/component-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/component-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/component-parser.js +42 -0
- package/dist/observability/engine/doc-graph/component-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/component-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/component-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/component-parser.test.js +40 -0
- package/dist/observability/engine/doc-graph/component-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/component-use-detector.d.ts +8 -0
- package/dist/observability/engine/doc-graph/component-use-detector.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/component-use-detector.js +62 -0
- package/dist/observability/engine/doc-graph/component-use-detector.js.map +1 -0
- package/dist/observability/engine/doc-graph/component-use-detector.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/component-use-detector.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/component-use-detector.test.js +38 -0
- package/dist/observability/engine/doc-graph/component-use-detector.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/decision-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/decision-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/decision-parser.js +60 -0
- package/dist/observability/engine/doc-graph/decision-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/decision-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/decision-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/decision-parser.test.js +65 -0
- package/dist/observability/engine/doc-graph/decision-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/design-props.d.ts +9 -0
- package/dist/observability/engine/doc-graph/design-props.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/design-props.js +50 -0
- package/dist/observability/engine/doc-graph/design-props.js.map +1 -0
- package/dist/observability/engine/doc-graph/edge-builder.d.ts +28 -0
- package/dist/observability/engine/doc-graph/edge-builder.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/edge-builder.js +75 -0
- package/dist/observability/engine/doc-graph/edge-builder.js.map +1 -0
- package/dist/observability/engine/doc-graph/edge-builder.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/edge-builder.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/edge-builder.test.js +124 -0
- package/dist/observability/engine/doc-graph/edge-builder.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/feature-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/feature-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/feature-parser.js +78 -0
- package/dist/observability/engine/doc-graph/feature-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/feature-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/feature-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/feature-parser.test.js +51 -0
- package/dist/observability/engine/doc-graph/feature-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/index.d.ts +3 -0
- package/dist/observability/engine/doc-graph/index.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/index.js +138 -0
- package/dist/observability/engine/doc-graph/index.js.map +1 -0
- package/dist/observability/engine/doc-graph/index.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/index.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/index.test.js +82 -0
- package/dist/observability/engine/doc-graph/index.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/parse-markdown.d.ts +15 -0
- package/dist/observability/engine/doc-graph/parse-markdown.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/parse-markdown.js +79 -0
- package/dist/observability/engine/doc-graph/parse-markdown.js.map +1 -0
- package/dist/observability/engine/doc-graph/parse-markdown.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/parse-markdown.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/parse-markdown.test.js +50 -0
- package/dist/observability/engine/doc-graph/parse-markdown.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.js +38 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.test.js +38 -0
- package/dist/observability/engine/doc-graph/plan-task-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.js +40 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.test.js +31 -0
- package/dist/observability/engine/doc-graph/playbook-task-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/rule-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/rule-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/rule-parser.js +65 -0
- package/dist/observability/engine/doc-graph/rule-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/rule-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/rule-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/rule-parser.test.js +44 -0
- package/dist/observability/engine/doc-graph/rule-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/story-parser.d.ts +8 -0
- package/dist/observability/engine/doc-graph/story-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/story-parser.js +109 -0
- package/dist/observability/engine/doc-graph/story-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/story-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/story-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/story-parser.test.js +66 -0
- package/dist/observability/engine/doc-graph/story-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/test-discovery.d.ts +3 -0
- package/dist/observability/engine/doc-graph/test-discovery.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/test-discovery.js +122 -0
- package/dist/observability/engine/doc-graph/test-discovery.js.map +1 -0
- package/dist/observability/engine/doc-graph/test-discovery.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/test-discovery.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/test-discovery.test.js +39 -0
- package/dist/observability/engine/doc-graph/test-discovery.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/token-parser.d.ts +3 -0
- package/dist/observability/engine/doc-graph/token-parser.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/token-parser.js +67 -0
- package/dist/observability/engine/doc-graph/token-parser.js.map +1 -0
- package/dist/observability/engine/doc-graph/token-parser.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/token-parser.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/token-parser.test.js +39 -0
- package/dist/observability/engine/doc-graph/token-parser.test.js.map +1 -0
- package/dist/observability/engine/doc-graph/token-use-detector.d.ts +10 -0
- package/dist/observability/engine/doc-graph/token-use-detector.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/token-use-detector.js +105 -0
- package/dist/observability/engine/doc-graph/token-use-detector.js.map +1 -0
- package/dist/observability/engine/doc-graph/token-use-detector.test.d.ts +2 -0
- package/dist/observability/engine/doc-graph/token-use-detector.test.d.ts.map +1 -0
- package/dist/observability/engine/doc-graph/token-use-detector.test.js +60 -0
- package/dist/observability/engine/doc-graph/token-use-detector.test.js.map +1 -0
- package/dist/observability/engine/event-schemas.d.ts +12 -0
- package/dist/observability/engine/event-schemas.d.ts.map +1 -0
- package/dist/observability/engine/event-schemas.js +182 -0
- package/dist/observability/engine/event-schemas.js.map +1 -0
- package/dist/observability/engine/event-schemas.test.d.ts +2 -0
- package/dist/observability/engine/event-schemas.test.d.ts.map +1 -0
- package/dist/observability/engine/event-schemas.test.js +200 -0
- package/dist/observability/engine/event-schemas.test.js.map +1 -0
- package/dist/observability/engine/fix-agent-dispatcher.d.ts +19 -0
- package/dist/observability/engine/fix-agent-dispatcher.d.ts.map +1 -0
- package/dist/observability/engine/fix-agent-dispatcher.js +88 -0
- package/dist/observability/engine/fix-agent-dispatcher.js.map +1 -0
- package/dist/observability/engine/fix-agent-dispatcher.test.d.ts +2 -0
- package/dist/observability/engine/fix-agent-dispatcher.test.d.ts.map +1 -0
- package/dist/observability/engine/fix-agent-dispatcher.test.js +54 -0
- package/dist/observability/engine/fix-agent-dispatcher.test.js.map +1 -0
- package/dist/observability/engine/fix-flow.d.ts +30 -0
- package/dist/observability/engine/fix-flow.d.ts.map +1 -0
- package/dist/observability/engine/fix-flow.js +105 -0
- package/dist/observability/engine/fix-flow.js.map +1 -0
- package/dist/observability/engine/fix-flow.test.d.ts +2 -0
- package/dist/observability/engine/fix-flow.test.d.ts.map +1 -0
- package/dist/observability/engine/fix-flow.test.js +127 -0
- package/dist/observability/engine/fix-flow.test.js.map +1 -0
- package/dist/observability/engine/fix-plan.d.ts +3 -0
- package/dist/observability/engine/fix-plan.d.ts.map +1 -0
- package/dist/observability/engine/fix-plan.js +13 -0
- package/dist/observability/engine/fix-plan.js.map +1 -0
- package/dist/observability/engine/fix-plan.test.d.ts +2 -0
- package/dist/observability/engine/fix-plan.test.d.ts.map +1 -0
- package/dist/observability/engine/fix-plan.test.js +44 -0
- package/dist/observability/engine/fix-plan.test.js.map +1 -0
- package/dist/observability/engine/harvester.d.ts +16 -0
- package/dist/observability/engine/harvester.d.ts.map +1 -0
- package/dist/observability/engine/harvester.js +106 -0
- package/dist/observability/engine/harvester.js.map +1 -0
- package/dist/observability/engine/harvester.test.d.ts +2 -0
- package/dist/observability/engine/harvester.test.d.ts.map +1 -0
- package/dist/observability/engine/harvester.test.js +99 -0
- package/dist/observability/engine/harvester.test.js.map +1 -0
- package/dist/observability/engine/identity.d.ts +6 -0
- package/dist/observability/engine/identity.d.ts.map +1 -0
- package/dist/observability/engine/identity.js +50 -0
- package/dist/observability/engine/identity.js.map +1 -0
- package/dist/observability/engine/identity.test.d.ts +2 -0
- package/dist/observability/engine/identity.test.d.ts.map +1 -0
- package/dist/observability/engine/identity.test.js +29 -0
- package/dist/observability/engine/identity.test.js.map +1 -0
- package/dist/observability/engine/ledger-writer.d.ts +10 -0
- package/dist/observability/engine/ledger-writer.d.ts.map +1 -0
- package/dist/observability/engine/ledger-writer.js +50 -0
- package/dist/observability/engine/ledger-writer.js.map +1 -0
- package/dist/observability/engine/ledger-writer.test.d.ts +2 -0
- package/dist/observability/engine/ledger-writer.test.d.ts.map +1 -0
- package/dist/observability/engine/ledger-writer.test.js +72 -0
- package/dist/observability/engine/ledger-writer.test.js.map +1 -0
- package/dist/observability/engine/llm-dispatcher.d.ts +16 -0
- package/dist/observability/engine/llm-dispatcher.d.ts.map +1 -0
- package/dist/observability/engine/llm-dispatcher.js +183 -0
- package/dist/observability/engine/llm-dispatcher.js.map +1 -0
- package/dist/observability/engine/llm-dispatcher.test.d.ts +2 -0
- package/dist/observability/engine/llm-dispatcher.test.d.ts.map +1 -0
- package/dist/observability/engine/llm-dispatcher.test.js +109 -0
- package/dist/observability/engine/llm-dispatcher.test.js.map +1 -0
- package/dist/observability/engine/phase-audit.d.ts +22 -0
- package/dist/observability/engine/phase-audit.d.ts.map +1 -0
- package/dist/observability/engine/phase-audit.js +98 -0
- package/dist/observability/engine/phase-audit.js.map +1 -0
- package/dist/observability/engine/phase-audit.test.d.ts +2 -0
- package/dist/observability/engine/phase-audit.test.d.ts.map +1 -0
- package/dist/observability/engine/phase-audit.test.js +82 -0
- package/dist/observability/engine/phase-audit.test.js.map +1 -0
- package/dist/observability/engine/phase-subsets.d.ts +5 -0
- package/dist/observability/engine/phase-subsets.d.ts.map +1 -0
- package/dist/observability/engine/phase-subsets.js +23 -0
- package/dist/observability/engine/phase-subsets.js.map +1 -0
- package/dist/observability/engine/phase-subsets.test.d.ts +2 -0
- package/dist/observability/engine/phase-subsets.test.d.ts.map +1 -0
- package/dist/observability/engine/phase-subsets.test.js +24 -0
- package/dist/observability/engine/phase-subsets.test.js.map +1 -0
- package/dist/observability/engine/redact.d.ts +9 -0
- package/dist/observability/engine/redact.d.ts.map +1 -0
- package/dist/observability/engine/redact.js +134 -0
- package/dist/observability/engine/redact.js.map +1 -0
- package/dist/observability/engine/redact.test.d.ts +2 -0
- package/dist/observability/engine/redact.test.d.ts.map +1 -0
- package/dist/observability/engine/redact.test.js +244 -0
- package/dist/observability/engine/redact.test.js.map +1 -0
- package/dist/observability/engine/stall.d.ts +13 -0
- package/dist/observability/engine/stall.d.ts.map +1 -0
- package/dist/observability/engine/stall.js +167 -0
- package/dist/observability/engine/stall.js.map +1 -0
- package/dist/observability/engine/stall.test.d.ts +2 -0
- package/dist/observability/engine/stall.test.d.ts.map +1 -0
- package/dist/observability/engine/stall.test.js +148 -0
- package/dist/observability/engine/stall.test.js.map +1 -0
- package/dist/observability/engine/synthesizer.d.ts +35 -0
- package/dist/observability/engine/synthesizer.d.ts.map +1 -0
- package/dist/observability/engine/synthesizer.js +298 -0
- package/dist/observability/engine/synthesizer.js.map +1 -0
- package/dist/observability/engine/synthesizer.test.d.ts +2 -0
- package/dist/observability/engine/synthesizer.test.d.ts.map +1 -0
- package/dist/observability/engine/synthesizer.test.js +183 -0
- package/dist/observability/engine/synthesizer.test.js.map +1 -0
- package/dist/observability/engine/types.d.ts +422 -0
- package/dist/observability/engine/types.d.ts.map +1 -0
- package/dist/observability/engine/types.js +3 -0
- package/dist/observability/engine/types.js.map +1 -0
- package/dist/observability/engine/types.test.d.ts +2 -0
- package/dist/observability/engine/types.test.d.ts.map +1 -0
- package/dist/observability/engine/types.test.js +16 -0
- package/dist/observability/engine/types.test.js.map +1 -0
- package/dist/observability/renderers/_lib.d.ts +7 -0
- package/dist/observability/renderers/_lib.d.ts.map +1 -0
- package/dist/observability/renderers/_lib.js +28 -0
- package/dist/observability/renderers/_lib.js.map +1 -0
- package/dist/observability/renderers/dashboard.d.ts +5 -0
- package/dist/observability/renderers/dashboard.d.ts.map +1 -0
- package/dist/observability/renderers/dashboard.js +100 -0
- package/dist/observability/renderers/dashboard.js.map +1 -0
- package/dist/observability/renderers/dashboard.test.d.ts +2 -0
- package/dist/observability/renderers/dashboard.test.d.ts.map +1 -0
- package/dist/observability/renderers/dashboard.test.js +142 -0
- package/dist/observability/renderers/dashboard.test.js.map +1 -0
- package/dist/observability/renderers/markdown.d.ts +4 -0
- package/dist/observability/renderers/markdown.d.ts.map +1 -0
- package/dist/observability/renderers/markdown.js +225 -0
- package/dist/observability/renderers/markdown.js.map +1 -0
- package/dist/observability/renderers/markdown.test.d.ts +2 -0
- package/dist/observability/renderers/markdown.test.d.ts.map +1 -0
- package/dist/observability/renderers/markdown.test.js +169 -0
- package/dist/observability/renderers/markdown.test.js.map +1 -0
- package/dist/observability/renderers/mmr-findings.d.ts +10 -0
- package/dist/observability/renderers/mmr-findings.d.ts.map +1 -0
- package/dist/observability/renderers/mmr-findings.js +21 -0
- package/dist/observability/renderers/mmr-findings.js.map +1 -0
- package/dist/observability/renderers/mmr-findings.test.d.ts +2 -0
- package/dist/observability/renderers/mmr-findings.test.d.ts.map +1 -0
- package/dist/observability/renderers/mmr-findings.test.js +86 -0
- package/dist/observability/renderers/mmr-findings.test.js.map +1 -0
- package/dist/observability/renderers/sidecar.d.ts +5 -0
- package/dist/observability/renderers/sidecar.d.ts.map +1 -0
- package/dist/observability/renderers/sidecar.js +51 -0
- package/dist/observability/renderers/sidecar.js.map +1 -0
- package/dist/observability/renderers/sidecar.test.d.ts +2 -0
- package/dist/observability/renderers/sidecar.test.d.ts.map +1 -0
- package/dist/observability/renderers/sidecar.test.js +77 -0
- package/dist/observability/renderers/sidecar.test.js.map +1 -0
- package/dist/observability/renderers/terminal.d.ts +7 -0
- package/dist/observability/renderers/terminal.d.ts.map +1 -0
- package/dist/observability/renderers/terminal.js +96 -0
- package/dist/observability/renderers/terminal.js.map +1 -0
- package/dist/observability/renderers/terminal.test.d.ts +2 -0
- package/dist/observability/renderers/terminal.test.d.ts.map +1 -0
- package/dist/observability/renderers/terminal.test.js +163 -0
- package/dist/observability/renderers/terminal.test.js.map +1 -0
- package/dist/project/adopt.d.ts.map +1 -1
- package/dist/project/adopt.js +3 -1
- package/dist/project/adopt.js.map +1 -1
- package/dist/project/detectors/coverage.test.js +3 -2
- package/dist/project/detectors/coverage.test.js.map +1 -1
- package/dist/project/detectors/disambiguate.js +1 -1
- package/dist/project/detectors/disambiguate.js.map +1 -1
- package/dist/project/detectors/index.d.ts.map +1 -1
- package/dist/project/detectors/index.js +2 -0
- package/dist/project/detectors/index.js.map +1 -1
- package/dist/project/detectors/resolve-detection.test.js +57 -0
- package/dist/project/detectors/resolve-detection.test.js.map +1 -1
- package/dist/project/detectors/types.d.ts +6 -2
- package/dist/project/detectors/types.d.ts.map +1 -1
- package/dist/project/detectors/types.js.map +1 -1
- package/dist/project/detectors/web3.d.ts +4 -0
- package/dist/project/detectors/web3.d.ts.map +1 -0
- package/dist/project/detectors/web3.js +37 -0
- package/dist/project/detectors/web3.js.map +1 -0
- package/dist/project/detectors/web3.test.d.ts +2 -0
- package/dist/project/detectors/web3.test.d.ts.map +1 -0
- package/dist/project/detectors/web3.test.js +75 -0
- package/dist/project/detectors/web3.test.js.map +1 -0
- package/dist/state/state-manager.d.ts +7 -2
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +31 -2
- package/dist/state/state-manager.js.map +1 -1
- package/dist/state/state-manager.test.js +88 -3
- package/dist/state/state-manager.test.js.map +1 -1
- package/dist/types/config.d.ts +8 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/state.d.ts +2 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/wizard/copy/core.d.ts.map +1 -1
- package/dist/wizard/copy/core.js +4 -0
- package/dist/wizard/copy/core.js.map +1 -1
- package/dist/wizard/copy/index.d.ts.map +1 -1
- package/dist/wizard/copy/index.js +2 -0
- package/dist/wizard/copy/index.js.map +1 -1
- package/dist/wizard/copy/types.d.ts +5 -1
- package/dist/wizard/copy/types.d.ts.map +1 -1
- package/dist/wizard/copy/types.test-d.js +7 -0
- package/dist/wizard/copy/types.test-d.js.map +1 -1
- package/dist/wizard/copy/web3.d.ts +3 -0
- package/dist/wizard/copy/web3.d.ts.map +1 -0
- package/dist/wizard/copy/web3.js +15 -0
- package/dist/wizard/copy/web3.js.map +1 -0
- package/dist/wizard/questions.d.ts +2 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +8 -1
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +14 -0
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +1 -0
- package/dist/wizard/wizard.js.map +1 -1
- package/package.json +16 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web3-audit-workflow
|
|
3
|
+
description: Pre-audit readiness, tooling (Slither, Echidna, Halmos, Certora), CI integration, firm selection, timing, remediation, and post-launch bug bounties for protocol teams preparing for a smart-contract audit
|
|
4
|
+
topics: [web3, audit, slither, echidna, halmos, security]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Audits are expensive ($30k–$500k+) and time-consuming, and the meter starts the day the auditors open your repo — not the day they find their first bug. A team that hands over a half-finished spec, 40% test coverage, and a Slither report full of unaddressed mediums is paying senior security engineers to do the work the team should have done before kickoff. The protocols that get the most out of an audit treat the engagement as a final review against work already proven correct, not as a substitute for it. Maximize value by being ready before the audit starts.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Walk into the audit with a written spec, >90% line and >80% branch coverage, a green Slither CI gate, a committed threat model, and a testnet-deployed contract — anything less and you are paying $5k/day for engineers to fill in your gaps. Wire `Slither` into every PR as a blocking check, layer `Echidna` and `Halmos` over invariant tests for the critical paths, and reserve `Certora` for high-TVL functions where formal verification pays for itself. Pick the firm to match the stakes: top-tier (Trail of Bits, OpenZeppelin, Consensys Diligence, Spearbit, Cantina) for serious TVL, competitive platforms (Code4rena, Sherlock, Cantina) for cost-sensitive scope. Book 2–4 months ahead, freeze code 1–2 weeks before kickoff, fix every High and Medium with a test plus an auditor re-verification, then run an `Immunefi` bounty continuously after launch.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Pre-audit readiness checklist
|
|
16
|
+
|
|
17
|
+
Work this list in order. Each item is a precondition for the next; skipping one means the auditors spend their hours on what you should have shipped. A useful mental test: pretend you are paying $5,000/day out of your own pocket for each engineer on the audit. Would you rather they spend day one orienting on your README, or finding bugs? Every gap in this checklist trades day-zero engineering hours of your own for day-one auditor hours that cost ten times as much.
|
|
18
|
+
|
|
19
|
+
1. **Specification document with invariants explicitly listed.** "Total supply equals sum of balances," "no user withdraws more than they deposited," "only governance can change fee tiers," "the protocol is solvent at every block." Auditors cannot tell you whether your code is correct against an undefined target — without invariants they are reduced to pattern-matching for known vulnerabilities, which is the cheapest, lowest-value mode of audit work. The spec also becomes the artifact the audit report references, so a thin spec produces a thin report. Cross-ref `web3-requirements.md` for the spec template and invariant style.
|
|
20
|
+
2. **>90% line coverage, >80% branch coverage.** Generated via `forge coverage --report lcov` and enforced in CI. Anything less means auditors are reviewing code paths your tests have never executed, and they will flag those paths as untested rather than as subtly broken — wasting findings on hygiene. Aim higher on the contracts that touch funds (95%+) and accept lower on view-only helpers if the budget is tight. Cross-ref `web3-testing.md` for the fuzz and invariant patterns that make coverage meaningful rather than ceremonial.
|
|
21
|
+
3. **`forge test --gas-report` baseline captured.** Commit the report as `gas-snapshot.txt` at the repo root. Auditors flag gas regressions and griefing vectors (loops that scale with user count, unbounded storage growth); without a baseline you cannot tell whether a remediation fix made gas worse, and reviewers cannot tell whether a hot path you claim is "cheap" actually is.
|
|
22
|
+
4. **Slither CI gate green.** Zero high/medium findings, or each one annotated with a justification comment that survives review. Auditors who open the repo and see a wall of unaddressed Slither warnings will assume the rest of the codebase is held to the same standard, and price the engagement accordingly. See the next section for the config.
|
|
23
|
+
5. **Threat model document committed.** Who are the attackers (MEV searchers, governance attackers, malicious LPs, compromised oracles), what are their capabilities (flashloans, large stake, validator control, social engineering), what assets are at risk (TVL, governance tokens, NFT ownership), and what assumptions does the protocol make about external contracts and oracles. A short doc — one page is fine — that frames the audit conversation and makes implicit assumptions explicit.
|
|
24
|
+
6. **Deployment scripts tested on a public testnet.** Sepolia, Base Sepolia, Arbitrum Sepolia — whichever matches your target. Auditors should be able to interact with a live deployment, not just read source. A working testnet deployment also flushes out the deploy-script bugs (mis-ordered constructor args, wrong proxy admin, forgotten role grants) that are easy to miss in unit tests but catastrophic on mainnet.
|
|
25
|
+
7. **README with architecture, contracts list, deployment addresses.** A one-paragraph mental model per contract, a diagram of how they call each other, the testnet addresses, and a "how to run the tests" section that works from a clean checkout. Saves the auditors a full day of orientation that you are otherwise paying for.
|
|
26
|
+
|
|
27
|
+
### Mandatory tooling: Slither
|
|
28
|
+
|
|
29
|
+
`Slither` is the static-analysis baseline for Solidity. It is free, fast, catches a wide class of footguns (reentrancy, uninitialized storage, shadowed variables, incorrect ERC-20 returns), and runs in under a minute on most repos. Install once, run on every PR, fail the build on regressions.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install slither-analyzer
|
|
33
|
+
slither . --config-file .slither.config.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Minimal `.slither.config.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"filter_paths": "lib/|test/|script/",
|
|
41
|
+
"exclude_informational": true,
|
|
42
|
+
"exclude_low": false,
|
|
43
|
+
"fail_high": true,
|
|
44
|
+
"fail_medium": true
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The CI rule is simple: zero high and zero medium findings unless explicitly justified with a `// slither-disable-next-line <detector>` comment naming the detector and the reason. Every disable is a claim the team is making in writing; auditors will read them and push back on weak ones.
|
|
49
|
+
|
|
50
|
+
Slither also has a `slither-mutate` mode for mutation testing — useful but slow, run it weekly rather than per-PR. The `--print human-summary` flag produces a one-screen overview that is excellent to commit as `slither-summary.txt` for auditors to skim during orientation. Complement Slither with `Aderyn` (Cyfrin's Rust-based static analyzer) for a second-opinion pass; the detectors overlap but each catches a small set the other misses.
|
|
51
|
+
|
|
52
|
+
### Recommended tooling: Echidna, Halmos, Certora
|
|
53
|
+
|
|
54
|
+
Static analysis catches patterns; dynamic and symbolic tools catch logic. Each tool answers a different question, and they stack rather than substitute.
|
|
55
|
+
|
|
56
|
+
- **`Echidna`** — property-based fuzzing from Trail of Bits. Extends Foundry invariant tests with smarter input generation: a maintained corpus that survives between runs, shrinking of failing sequences to minimal reproductions, and coverage-guided exploration that biases toward unvisited branches. Reach for it when your Foundry invariant tests pass too quickly to trust — Echidna will find the input you didn't think to write, and produces a replayable test case when it does. The cost is wiring up a config and learning to write properties in its slightly different style; budget half a day per contract.
|
|
57
|
+
- **`Halmos`** — open-source formal verification via symbolic execution from a16z. Where Echidna explores by sampling, Halmos explores by symbolically encoding all input values within a bound and asking an SMT solver whether the property can be violated. Great for path-explosion analysis on small, critical functions: pricing math, share calculations, access-control checks, ERC-4626 share/asset round-trips. Free, fast on bounded loops (it struggles with unbounded ones), and crucially it runs against the same `forge test` signatures you already have — write a `check_*` test that asserts the property, point Halmos at it, and get a proof or a counterexample.
|
|
58
|
+
- **`Certora`** — commercial formal verification. The gold standard, used by Aave, Compound, MakerDAO, and Lido. Demands a separate specification language (CVL — Certora Verification Language) and an engagement model where Certora engineers help write the spec, but the result is a machine-checked proof against arbitrary state evolution rather than a sample of cases. Expensive — six figures for a real engagement — and slow to start, so reserve it for the most consequential invariants on a high-TVL protocol where the cost of being wrong dwarfs the cost of being thorough.
|
|
59
|
+
|
|
60
|
+
The reach order is cumulative, not exclusive: invariant tests on every state-changing function first, Echidna on the critical contracts that handle funds, Halmos on the highest-stakes pure functions (math, accounting), Certora only if TVL or systemic importance justifies the budget. A protocol that ships with all four is rare and signals seriousness to the ecosystem.
|
|
61
|
+
|
|
62
|
+
### CI integration
|
|
63
|
+
|
|
64
|
+
Run static analysis, tests, and coverage on every PR — not nightly, not weekly, every PR. Quality gates that run after merge catch bugs after they have already been reviewed by humans operating under the assumption that the suite is green. A representative GitHub Actions snippet:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
name: audit-readiness
|
|
68
|
+
on: [pull_request]
|
|
69
|
+
jobs:
|
|
70
|
+
check:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
with: { submodules: recursive }
|
|
75
|
+
- uses: foundry-rs/foundry-toolchain@v1
|
|
76
|
+
- uses: actions/setup-python@v5
|
|
77
|
+
with: { python-version: "3.11" }
|
|
78
|
+
- run: pip install slither-analyzer
|
|
79
|
+
- run: forge build --sizes
|
|
80
|
+
- run: forge test -vvv
|
|
81
|
+
- run: forge coverage --report lcov
|
|
82
|
+
- run: slither . --config-file .slither.config.json
|
|
83
|
+
- name: enforce coverage floor
|
|
84
|
+
run: |
|
|
85
|
+
line_pct=$(grep -m1 'lines\.\.\.\.\.\.' lcov.info.summary | awk '{print $2}' | tr -d '%')
|
|
86
|
+
awk "BEGIN { exit !($line_pct >= 90) }" || { echo "coverage below 90%"; exit 1; }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Add a coverage threshold check (e.g., `lcov-summary` or the inline script above) so the build fails when line coverage drops below 90%. Coverage that only goes up is the only coverage gate that keeps a test suite honest — a soft target lets a Friday-afternoon PR shave a percent and then never recover. Wire the Slither output to a PR comment so reviewers see new findings inline rather than digging through CI logs; both `crytic/slither-action` and a custom step that posts via `gh pr comment` work fine.
|
|
90
|
+
|
|
91
|
+
Long-running checks (Echidna campaigns, Halmos runs over the full critical surface) belong on a separate nightly or pre-release workflow, not the PR gate — a 30-minute fuzz run on every push burns CI minutes and trains the team to ignore failures. Keep the PR gate fast (<5 minutes) and reserve the slower campaigns for tagged releases and the final pre-audit freeze.
|
|
92
|
+
|
|
93
|
+
### What auditors actually do during the engagement
|
|
94
|
+
|
|
95
|
+
Knowing the workflow on the other side helps you prepare what they need. A typical engagement runs in three phases:
|
|
96
|
+
|
|
97
|
+
1. **Orientation (days 1–3).** Reading the spec, the README, and the source. Building a mental model, drawing call graphs, identifying trust boundaries. This phase is entirely paid for by your documentation quality — a great spec compresses this to a day, a missing one stretches it to a week.
|
|
98
|
+
2. **Active review (days 4 through N−2).** Running tools (Slither, Aderyn, custom semgrep rules, sometimes Echidna or Halmos against the firm's internal property libraries), reading code adversarially against the spec, writing PoC exploits for suspected bugs. This is where bugs are found. Your responsiveness here directly translates to findings.
|
|
99
|
+
3. **Reporting (final 2 days).** Writing up findings with severity, impact, recommendation, and PoC. Each finding takes hours to write well; a firm that finds 20 bugs in week one and spends week two writing them up is doing exactly the right thing, even if it looks like they "stopped finding bugs."
|
|
100
|
+
|
|
101
|
+
If your audit appears to slow down halfway through, that is usually the firm transitioning from phase 2 to phase 3, not running out of material.
|
|
102
|
+
|
|
103
|
+
A few interaction norms that auditors appreciate and many teams skip: keep the Slack/Discord channel open for the full engagement, not just business hours; do not push code to the audited branch (use a `post-audit-fixes` branch instead); confirm receipt of each finding within a day so the firm knows the report landed; and resist the urge to argue severity in writing — every back-and-forth on "this should be Medium not High" burns hours that could go to finding the next bug. Severity disputes are a small final-call meeting at the end, not a running thread.
|
|
104
|
+
|
|
105
|
+
### Choosing an audit firm
|
|
106
|
+
|
|
107
|
+
Match the firm to the stakes. The wrong firm for the wrong protocol wastes money in both directions — overpaying a top-tier firm to review a 200-line vault, or sending a complex cross-chain protocol to a competitive audit that lacks the senior architectural review it needs.
|
|
108
|
+
|
|
109
|
+
- **High-TVL or novel protocols** — top-tier private engagements: `Trail of Bits`, `OpenZeppelin`, `Consensys Diligence`, `Spearbit`, `Cantina`. Expect $50k–$500k+ for two to six weeks with two or three senior engineers. These firms produce public reports the ecosystem trusts, which is itself a marketing asset on launch day. Each has a flavor: Trail of Bits is rigorous and broad, OpenZeppelin is deep on standards and patterns they helped define, Consensys Diligence is strong on EVM internals, Spearbit and Cantina pool elite independent researchers under a coordinator.
|
|
110
|
+
- **Lower TVL or budget-constrained** — competitive audits: `Code4rena`, `Sherlock`, `Cantina` contests. A wide pool of reviewers competes for a fixed prize pool. Cheaper, faster turnaround, more findings by volume, but less narrative analysis of architectural risk — you get a list of bugs, not a coherent picture of how the system might fail. Pair with a short private review if the protocol is non-trivial. Sherlock additionally backs findings with a payout guarantee that functions like insurance.
|
|
111
|
+
- **Pre-audit pass** — a one-week private review from a solo security researcher (many ex-firm engineers freelance on Cantina or Twitter) is a cheap way to shake out the obvious bugs before the main audit, so the firm spends its time on the subtle ones rather than the embarrassing ones.
|
|
112
|
+
|
|
113
|
+
Never rely on a single audit for a complex protocol. Two firms in series, or one firm plus a competitive audit, finds materially more bugs than either alone — the second reviewer sees what the first missed precisely because they came in fresh, and the cost is roughly additive while the bug-discovery rate is more than additive. For high-TVL launches, three layers (private firm → competitive audit → ongoing bounty) is the modern standard.
|
|
114
|
+
|
|
115
|
+
### Writing the audit RFP
|
|
116
|
+
|
|
117
|
+
When reaching out to a firm or platform, send a single document with: protocol summary in two paragraphs, line count in scope (split by `cloc src/`), spec + threat model attached, current test coverage numbers, the SHA you intend to freeze on, the launch date you are targeting, and your budget range. Firms reply faster and more concretely to specific asks than to "we'd like to audit our protocol, please send pricing." Naming the specific contracts in scope (not "all contracts") and explicitly excluding test helpers and deploy scripts from the line count keeps the quote tight and honest. If you need an NDA, send it with the first email — the back-and-forth otherwise burns a week.
|
|
118
|
+
|
|
119
|
+
### Audit timing and process
|
|
120
|
+
|
|
121
|
+
Book 2–4 months ahead — the firms worth hiring are booked out, and the last-minute slots that do appear come from teams whose contracts slipped, which is not a market you want to be buying into. When you reach out, send the spec, the threat model, the test coverage numbers, and a one-page protocol summary; firms triage proposals by readiness, and a well-prepared inquiry gets a better slot than a vague one even with similar timing.
|
|
122
|
+
|
|
123
|
+
Code freeze 1–2 weeks before the audit start date: no new features, only documentation, additional tests, and bug fixes against issues you find internally during the freeze. Tag the commit (`v1.0-audit-start`) and share that exact SHA with the firm in writing. Do not change scope mid-audit — every change invalidates findings the auditors already wrote against the prior version and forces them to redo work you are paying for at $5k/day. If a serious bug is discovered internally during the audit, fix it on a side branch and hand the patch to the auditors as a "fix to verify" rather than rebasing main.
|
|
124
|
+
|
|
125
|
+
During the audit, be responsive. Auditors will ask design questions ("is this rounding direction intentional?"), request access to off-chain components (subgraphs, keeper scripts), and float hypotheses they want confirmed before writing them up. A 24-hour response time is fine; a four-day silence wastes a full senior-engineer-day of audit budget per question. Designate one team member as the audit liaison so questions land in one place and answers come from one source — fragmented responses from three engineers produce contradictory context the auditors then have to reconcile.
|
|
126
|
+
|
|
127
|
+
### Post-audit remediation
|
|
128
|
+
|
|
129
|
+
Every High and Medium finding gets three things: a fix, a test that would have caught the bug (regression coverage that lives in the repo forever), and a re-verification pass from the auditor against the actual fix commit. Lows and informationals get triaged — fix the cheap ones, document the rest with a written rationale in a remediation appendix. Do not close a finding because the fix "looks right" or "the test passes" — the auditor's re-verification is the only signal that matters, because most bad fixes pass the obvious test and fail a subtler condition the auditor noticed but did not write up.
|
|
130
|
+
|
|
131
|
+
Publish the report after fixes land. Do not hide findings — the community can spot a hidden bug from on-chain behavior, and a transparent disclosure with a remediation appendix is a credibility asset that pays back over the protocol's lifetime. Include the commit hash that addressed each finding so anyone can verify the fix independently. Hiding a Critical finding is the worst-case mode: it discredits the team, invalidates the audit's value as a signal, and historically precedes the exploit by weeks.
|
|
132
|
+
|
|
133
|
+
### Bug bounty post-launch
|
|
134
|
+
|
|
135
|
+
Audits are a snapshot; bug bounties are continuous. List on `Immunefi` (or Cantina, or both) for protocol-grade payouts with a tiered, TVL-scaled payout table — typical curves are $50k for critical low-TVL bugs, $250k–$500k for mid-TVL, $1M–$2M+ for critical high-TVL findings. A $10k bounty on a $100M protocol is an insult to whitehats and an invitation to blackhats: a sophisticated researcher who finds a critical bug will rationally sell it on the open market unless your bounty pays meaningfully more than the bug is worth exploited.
|
|
136
|
+
|
|
137
|
+
Run the bounty continuously, not for a launch week and then off — most exploitable bugs are found weeks or months after deployment, by researchers who only look at protocols with live, well-funded, fast-paying programs. Pay quickly when reports land (within days, not weeks), grade reports honestly against the published tier table, and publish post-mortems for valid findings so the next researcher trusts you. A program that ghosts researchers or downgrades findings to underpay loses access to the whitehat community within one cycle.
|
|
138
|
+
|
|
139
|
+
Define the scope precisely in the bounty listing — which contracts, which networks, which versions, and what is explicitly out-of-scope (frontend bugs, off-chain infra, social engineering). Vague scope produces low-quality reports and disputes over payout. Include a "safe harbor" clause so researchers know exploit-style testing on testnet (or sometimes on a forked mainnet) is welcome rather than legally fraught. Immunefi's template legal language is the de facto industry standard; adapt it rather than writing your own.
|
|
140
|
+
|
|
141
|
+
### Anti-patterns to avoid
|
|
142
|
+
|
|
143
|
+
A few patterns reliably destroy audit value, and the firms see them constantly.
|
|
144
|
+
|
|
145
|
+
- **Booking the audit before the code is ready.** "We'll be done by then" almost never holds. Slipping the freeze date by a week to fit the booked slot leaves a half-finished codebase under review, which produces a half-useful report. Either delay the audit or pay a partial rebooking fee — both are cheaper than a shallow review of unfinished code.
|
|
146
|
+
- **Changing scope mid-audit.** Adding a contract on day 4 of a 10-day audit invalidates the threat-model assumptions auditors are reasoning from. Hold new scope until the next engagement.
|
|
147
|
+
- **Treating audit findings as optional.** Every High and Medium is a real finding even if "we don't think it's exploitable" — auditors qualify severity, not exploitability under your current threat model, and threat models change post-launch.
|
|
148
|
+
- **Hiring the cheapest firm to check a box for investors.** A weak audit from a low-reputation firm is worse than no audit, because it manufactures false confidence. Investors and integrators who know the space recognize the difference; a $20k audit on a $50M protocol reads as negligence, not frugality.
|
|
149
|
+
- **Skipping the bounty because "we already audited."** Audits are a snapshot; bounties are continuous. Every protocol that has been exploited had been audited.
|
|
150
|
+
- **Ignoring informational findings.** Many of yesterday's High-severity exploits started as last year's "Informational" notes the team didn't bother to fix. An informational finding is still a real signal about your design — treat it as a free pre-warning, not as noise.
|
|
151
|
+
- **Auditing the same contracts repeatedly while leaving glue code unaudited.** Deployment scripts, multisig configuration, upgrade procedures, off-chain keepers, and frontend transaction construction are all attack surface. Several high-profile exploits have targeted exactly these — a perfectly audited contract called incorrectly by a buggy frontend or initialized wrong by an unaudited script. Scope at least one engagement around the operational glue.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web3-common-vulnerabilities
|
|
3
|
+
description: SWC-style checklist of the most-exploited Solidity bugs — reentrancy, delegatecall hazards, signature replay, front-running, unchecked calls, unbounded loops, and the small set of patterns that catch them
|
|
4
|
+
topics: [web3, vulnerabilities, security, swc, solidity]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The most-exploited contract bugs are usually the same handful, recycled across protocols by attackers who know exactly which patterns auditors and authors keep missing. Internalize this checklist; gate every PR with Slither and Foundry tests so the mechanical findings never reach review; require a dedicated reviewer pass for any change that touches the patterns below. Where this doc is the SWC-style enumeration of "what goes wrong," `web3-security.md` is the practices doc on "how to build so it doesn't" — read both, and treat `web3-audit-workflow.md` as the tooling glue.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
There are roughly ten vulnerability classes that account for the overwhelming majority of historical losses, and almost every one has a standard mitigation. Slither catches a meaningful subset — unchecked low-level calls, `tx.origin` auth, obvious reentrancy — in CI, before a human ever sees the diff. Foundry fuzz and invariant tests catch another slice when the spec is written down: balance conservation, monotonic nonces, no-free-mint. Manual review catches the rest — the cross-function reentrancy, the subtle `delegatecall` target, the signature missing a chain ID. None of these layers is sufficient alone, and "we couldn't find a problem" is not the same as "there is no problem."
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Reentrancy
|
|
16
|
+
|
|
17
|
+
The classic exploit: an external call to a user-controlled address re-enters the same function (or a sibling) before storage is updated, letting the attacker drain a balance N times. Solve it structurally with Checks-Effects-Interactions, and defensively with OpenZeppelin's `nonReentrant`.
|
|
18
|
+
|
|
19
|
+
Vulnerable:
|
|
20
|
+
|
|
21
|
+
```solidity
|
|
22
|
+
function withdraw() external {
|
|
23
|
+
uint256 amount = balances[msg.sender];
|
|
24
|
+
(bool ok, ) = msg.sender.call{value: amount}(""); // re-entry happens here
|
|
25
|
+
require(ok, "send failed");
|
|
26
|
+
balances[msg.sender] = 0; // too late
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Fixed:
|
|
31
|
+
|
|
32
|
+
```solidity
|
|
33
|
+
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
34
|
+
|
|
35
|
+
contract Vault is ReentrancyGuard {
|
|
36
|
+
mapping(address => uint256) public balances;
|
|
37
|
+
error Empty();
|
|
38
|
+
error TransferFailed();
|
|
39
|
+
|
|
40
|
+
function withdraw() external nonReentrant {
|
|
41
|
+
uint256 amount = balances[msg.sender];
|
|
42
|
+
if (amount == 0) revert Empty();
|
|
43
|
+
balances[msg.sender] = 0; // effect before
|
|
44
|
+
(bool ok, ) = msg.sender.call{value: amount}(""); // interaction last
|
|
45
|
+
if (!ok) revert TransferFailed();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Cross-function and read-only reentrancy (a view function reads stale state during a reentrant call) both exist — `nonReentrant` on every state-touching external is cheap insurance.
|
|
51
|
+
|
|
52
|
+
### Integer over/underflow
|
|
53
|
+
|
|
54
|
+
Solidity 0.8+ inserts overflow checks into every arithmetic op by default; the famous 2018 BatchOverflow-style bugs are no longer reachable in idiomatic code. The remaining risk is the `unchecked { ... }` escape hatch and importing legacy `<0.8` libraries unguarded. Use `unchecked` only where overflow is provably impossible (loop counters bounded by a checked length) and leave a comment:
|
|
55
|
+
|
|
56
|
+
```solidity
|
|
57
|
+
for (uint256 i = 0; i < items.length; ) {
|
|
58
|
+
// ... body ...
|
|
59
|
+
unchecked { ++i; } // bounded by items.length, cannot overflow
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Front-running (MEV)
|
|
64
|
+
|
|
65
|
+
Every pending transaction is public; searchers and builders can reorder, sandwich, or front-run anything price-sensitive. For auctions, oracle updates, and competitive ops, use a commit-reveal scheme so bids are not visible until reveal:
|
|
66
|
+
|
|
67
|
+
```solidity
|
|
68
|
+
mapping(address => bytes32) public commits;
|
|
69
|
+
|
|
70
|
+
function commitBid(bytes32 hash) external { commits[msg.sender] = hash; }
|
|
71
|
+
|
|
72
|
+
function revealBid(uint256 bid, bytes32 salt) external payable {
|
|
73
|
+
require(keccak256(abi.encode(bid, salt, msg.sender)) == commits[msg.sender], "bad reveal");
|
|
74
|
+
require(msg.value == bid, "value != bid");
|
|
75
|
+
// ... resolve auction ...
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For one-shot user txs that must not be sandwiched, route through Flashbots Protect or a private mempool RPC. Quote slippage tolerances and deadlines on every swap.
|
|
80
|
+
|
|
81
|
+
### `delegatecall` hazards
|
|
82
|
+
|
|
83
|
+
`delegatecall` runs target bytecode against the caller's storage and `msg.sender` — the foundation of upgradeable proxies, and a foot-cannon if the target address is attacker-controlled. Never `delegatecall` to an unvalidated address:
|
|
84
|
+
|
|
85
|
+
```solidity
|
|
86
|
+
// VULNERABLE — total takeover
|
|
87
|
+
function forward(address target, bytes calldata data) external {
|
|
88
|
+
(bool ok, ) = target.delegatecall(data);
|
|
89
|
+
require(ok);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If you need a proxy, use OpenZeppelin's `ERC1967Proxy` / `UUPSUpgradeable` with an admin-gated `_authorizeUpgrade`, and pin the implementation address in `immutable` storage where the architecture allows.
|
|
94
|
+
|
|
95
|
+
### Unchecked external calls
|
|
96
|
+
|
|
97
|
+
Low-level `.call`, `.delegatecall`, and `.staticcall` return `(bool ok, bytes memory)`. Ignoring `ok` silently swallows reverts:
|
|
98
|
+
|
|
99
|
+
```solidity
|
|
100
|
+
// VULNERABLE
|
|
101
|
+
recipient.call{value: amount}("");
|
|
102
|
+
|
|
103
|
+
// FIXED
|
|
104
|
+
(bool ok, ) = recipient.call{value: amount}("");
|
|
105
|
+
if (!ok) revert TransferFailed();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For ERC-20, use OpenZeppelin's `SafeERC20.safeTransfer` / `safeTransferFrom` — they handle non-standard tokens (USDT) that don't return a bool and revert on failure.
|
|
109
|
+
|
|
110
|
+
### Signature replay (EIP-712 + nonces)
|
|
111
|
+
|
|
112
|
+
A signature without a nonce, chain ID, and contract address can be replayed forever, on every chain. Use EIP-712 typed data with a per-signer nonce that increments on every accepted signature:
|
|
113
|
+
|
|
114
|
+
```solidity
|
|
115
|
+
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
116
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
117
|
+
|
|
118
|
+
contract Permit is EIP712 {
|
|
119
|
+
mapping(address => uint256) public nonces;
|
|
120
|
+
bytes32 private constant TYPEHASH =
|
|
121
|
+
keccak256("Claim(address user,uint256 amount,uint256 nonce,uint256 deadline)");
|
|
122
|
+
|
|
123
|
+
constructor() EIP712("MyProtocol", "1") {}
|
|
124
|
+
|
|
125
|
+
function claim(address user, uint256 amount, uint256 deadline, bytes calldata sig) external {
|
|
126
|
+
require(block.timestamp <= deadline, "expired");
|
|
127
|
+
bytes32 digest = _hashTypedDataV4(
|
|
128
|
+
keccak256(abi.encode(TYPEHASH, user, amount, nonces[user]++, deadline))
|
|
129
|
+
);
|
|
130
|
+
require(ECDSA.recover(digest, sig) == user, "bad sig");
|
|
131
|
+
// ... pay out ...
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The domain separator already includes `chainid` and `address(this)`, so the same signature cannot be replayed on another chain or fork.
|
|
137
|
+
|
|
138
|
+
### DoS via unbounded arrays
|
|
139
|
+
|
|
140
|
+
Looping over user-controlled arrays grows gas with N; an attacker grows N until the function reverts forever, bricking whatever it gated. Anti-pattern:
|
|
141
|
+
|
|
142
|
+
```solidity
|
|
143
|
+
// VULNERABLE — one griefer locks every payout
|
|
144
|
+
function distribute() external {
|
|
145
|
+
for (uint256 i; i < recipients.length; ++i) {
|
|
146
|
+
(bool ok, ) = recipients[i].call{value: shares[i]}("");
|
|
147
|
+
require(ok); // any reverting recipient halts everyone
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Convert to pull payments: credit each recipient in storage and let them withdraw individually. Same fix for any "iterate over all users" loop — cap input length, paginate, or invert control.
|
|
153
|
+
|
|
154
|
+
### Approve-and-pull race
|
|
155
|
+
|
|
156
|
+
ERC-20 `approve(spender, N)` is vulnerable to a known race: a spender watching the mempool can spend the old allowance, then the new one, ending up with `old + new`. Mitigate one of three ways: (1) set allowance to `0` first, then to the new value in a second tx; (2) use `increaseAllowance` / `decreaseAllowance` on tokens that support them; (3) use OpenZeppelin's `SafeERC20.forceApprove` which handles the zero-reset under the hood. For new code, prefer EIP-2612 `permit` so allowance and use happen atomically in one tx.
|
|
157
|
+
|
|
158
|
+
### `tx.origin`
|
|
159
|
+
|
|
160
|
+
`tx.origin` is the EOA that started the transaction tree; `msg.sender` is the immediate caller. A phishing contract that tricks a user into calling it can pass any `tx.origin` check while `msg.sender` is the malicious contract. Authorize with `msg.sender`, always:
|
|
161
|
+
|
|
162
|
+
```solidity
|
|
163
|
+
modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; } // correct
|
|
164
|
+
// require(tx.origin == owner) // WRONG — phishable
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The only narrow use is `require(tx.origin == msg.sender)` to refuse contract callers, and even that is increasingly fragile under account abstraction (EIP-4337) and EIP-3074.
|
|
168
|
+
|
|
169
|
+
### Self-destruct deprecation
|
|
170
|
+
|
|
171
|
+
`SELFDESTRUCT` no longer deletes code or storage after EIP-6780 (Dencun, March 2024) — it only forwards the ETH balance, and only fully self-destructs if called in the same tx that created the contract. Audits and tooling still flag it; treat it as effectively removed, never rely on it for upgrade or wipe semantics, and migrate any legacy code that did.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web3-conventions
|
|
3
|
+
description: Solidity style and convention discipline for smart-contract teams — forge fmt as single formatter, NatSpec on public functions, pinned pragma, custom errors over string reverts, naming and ordering rules
|
|
4
|
+
topics: [web3, conventions, solidity, forge-fmt, natspec]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Solidity is brittle. The compiler is fast and unforgiving, the gas model rewards terseness, and the deployment surface is immutable — every style drift you tolerate during development eventually becomes a bytecode-determinism question, an audit comment, or a bug nobody can patch. The conventions below are the ones enforced by `forge fmt` and code review at every serious shop; encode them as CI gates and they stop being judgment calls.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Use `forge fmt` as the single formatter for any Foundry project — it replaces prettier-solidity-plugin and is the only formatter that tracks the official Solidity style guide. Require `NatSpec` (`@notice`, `@dev`, `@param`, `@return`) on every public and external function; this is what wallets and block explorers render to users. Pin the `pragma` exactly (`pragma solidity 0.8.24;`) instead of carets — reproducible bytecode is a security property, not a preference. Prefer custom errors to string reverts: cheaper, introspectable, and trivially decoded by clients. Follow PascalCase contracts, camelCase functions/variables, UPPER_SNAKE_CASE constants, leading-underscore private vars, and past-tense event names — these match the [official style guide](https://docs.soliditylang.org/en/latest/style-guide.html) and what `forge fmt` enforces structurally.
|
|
12
|
+
|
|
13
|
+
## Deep Guidance
|
|
14
|
+
|
|
15
|
+
### Pragma and license
|
|
16
|
+
|
|
17
|
+
Every Solidity file starts with an SPDX license identifier and an exact pragma. The license header is required by `solc` and surfaces in verified-source explorers; the pinned pragma is what makes your deployed bytecode reproducible months later.
|
|
18
|
+
|
|
19
|
+
```solidity
|
|
20
|
+
// SPDX-License-Identifier: MIT
|
|
21
|
+
pragma solidity 0.8.24;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Do not use `pragma solidity ^0.8.0;` in deployable contracts. A caret range lets `solc` 0.8.24 and 0.8.27 produce different bytecode for the same source, which breaks deterministic deployments (CREATE2 addresses), audit reproduction, and source verification on chains with strict matching. Pin to a specific patch version, commit `foundry.toml` with the same `solc_version`, and bump deliberately. Use `// SPDX-License-Identifier: MIT` for permissive code and `// SPDX-License-Identifier: AGPL-3.0` for protocol code that should stay open.
|
|
25
|
+
|
|
26
|
+
### NatSpec on public functions
|
|
27
|
+
|
|
28
|
+
NatSpec is not a documentation nicety — it is the contract the user sees. Wallets like Rabby, Frame, and Safe render `@notice` strings during transaction confirmation, and Etherscan surfaces NatSpec on verified contracts. Missing NatSpec on a public function means the user signs a transaction with no human-readable description.
|
|
29
|
+
|
|
30
|
+
```solidity
|
|
31
|
+
/// @title Vault
|
|
32
|
+
/// @notice Holds user deposits and accrues yield.
|
|
33
|
+
contract Vault {
|
|
34
|
+
/// @notice Deposit `amount` of the underlying asset and mint vault shares.
|
|
35
|
+
/// @dev Pulls tokens via `transferFrom`; caller must approve first.
|
|
36
|
+
/// Reverts with `ZeroAmount` if `amount == 0`.
|
|
37
|
+
/// @param amount Amount of underlying to deposit, in token decimals.
|
|
38
|
+
/// @param receiver Address that will receive the minted shares.
|
|
39
|
+
/// @return shares Number of vault shares minted to `receiver`.
|
|
40
|
+
function deposit(uint256 amount, address receiver)
|
|
41
|
+
external
|
|
42
|
+
returns (uint256 shares)
|
|
43
|
+
{
|
|
44
|
+
if (amount == 0) revert ZeroAmount();
|
|
45
|
+
// ...
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Rules:
|
|
51
|
+
|
|
52
|
+
- `@notice` is mandatory on every `public`/`external` function — it is the end-user description.
|
|
53
|
+
- `@dev` is for integrators reading the source; explain invariants, side effects, and revert conditions here.
|
|
54
|
+
- `@param` and `@return` are mandatory whenever the function has parameters or named returns.
|
|
55
|
+
- `@inheritdoc` on overrides — do not copy-paste the parent's NatSpec.
|
|
56
|
+
- Internal/private functions can use `//` comments; reserve `///` for documented interfaces.
|
|
57
|
+
|
|
58
|
+
### Naming and ordering
|
|
59
|
+
|
|
60
|
+
The Solidity style guide names the categories; `forge fmt` enforces the ordering within a contract.
|
|
61
|
+
|
|
62
|
+
```solidity
|
|
63
|
+
// SPDX-License-Identifier: MIT
|
|
64
|
+
pragma solidity 0.8.24;
|
|
65
|
+
|
|
66
|
+
contract Vault {
|
|
67
|
+
// 1. State variables
|
|
68
|
+
uint256 public constant MAX_FEE_BPS = 500;
|
|
69
|
+
address public owner;
|
|
70
|
+
uint256 private _totalShares;
|
|
71
|
+
|
|
72
|
+
// 2. Events
|
|
73
|
+
event Deposited(address indexed user, uint256 amount, uint256 shares);
|
|
74
|
+
|
|
75
|
+
// 3. Errors
|
|
76
|
+
error ZeroAmount();
|
|
77
|
+
error InsufficientBalance(uint256 available);
|
|
78
|
+
|
|
79
|
+
// 4. Modifiers
|
|
80
|
+
modifier onlyOwner() {
|
|
81
|
+
if (msg.sender != owner) revert InsufficientBalance(0);
|
|
82
|
+
_;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 5. Constructor
|
|
86
|
+
constructor(address _owner) {
|
|
87
|
+
owner = _owner;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 6. receive / fallback (none here)
|
|
91
|
+
|
|
92
|
+
// 7. external -> public -> internal -> private functions
|
|
93
|
+
function deposit(uint256 amount) external returns (uint256) { /* ... */ }
|
|
94
|
+
function totalShares() public view returns (uint256) { return _totalShares; }
|
|
95
|
+
function _mintShares(address to, uint256 shares) internal { /* ... */ }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Naming rubric:
|
|
100
|
+
|
|
101
|
+
- **Contracts / libraries / interfaces**: `PascalCase`. Interfaces prefixed with `I` (`IERC20`, `IVault`).
|
|
102
|
+
- **Functions / state variables / locals**: `camelCase` (`totalShares`, `computeFee`).
|
|
103
|
+
- **Constants and immutables**: `UPPER_SNAKE_CASE` (`MAX_FEE_BPS`, `WETH`).
|
|
104
|
+
- **Private / internal storage**: single leading underscore (`_totalShares`, `_pendingWithdrawals`). Function parameters that shadow state vars also take a leading underscore (`address _owner`).
|
|
105
|
+
- **Custom errors**: `PascalCase`, descriptive (`InsufficientBalance(uint256 available)` not `ERR_BAL`).
|
|
106
|
+
- **Events**: `PascalCase`, past-tense verb (`Deposited`, `OwnerUpdated`, `Withdrawn`) — events describe something that already happened.
|
|
107
|
+
|
|
108
|
+
### `forge fmt` as single formatter
|
|
109
|
+
|
|
110
|
+
Foundry ships its own formatter: `forge fmt`. It is the only formatter that tracks the Solidity style guide in lockstep with `solc` releases, and it eliminates the `prettier` + `prettier-plugin-solidity` + `solhint` + `solhint-prettier` dependency stack that plagued older projects.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
forge fmt # format every .sol file in src/, test/, script/
|
|
114
|
+
forge fmt --check # CI mode: exit non-zero if anything is unformatted
|
|
115
|
+
forge fmt path/to/Vault.sol # single file
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Wire `forge fmt --check` into CI and a pre-commit hook. Configure it in `foundry.toml`:
|
|
119
|
+
|
|
120
|
+
```toml
|
|
121
|
+
[fmt]
|
|
122
|
+
line_length = 120
|
|
123
|
+
tab_width = 4
|
|
124
|
+
bracket_spacing = true
|
|
125
|
+
int_types = "long" # uint256, not uint
|
|
126
|
+
quote_style = "double"
|
|
127
|
+
number_underscore = "thousands"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If you want lint rules beyond formatting (e.g. flag `tx.origin`, missing visibility, reentrancy patterns), add `solhint` with a minimal `.solhint.json` — but do not duplicate formatting rules between them. Let `forge fmt` own layout; let `solhint` own semantics.
|
|
131
|
+
|
|
132
|
+
### Custom errors
|
|
133
|
+
|
|
134
|
+
`require(condition, "string message")` was the only revert pattern before Solidity 0.8.4. Custom errors are strictly better: cheaper to deploy and call, encode arguments, and decode reliably in clients.
|
|
135
|
+
|
|
136
|
+
```solidity
|
|
137
|
+
// Before — string revert
|
|
138
|
+
function withdraw(uint256 amount) external {
|
|
139
|
+
require(amount > 0, "Vault: zero amount");
|
|
140
|
+
require(balances[msg.sender] >= amount, "Vault: insufficient balance");
|
|
141
|
+
// ...
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// After — custom errors
|
|
145
|
+
error ZeroAmount();
|
|
146
|
+
error InsufficientBalance(uint256 requested, uint256 available);
|
|
147
|
+
|
|
148
|
+
function withdraw(uint256 amount) external {
|
|
149
|
+
if (amount == 0) revert ZeroAmount();
|
|
150
|
+
uint256 bal = balances[msg.sender];
|
|
151
|
+
if (bal < amount) revert InsufficientBalance(amount, bal);
|
|
152
|
+
// ...
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Benefits:
|
|
157
|
+
|
|
158
|
+
- **Gas**: each unique revert string costs ~50 bytes of deployed bytecode plus runtime memory expansion; custom errors are a 4-byte selector.
|
|
159
|
+
- **Introspection**: clients (ethers, viem, foundry traces) decode `InsufficientBalance(100, 42)` directly. String reverts are opaque blobs.
|
|
160
|
+
- **Refactor safety**: rename the error type and the compiler finds every caller; rename a string and nothing breaks until production.
|
|
161
|
+
|
|
162
|
+
Reserve `require` for legacy interface compatibility or when you genuinely need the string in a chain explorer; everywhere else, define a typed error.
|