A desk on Robinhood Chain
THE CURB
The position · Mechanism · decision records · C09 · proposed, not decided

A self-review, not a review

the checklist a reviewer would walk, walked by the author; one open finding; what it did not do.

Status: Filed 12 September 2026 by the author of the code, against commit 1d6b3f7 of contracts/src/CompanySeries.sol. This is not an independent review. It does not satisfy C09, and the site does not say reviewed because of it.

13 September mainnet preparation addendum: local source has since added two-step operator nomination/acceptance/cancellation, and the public series deployment tool checks a reviewed Safe expectation on the target chain. The tables keep the earlier source scope, and those findings do not cover the changed source, which needs a new pinned release and independent review. Scope, open findings and acceptance fields: the mainnet dossier.

Method

Line-by-line reading against the blueprint's cases; unit tests, fuzz sequence and invariant handler (256 sequences, depth 64, recorded seed); fork tests on both real components; each item below checked by hand.

Checklist and findings
AreaLooked atFinding
ReentrancynonReentrant mint, claimComponent; allocateExit calls nothing external; claim effects firstHeld (T09); the invariant handler never paid a claim twice.
Checks-effects-interactionsToken calls after state changes; receipt after both pulls and delta checksHeld.
Return values of tokens_pull/_push accept true or no data onlyHeld for standard and no-return tokens; non-32-byte data makes abi.decode revert (fails closed).
Balance deltasExactly lots × q in (mint), exactly the claim out (claimComponent)Held; fee-on-transfer components unusable (T10) and unpayable (T22/T23 logic), deliberately.
Whole-liability checkPays only if held ≥ liability; a mint leaving the series short revertsHeld (T08, T23; invariant ShortfallHaltsPayment).
CapcapLots bounds n × q + reserved per componentHeld (T22; invariant CapCountsReserved).
Immutables and constructionImmutable components, units, cap; zero address, equal components, non-contract, zero units, zero cap, cap × q overflow refusedHeld (T01, T19).
Access controlonlyOperator: stops, permits, transferOperator, cancelOperatorTransfer; acceptOperator: the nominated pendingOperator only (a new surface since 13 September 2026); no admin mint, unallocated burn or sweepHeld (T24; the invariant handler's transferReceipt never succeeds).
Receipt transfertransfer, transferFrom, approve always revertHeld (T17).
PermitsOn-chain mint (unexpired) and claim permits; no backend signatureHeld (T25).
PausesMint and per-component claim pauses independent; A's leaves BHeld (T04, T20, operator-limit tests, the drill).
Arithmetic18 decimals, uint256, lots × q bounded by the constructor's overflow checkHeld (T12).
EventsEvery state change emits; indexer checked against the ABI fixtureHeld.
TimePreview deadline vs block.timestamp; validators shift secondsAcceptable for a fifteen-minute preview; noted.
Low-level calls to a component with no codeA codeless call succeeds with no dataCovered: the constructor refuses non-contracts; the delta check catches a no-op or a later self-destruct, and every operation then reverts, correctly.
transferOperatorHistorical source: one step, no acceptanceFinding in the reviewed source: a typo immediately hands away authority. Local remediation prepared 13 September: nomination (authority kept), nominee-only acceptance, cancellation; see ADR-001. Independent verification and release signoff remain open.
Reason stringsOn-chain stop reason, length unenforcedThe operator tool refuses under eight characters, the contract does not; acceptable.
Front-runningExit and claim act only on the caller's rights; a raced mint cap check fails cleanlyNo holder can act on another's rights.
Gas griefingA gas-exhausting component transferFails closed; the claim stays whole (drill: frozen A).
The credit desk, walked the same way

contracts/src/CreditDesk.sol (fifteen tests in test/CreditDesk.t.sol), 12 September 2026: forty lines, one job.

AreaLooked atFinding
SurfacetopUp only; two immutables; no owner, pause, upgrade or receiveHeld.
ReentrancyOne transferFrom, a view call, then the event; no stored balance or mappingHeld; re-entry can only pay again.
Return valuesFalse, revert or malformed data: TransferFailedHeld (returns-false, halted, no-allowance tests).
Balance deltaTreasury rises by exactly amount, else DeltaWrongHeld (fee-on-transfer test); the site credits from the event, so an underpaying token is unusable.
Zero casesamount == 0, keyHash == 0; zero addresses and a non-contract token at constructionRefused; held.
Who pays whommsg.sender pays an immutable treasury; anyone may top up any hashHeld; a top-up to an unheld hash is the payer's recorded loss.
Front-runningCredit goes to the payer's chosen hashNone.
Site sideEvent-priced at the block's rate; reorgs uncredit; idempotent index; two rows per key, one writer eachHeld (tests/credits.test.ts, local rehearsal). Closed 13 September 2026: a spend-row race lost one of two concurrent charges. Now written conditionally on the version read (writeSnapshotIf); the loser re-reads and is refused, with figures, if too little is left; a write whose reply was lost is found by its token, not repeated (tests/credits.test.ts, tests/store-conformance.ts).
The treasury's keyMultisig by policy; a codeless treasury refused on a public chainHeld; an EOA treasury only on chain 31337.
Static analysis, run and read (13 September 2026)

Slither 0.11 on both contracts, compiled as built (solc 0.8.30, via-IR, optimizer 200, prague), on every source push (.github/workflows/static-analysis.yml, advisory; reports kept per run). Fifteen findings; none changes the source. The readings are the author's; a reviewer may differ.

DetectorWhereReading
reentrancy-balance (high, medium confidence)CreditDesk.topUp; CompanySeries.mint, claimComponentThe delta check is intended. Series: nonReentrant, ledger written after checks. Desk: no guard or state; a reentering transferFrom fails the outer delta check, so everything reverts and nothing is emitted. A guard would cost a slot and change the recorded bytecode; not added. Launch condition: a plain ERC-20 token that calls nobody.
reentrancy-no-eth, reentrancy-benign (medium, low)CompanySeries.mintnonReentrant; totalSupply, balanceOf written after the delta checks.
reentrancy-events (low)CreditDesk.topUpEvent after the call on purpose: a failed transfer records no top-up.
timestamp (low)CompanySeries.mintDeadlines and permit expiries span minutes to days; seconds of sequencer drift change nothing.
low-level-calls (informational)_pull, _push, topUpDeliberate: handles tokens returning nothing or false, without a library.
cyclomatic-complexity (informational)claimComponentEach component's path spelled out; a chosen style.
What this review did not do
  • —It did not read the components' code (issuer proxies); fork tests read behaviour, not source.
  • —No economic attacks across series: one series, no factory.
  • —Static analysis ran only after the fact.
  • —It was done by the person who wrote the code. It is not an independent review.