Crossfoot audits tokenized-asset feeds from the outside: it replays the feed's public rules at exact blocks, shows whether each update went through the guarded path or the admin bypass, and gives consumers an evidence-backed decision.
A vendored ACTUS PAM engine in Rust models what a tokenized instrument should be worth from its contract terms, in exact decimal arithmetic with no floats and no clock reads, and compares the result with what the contract stored on chain at pinned blocks.
Where the underlying portfolio is not observable, the value is never recomputed. The tool replays the oracle's own posting rules per implementation era instead: deviation bound, spacing, min and max, stored rounds against emitted events.
Two targets today. svzchf, the Frankencoin savings vault: full recomputation, two independent model paths. mtbill, Midas mTBILL: consistency checks, no recomputation, the result always carries INPUT_GAP for the NAV.
The binary issues chain reads and keyless HTTP GETs. It holds no key and has no code path that can sign or send a transaction. Every read is pinned to an explicit block; nothing is read at latest.
Every raw response is stored byte for byte with its sha256 in a manifest, and a run can be replayed offline from the local cache. Bundles are not yet self-contained; crossfoot verify, which re-hashes and replays from the bundle alone, is planned, not shipped.
An unchecked post is a posting-path finding: the poster key used the setter that skips the deviation guard, and the move exceeded the bound in force at that block. It says nothing about whether the value was wrong.
| Largest cases | Posts | Bound then | Largest move | Period |
|---|---|---|---|---|
| mBTC | 15 | 0.05 % | 0.105 % | 2024-11-21 to 2025-03-11, posted through a Safe; the most by count |
| mSL | 10 | 0.05 % | 5.727 % | 2025-05-07 to 2026-03-31, then the bound raised to 0.35 % |
| mevBTC | 5 | 0.4 % | 19.312 % | 2026-02-13 to 2026-03-18, five weeks; the largest move |
The bound is a governance parameter changed by timelocked upgrades that never show in the feed's own transaction list, so Crossfoot reads it from archive state at the block, never from head. Rounds posted through a Safe are invisible there too: on the six oldest feeds, 215 such rounds came from AnswerUpdated logs, 28 of them over the bound.
--offline serves every read from the cache and fails on a miss, which proves a replay made no network call.
--verify-root is where cache/ and bundles/ live. Live cross-checks are ignored by default and need the network once.
# build and test (offline) cargo build --release cargo test cargo test -p crossfoot -- --ignored # live cross-checks # fetch, run, render crossfoot fetch svzchf --block <B1> [--baseline-block <B0>] crossfoot run svzchf --baseline-block <B0> --block <B1> crossfoot run mtbill --baseline-block <B0> --block <B1> crossfoot render --bundles bundles --out site