How the smart contracts are tested.

Every contract behind the Pigfox demos passes the same gates before it reaches Base Sepolia: two linters, unit and invariant tests, static analysis, two independent property fuzzers, and a 100% coverage gate. Not a house style — a shared pipeline, in its own public repository, that all 40 demo repositories consume.


PIGFOX SOLIDITY PIPELINE v1

The pipeline lives in one place: github.com/pigfox/solidity-pipeline. Each contract repository vendors it as a submodule and calls its reusable workflow, so there is exactly one definition of “green” and a fix to a gate lands once instead of once per repository. It also means the gates that run in CI are the same bytes a developer runs locally, rather than an approximation of them.

Lint forge lint · Solhint style and natspec catches drift in seconds, not after the fuzzers Foundry unit · invariant · drill pinned toolchain catches broken logic and violated invariants Slither static analysis fails on low and up catches known-unsafe patterns before runtime Echidna + Medusa property fuzzing, twice count asserted, not trusted catches counterexamples hand-written tests missed Base Sepolia rehearsal, then live broadcast catches what only real infrastructure reveals Each CI stage runs on every push. Nothing reaches the chain that has not survived every one of them.

One property set per repository. Three engines each.

Each repository has exactly one test/Properties.sol holding its invariants, and Foundry, Echidna and Medusa all execute that same file — so a property cannot hold in one engine and silently not exist in another. What each set proves is documented in that repository, beside the properties themselves.


Where the figures are.

This page used to print a table of test counts, property counts and fuzzing-call totals. It no longer does, and the reason is the point of the page.

Those numbers were measured honestly. Then more demos shipped and the table did not move: it went on naming a subset of the repositories, and totalling only their properties, long after both were wrong — on a page whose entire subject is not trusting a figure you have not checked. A number nothing verifies is a number that rots, and rots quietly.

So the figures live where they are produced and where they are enforced: in each repository, on every push. The pipeline asserts them there — the coverage gate fails below 100%, the property count must agree four ways before a build passes — and no copy of them here could be more current than the run itself. Each repository below links to its live CI, which updates itself and cannot be out of date by construction.

DemoRepositoryPipeline
ZK Escrow Explorer zk-escrow latest run
RWA Tokenization Explorer rwa-tokenization latest run
ZK KYC Pass Explorer zk-kyc-pass latest run
Gas Optimization Race gas-optimization latest run
Interest Rate Model Explorer interest-rate-model latest run
AI Parametric Insurance ai-parametric-insurance latest run
Oracle Health Explorer oracle-health-explorer latest run
Deposit Dispute deposit-dispute latest run
MMR Log mmr-log latest run
Bench Lab bench-lab latest run
Judge Lab judge-lab latest run
SBA Lab sba-lab latest run
Access Audit Lab access-audit-lab latest run
Agent Gateway agent-gateway latest run
unselect2 unselect2 latest run
WASM Keygrind wasm-keygrind latest run
Backpressure Lab backpressure-lab latest run
Aegean Placement aegean-placement latest run
pprof Lab pprof-lab latest run
Reconcile Lab reconcile-lab latest run
Orbit Lab orbit-lab latest run
Kafka Lab kafka-lab latest run
RAG Lab rag-lab latest run
Cosmos Lab cosmos-lab latest run
Goroutine Visualizer goroutine-visualizer latest run
Rust Lifetime Lab rust-lifetime-lab latest run
Rust Move Lab rust-move-lab latest run
Rust Borrow Lab rust-borrow-lab latest run
Rust Send/Sync Lab rust-send-sync-lab latest run
LangDAG Lab langdag-lab latest run
ETL Lab etl-lab latest run
Slack AI Lab slack-ai-lab latest run
AMM Lab amm-lab latest run
Arb Lab arb-lab latest run
Agent Swarm Lab agent-swarm-lab latest run
CRDT Lab crdt-lab latest run
Wallet risk triage & sanctions screening wallet-risk-lab latest run
P2P Lab p2p-lab latest run
WebSocket Lab websocket-lab latest run
FIX Lab fix-lab latest run

A page giving each demo its own pipeline evidence — the figures, pegged to the commit that produced them, so a displayed number is true by construction rather than true until the repository changes — is the work this table is a placeholder for.

