# Static analysis passes - 2026-09-11 Four tools run over the five production contracts at commit `a44d93f` (tag `audit-2026-09-12-supplement`; the contracts are byte-identical to the code baseline `audit-2026-09-12-remediation`). Raw output is beside this file. Every result was read against the source and against what the audit brief, `contracts/AUDIT.md` and the Pashov-workflow supplement already record. Result: no new finding that requires a code change. Two optional nits are recorded below. These passes are not a substitute for the phased independent review in `LAUNCH.md`; they exist so that a firm or a contest bot does not report tool output as a discovery. ## What ran | tool | version | invocation | output | |---|---|---|---| | Slither | 0.11.6 | `cd contracts && slither . --config-file slither.config.json --checklist` (project config: node_modules and test mocks filtered, eight noisy detectors excluded, same as the 2026-09-01 review) | `slither.md`, full detector text in `slither.stderr.txt` | | Semgrep | 1.172.0 with the Decurity smart-contract rules at commit `2e878a8` (57 Solidity rules: security, best-practice, performance) | `semgrep --config /solidity` on the five files | `semgrep.json` | | Aderyn | 0.6.8, Linux build in a container over the Hardhat project | `aderyn . -o report.md` (88 detectors) | `aderyn.md` | | 4naly3er | Picodes/4naly3er commit `8a9d1eb`, patched locally to add solc 0.8.24 and `evmVersion: cancun` (the stock tool tops out at 0.8.23 and cannot compile OpenZeppelin's `mcopy`) | `yarn analyze contracts scope.txt` with the five files in scope | `4naly3er.md` | Aderyn and 4naly3er have no Windows builds; they ran in `node:22-bookworm` containers with the contracts directory mounted. Nothing in the repository was modified by any run. ## Counts | tool | high / error | medium / warning | low | informational or gas | |---|---|---|---|---| | Slither | 3 (weak-prng 1, reentrancy-eth 2) | 2 (unused-return) | 4 | 1 | | Semgrep | 1 (arbitrary-low-level-call) | 4 (exact-balance-check) | 0 | 36 | | Aderyn | 4 issue types | 0 | 11 issue types | 0 | | 4naly3er | 0 | 5 issue types | 12 issue types | 22 non-critical, 15 gas | ## Triage ### Already recorded, no action - **Slither weak-prng on `setStartingIndex`.** Documented in `contracts/AUDIT.md` ("Slither still reports weak-prng here; that detector matches on keccak256(blockhash) % N and cannot see the commit step"). The reveal assumptions are in the brief, and the nonuniform-rotation observation from the Pashov-workflow pass is preserved for the firm. - **Slither reentrancy-eth and reentrancy-benign on `RoyaltyRouter.release` and `_send`.** The two accepted findings in `contracts/AUDIT.md`: the deferred-leg bookkeeping is written after the call by design, every entry point is `nonReentrant`, and the property is tested. Aderyn H-3 on the router is the same pattern seen inside the constructor, where no reentrancy is possible. - **Slither calls-loop, costly-loop, reentrancy-events on `DisorderlyAgentCollection.addMany`.** The four informational notes accepted in the 2026-09-01 review; owner-only batch over an external registry, chunked by procedure. Aderyn L-2 and L-7 and 4naly3er GAS-5 and GAS-13 are the same loops. - **Recipient gas exhaustion (4naly3er L-5, six ETH sends).** The Pashov- workflow lead G1/G2, reproduced in `contracts/test/pashov/07-recipient-gas.test.js` and preserved for the firm with its disposition. Destinations are immutable Safes chosen at deploy. - **Centralization (Aderyn L-1, 4naly3er M-2, 30 owner-only entry points).** The owner is the treasury Safe on every contract; brief section 1 states the Safe is not contract-constrained to the record and asks the firm to assess it. `renounceOwnership` is overridden to revert on all four. - **Chainlink staleness (4naly3er M-3, Slither unused-return).** `_price()` rejects a non-positive answer, a zero or future `updatedAt`, and any reading older than `MAX_PRICE_AGE`; the boundary is tested at 86400 and 86401 seconds in the supplement. The constructor's read is a sanity check that the feed is a feed, not a routing decision, and only needs `answer`. Round-id completeness checks are the deprecated Chainlink guidance and were not added. ### False positives, with the reason - **Semgrep arbitrary-low-level-call, `PayoutDistributor.claim`.** The call target is the account proven by the Merkle leaf, the value is the leaf's amount, calldata is empty, effects precede the interaction, and the function is `nonReentrant`. Standard pull payment. - **Semgrep exact-balance-check (four).** `withdraw`, `skim`, `unwrapWeth` and `sweepToken` revert on a zero balance so a no-op cannot succeed silently. Force-sent ETH raises the balance and flows to the treasury or the split, which is the intended destination for anything unaccounted. - **Semgrep basic-arithmetic-underflow (seven).** Each subtraction is guarded on the line before it: `c.claimed + amount > c.total`, `liabilities` tracked against every credit, `a.length == 0` before `a[a.length - 1]`, `updatedAt > block.timestamp` before the age check, and the metadata mapping runs only for `tokenId` in range. - **Aderyn H-1 and H-4.** Test mocks only (`contracts/test/`), which Aderyn scans and Slither's config filters out. - **Aderyn H-2, ETH transferred without address checks.** Every flagged send goes to an immutable constructor argument or to the Merkle-proven claimant. There is no caller-supplied recipient anywhere. - **4naly3er M-1 and M-4, L2 block numbers and sequencer.** Mainnet and Sepolia only. - **4naly3er L-4, division by zero.** `price` is only used on the branch where `_price()` returned `ok`, which requires `answer > 0`. - **4naly3er L-2 and L-12, `abi.encodePacked` with a dynamic type.** The bytes are the ERC-8041 membership encoding stored as metadata, never hashed; the length byte is always 32. - **4naly3er L-10, Ownable2Step.** All four ownable contracts already inherit `Ownable2Step`; the rule matched the base import. - **4naly3er L-6, strict deadline comparisons.** `openMint` refuses a `publicStart` at or before now on purpose: a reached schedule cannot be moved. - **4naly3er GAS-1, use ERC721A.** Rejected in `contracts/AUDIT.md` alongside `ERC721Enumerable`; the collection mints at most five per call. ### Optional nits, not changed at this tag - **Slither unused-return, `RoyaltyRouter._price` and the constructor.** The unnamed tuple fields could be named and asserted (`answeredInRound`, `roundId`) for readers who expect the older Chainlink checklist. No behavioural gap. - **Aderyn L-8, `DisorderlyAgentCollection.setContractURI` emits no event.** The wrapper is optional and not required for launch; an event would help indexers. Cosmetic. - **Gas suggestions (Semgrep 36, 4naly3er 15 types, Aderyn L-4 and L-5).** Not applied: the contracts are frozen at the audited tag and the savings are a few hundred gas per call on functions the Safe calls monthly. ## Not covered by these passes Fuzzing (Echidna) of the distributor liability and router reserve invariants, symbolic checks (Halmos) on the Merkle claim binding and the reveal mapping, and the Trail of Bits review skills are separate passes with their own records: `docs/audits/fuzzing.md`, `docs/audits/halmos.md` and `docs/audits/tob-2026-09-11/`. They were run on 2026-09-11 and rerun on 2026-09-15 after the review-driven harness fixes (`docs/audits/ STATUS-2026-09-15.md`).