The audit

Nine reviews, eight findings closed, three open — and a hash that proves less than the word "audited" suggests.

The contracts were reviewed on 21 September 2026 across nine independent passes: four agents writing adversarial test suites (accounting and rounding, EIP-4626 conformance, the deployment path, the browser signing layer) and five cold readings of the sources with no project context at all.

Eleven findings. Eight are closed. Three remain open and are listed below rather than buried.

contracts/audit/FINDINGS.md is the audit itself. Every finding was re-run or re-read before it was written down, and is recorded with the numbers it actually produced rather than an estimate.

This is not a third-party attestation. The review was performed by the same party that wrote the code. Nobody independent has signed anything. The hash below pins bytes; it does not say the review was good. Those are three separate promises.

What is still open

The deposit quote is slightly generous. The page inverts the displayed price instead of calling previewDeposit. On a 100,000 USDG deposit it shows 80,000.06 vDEED where the chain mints 80,000.00 — visible at two decimals, and in the depositor's favour on screen, which is the wrong direction to be wrong in.

The deposit button drops the cents. An amount typed as 5000.75 signs 5000750000 under a button reading "Deposit 5,000 USDG", and the activity log repeats the wrong figure afterwards.

A mistyped Roll number is permanent. RollRegistry.latest only moves up and a number can never be rewritten, so publishing Roll 65535 by accident pins latest there forever. Owner-only, so a footgun rather than an attack. A bound was written and reverted because it breaks fuzz tests that range over all uint16.

Verifying the audit yourself

The audit is hash-anchored, and the hash moves with each revision. Read it from contracts/audit/README.md in the branch you are checking, never from a value you remember.

Three commands, in rising order of what they prove.

1 — The document you were handed is the one that was published

sha256sum contracts/audit/MANIFEST.txt

One line, no toolchain. MANIFEST.txt lists the SHA-256 of every file reviewed, what those files compile to, which suites ran and how they came out — including FINDINGS.md's own hash, so a match covers the findings too.

2 — The manifest describes this repository

cd contracts
audit/manifest.sh | diff -u audit/MANIFEST.txt -

Regenerates the manifest from the working tree and diffs it. Silence means the sources, the compiled output and the test results all still match. This needs Foundry and Node and it rebuilds and re-runs everything, which is the point: verification here is regeneration, the same way a Roll's hash is checked by recomputing the CSV rather than trusting it.

3 — The audited code is the code that is live

audit/canonical-runtime.py out/DeedVault.sol/DeedVault.json \
  "$(cast code $VAULT --rpc-url $RPC_URL)" | cast keccak

That must equal the DeedVault codehash in the manifest.

A deployed contract's raw code hash is not a property of its source: Solidity patches constructor-set immutable values straight into the runtime bytecode, so the same source deployed twice with different arguments gives two different hashes. canonical-runtime.py zeroes exactly those byte ranges — they are listed in the manifest, so anyone can see what was blanked — and hashes what is left.

RollRegistry has no immutables, so cast codehash on it matches the manifest directly, with no masking at all.

What survived attack

The suites that tried and failed are as informative as the findings. Read the Attacked and held section of FINDINGS.md for what was attempted against the inflation vector, the rounding direction and the cap arithmetic.