Slither runs on every repository at fail-on: low, against one shared configuration. Every excluded detector is written down with a reason in the pipeline repository, because a detector switched off in a config file with no reason attached is indistinguishable from one switched off to make a build pass. Findings below that severity are printed rather than suppressed.


The stages.

Every stage runs on every push, with no fast path and no opt-out: the gates self-test, then the two linters, a doctrine gate, the property-count check, formatting on a pinned toolchain, build and test and coverage, static analysis, then each fuzzer in turn, and a single required check that the rest report into. Grouped by what they catch:

  • forge lint and Solhint. The pipeline’s first stage, and first because it is the cheapest: no fuzzing, no coverage run and no chain, so a style or natspec regression fails in seconds rather than after the fuzzers. Two linters rather than one, because they cover different ground — forge lint is Foundry’s own and needs no npm, and Solhint adds the natspec surface it does not check. Every rule the estate turns off is argued in writing in the pipeline repository, the same discipline as the Slither exclusions, and per-line suppression comments are not used at all. Because forge lint has no config-file flag, its configuration is copied into each repository rather than consumed — so a further gate checks each copy still matches the canonical one, and fails the build when it does not.
  • Foundry. Unit, fuzz and invariant tests on a pinned toolchain, all passing and none skipped — a skipped test is a red build here, not a yellow one. The formatting gate runs on a deliberately pinned compiler version so an unrelated upstream release cannot turn an unchanged tree red.
  • Slither. Static analysis on every push, failing the build on low-severity findings and up. The exclusions are written down in the pipeline repository rather than silently configured away, and per-file suppression comments are not used at all.
  • Echidna and Medusa. Two independent fuzzers hammer the same property file, in an in-memory EVM built fresh from source. Both assert the number of properties they registered — because a stale build artifact once silently shrank a property set and reported a smaller green run. The count is declared as a literal in Solidity, and four independent things must agree with it before a build passes.
  • Live chain. Every demo is deployed to Base Sepolia and verified on Basescan. For zk-escrow that also covers a UUPS upgrade and AI-arbitrated dispute settlement: the arbiter's ruling and its full reasoning are written on-chain, and a UUPS upgrade added arbiter rotation to recover disputes stranded behind an arbiter address for which no private key existed. The claim is that the mechanism has been exercised on a real chain, not that it has been exercised often — and the current state of it is readable on the demo pages, which read the chain rather than restating it here. Upgrades are rehearsed first on the same live chain, against throwaway contracts the drill deploys itself so it structurally cannot touch the real proxy, then broadcast, then verified on-chain.

Coverage without asterisks.

The gate requires 100% line, statement, branch, and function coverage on every file under src/. The only exclusions the estate grants are generated verifier contracts in the zero-knowledge repositories — produced verbatim by snarkjs from that repository's own proving key, so their identifiers and their residual uncovered lines are not ours to change. Those lines are the assembly paths taken when the BN254 precompiles themselves report failure: reaching them would need a broken EVM, not a broken proof, and the verifiers are still exercised end-to-end by real proofs. Every exclusion in force, and the argument for it, is written down in the pipeline repository rather than inferred from a percentage.

Generated code is excluded by name, printed on every run, never folded into a percentage. The check fails on a report it cannot parse, so a format change cannot pass by default; and it fails on an exclusion that no longer matches a real file, because a stale exclusion is a hole that reads as a decision.


What broke decides what gets fixed.

A failing pipeline is not a single event, and treating it as one is how a contract ends up edited to satisfy a broken tool. The rule is written down:

  • A coverage shortfall means the code is right and a path is untested. Write the test. No source change, and no redeploy — redeploying identical bytecode fixes nothing.
  • A tooling or configuration failure is fixed in the tooling or the configuration. Never in a contract.
  • A failing test or violated property means the deployed contract is not demo-worthy. The source is fixed; a plain contract is redeployed and re-verified, and a contract behind an upgradeable proxy has its implementation upgraded rather than replaced — the proxy holds the history, and preserving it is what a proxy is for. Then the entire pipeline runs again.

See the contracts in action →