Notes / Audits and snapshots
Source: data/rotation_audit_2026-09-19-robinhood-chain-index8.md in the repository, rendered here without changes. Source file
rotation_state.json -> robinhood-chain -> last_audited_target_index was 8 going into this run. Same discipline as every prior rotation audit in this project: trackedTargets(8) read live from the testnet oracle, then every authority fact re-derived from scratch against two independent RPCs (never re-read from scripts/lib/scorers.py's own cached logic), compared against getScore().
trackedTargets(8) = 0x00000000000014aA86C5d3c41765bb24e11bd701 -- the on-chain identity of the target scored as "Ekubo Core". trackedTargetsCount() = 50 going into this run.
| adminKeyScore | multisigScore | timelockScore | oracleAuthorityScore | crossExposureScore | compositeScore |
|---|---|---|---|---|---|
| 100 | 100 | 100 | 100 | 100 | 100 |
Two independent read RPCs against Robinhood Chain MAINNET (chain 4663, read-only): rpc.mainnet.chain.robinhood.com and robinhood.drpc.org. Raw eth_call/eth_getCode/eth_getStorageAt made directly with hand-computed selectors, not through score_ekubo_core().
| Check | RPC A result | RPC B result |
|---|---|---|
eth_getCode length | 18,797 bytes (real deployed code) | identical |
| EIP-1967 implementation/admin/beacon slots | all zero (not a proxy of any kind) | identical |
owner() (0x8da5cb5b) | reverts, empty data 0x | identical |
admin() (0xf851a440) | reverts, empty data 0x | identical |
governor() (0x0c340a24) | reverts, empty data 0x | identical |
0xdeadbeef (fake selector) | reverts, empty data 0x | identical |
swap_6269342730() (real function, hand-computed selector 0x00000000 -- Ekubo vanity-mines its selectors the same way it vanity-mined this contract's own leading-zero address) | reverts with decoded data 0x9cc3dd4a | identical |
0x9cc3dd4a independently computed (keccak256("InvalidSqrtRatioLimit()")[:4]) against every zero-argument error in ICore.sol -- matches InvalidSqrtRatioLimit() exactly, out of 10 candidate errors checked. This reproduces the project's own "differential test" claim (batch 5, data/scored_targets_2026-09-15-batch5.md) from scratch rather than trusting the docstring: a real function dispatches to real revert logic (non-empty, decodable error data) while owner()/admin()/ governor()/a garbage selector all hit the EVM's default "no matching function, no fallback" path (empty revert data) -- confirming the dispatcher routes correctly and the "authority" selectors genuinely don't exist as functions, not that they exist and revert for some unrelated reason.
Also independently re-fetched EkuboProtocol/evm-contracts' own src/interfaces/ICore.sol from GitHub and grepped it for owner|admin|upgrade| governor: zero matches in the 18-function ICore interface (only the routine mention of IFlashAccountant/IExposedStorage inheritance) -- confirms the project's batch-5 source-level claim directly rather than re-citing it.
crossExposureScore independently re-run via compute_cross_exposure() live (not read from a cache): 100 -- ekubo is its own signer-overlap group in scripts/lib/signer_overlap.py with empty known_eoa/safes (no authority surface to cross-reference, by design).
methodologyHash on-chain (0xf2606acd...4133b8975) independently recomputed as keccak256("authority-risk-oracle-v3") -- matches scripts/update_scores.py's current METHODOLOGY_VERSION exactly, not stale.
adminKeyScore/multisigScore/timelockScore are all 100 by architecture (no authority surface exists at all, not "not applicable"): _composite(100, 100, 100) = 100 -- matches the published compositeScore exactly. No divergence anywhere. No correction needed for this target; nothing pushed for index 8 itself.
Rather than re-running the full DefiLlama Robinhood Chain protocol sweep from scratch (176 protocols, already exhaustively worked through across batches 8-13 and index 7's own pass), this run started from re-checking whether batch 10's own "already covered" claims actually held up under this project's "never trust a hand-typed claim without independently re-deriving it" rule (the project's review rules) -- the same discipline this project's correction_*.md files exist to enforce.
data/scored_targets_2026-09-17-batch10.md states: "the Morpho vaults cover Morpho Blue/Steakhouse Financial", closing it out as a non-gap. Re-checked this directly rather than accepting it: by chain-specific TVL (api.llama.fi/protocols, chainTvls["Robinhood Chain"]), Morpho Blue is the single largest authority surface on this chain at ~$538M -- larger than every other tracked target's TVL combined. The claim conflates two distinct authority layers:
owner()/curator() governs that vault's fund allocation across markets -- already tracked individually (Steakhouse USDG, NetNet Credit, Purinta, Ethena x Steakhouse, Grove x Steakhouse, Steakhouse Turbo).owner() that can enable new IRMs/LLTVs (for future markets) and set the protocol fee + fee recipient for every market on the chain -- including Longbow's 55 isolated markets, which don't even go through a MetaMorpho/VaultV2 wrapper at all. No vault's own score says anything about who controls this. This was never actually traced on Robinhood Chain; batch 10's claim was wrong, not a genuine non-gap.Address 0x9D53d5E3bd5E8d4Cbfa6DB1ca238AEA02E651010 sourced from DefiLlama-Adapters/projects/morpho-blue/config.js (robinhood entry, fromBlock: 286) -- the same primary-source convention already used for every other DefiLlama-adapter-sourced target in this project (e.g. Meridian Perps, index 7). Confirmed live on 2 independent RPCs, all real deployed code:
| Address | Role | Bytecode size (both RPCs) |
|---|---|---|
0x9D53d5E3bd5E8d4Cbfa6DB1ca238AEA02E651010 | Morpho Blue singleton | 15,582 bytes |
0x0605…9728 | owner() | 171 bytes (Gnosis Safe proxy) |
owner() resolves identically on both RPCs to 0x0605…9728. feeRecipient() = zero address on both (no fee currently set -- the same "fee recipient currently zero" state independently found for Tempo's own not-yet-coded Morpho Blue scouting entry, chains/tempo/data/ scouted_targets_2026-09-17-run2.md, a consistent cross-chain Morpho deployment default, not a Robinhood-specific anomaly). owner() resolves to a real Gnosis Safe: 5-of-9, confirmed via live getOwners()/getThreshold() on both RPCs (identical owner lists). getModulesPaginated() returns empty on both RPCs; the Safe's guard storage slot (EIP-1967 guard slot) is zero on both -- no module, no guard.
Not a new one-off number: scored with the exact formula this project already established for this exact same protocol on Base (chains/base-ecosystem/scorers.py::score_morpho_blue, itself carrying the identical "owner() is scope-limited to future markets, immutable core" caveat) rather than reusing an unrelated family's formula (e.g. Arcus's threshold*25+5): adminKeyScore = 65 (threshold >= 3), multisigScore = min(100, threshold*15 + extra*5) = min(100, 75+20) = 95, timelockScore = 0 (no TimelockController or Safe guard/module anywhere in this chain). compositeScore = _composite(65, 95, 0) = 55.
crossExposureScore independently re-derived live via compute_cross_exposure() after wiring a new morpho_blue group into scripts/lib/signer_overlap.py (safes: ["0x0605…9728"]): 100 -- none of the 9 Safe owners appear in any other tracked signer-overlap group. Re-checked that this is the ONLY effect of wiring the new group in: spot-checked 7 pre-existing groups (ekubo, steakhouse, spark_savings, curve, meridian_perps, ramses, pendle) before and after -- every one unchanged (steakhouse still 40, matching the value already corrected in batch 13; everything else still 100).
New scorer score_morpho_blue_singleton() in scripts/lib/scorers.py, added to SIMPLE_SCORERS. New signer-overlap group morpho_blue in scripts/lib/signer_overlap.py. 4 new unit tests in scripts/lib/tests/test_robinhood_vault_and_infra_scorers.py (TestScoreMorphoBlueSingleton: the confirmed 5-of-9 case matching the Base sibling's formula exactly, a 2-of-3 case, an unresolved-Safe degraded case, and a multisig-score-cap regression test). Full suite: 461 tests pass (python3 -m unittest discover -s scripts/lib/tests).
python3 scripts/validate_all_scorers.py --ecosystem robinhood-chain was run but hit its own 300-second per-ecosystem timeout (a pre-existing tooling limitation for this specific slow ecosystem -- the same "two slow ones" gap the project's review rules already documents for the --skip robinhood-chain,tempo flag, not something this pass introduced). Not treated as a passing result and not substituted with a fabricated one -- the live 2-RPC re-derivation, the hand-verified expected tuple match immediately before sending (below), and the full unit-test suite are what this push's correctness actually rests on, same as index 7's own single-target push used the same reasoning to justify skipping the 50-minute full update_scores.py run.
Morpho Blue's score re-derived live immediately before sending (via score_morpho_blue_singleton() + a live compute_cross_exposure() call) and compared against the hand-derived expected tuple (65, 95, 0, 100, 100) -- matched (compositeScore computed as 55, not hand-typed), so the send proceeded. Pushed to the testnet oracle as a single-target updateScores() call (same rationale as index 7's Meridian Perps push):