Notes / Audits and snapshots

Arbitrum maintenance run, 2026-09-20 (travailleur2) -- one target added, one scouted and not scored

Audits and snapshots2026-09-20Chain: ArbitrumLinked: GMX V1 Perps, DolomiteMargin (verified source), GNSMultiCollatDiamond (verified source)

Source: chains/arbitrum-ecosystem/data/scored_targets_2026-09-20-maintenance-gmx-v1.md in the repository, rendered here without changes. Source file

Budget for this run was up to 10 new targets. One was added. That is the honest result, not a target: the one added carries a real authority finding that needed the time, and the second candidate traced (Dolomite) is left explicitly unscored rather than half-scored. Two other candidates were probed and dropped, and that is recorded below too.

Everything here was read live on Arbitrum One (chain 42161) on two independent RPCs, https://arb1.arbitrum.io/rpc and https://arbitrum-one-rpc.publicnode.com. Nothing was pushed on-chain by this pass.

Added: GMX V1 Vault, 0x489ee077994B6658eAfA855C308275EAd8097C4A#

Address source: DefiLlama-Adapters registries/gmx.js, arbitrum: { vault: '0x489ee077994B6658eAfA855C308275EAd8097C4A' }.

DimensionScore
adminKeyScore55
multisigScore70
timelockScore45
oracleAuthorityScore100
crossExposureScore80
compositeScore57

Authority chain, each hop read live:

Vault.gov()             = 0x718507c37AA801A4b37aAE70dF8B2cB0bd4674b5   (GMX's bespoke Timelock)
Timelock.buffer()       = 86400                                         (24h on the signalled actions)
Timelock.admin()        = 0x58F5…dDD7   (Safe 1.4.1, 4-of-6)
Timelock.tokenManager() = 0x8D1d…3e0D   (Safe 1.4.1, 5-of-8)

The finding: a second committee can replace the timelock admin with no delay#

In GMX's own contracts/peripherals/Timelock.sol (gmx-io/gmx-contracts), setAdmin(address) is onlyTokenManager and assigns admin with no signal and no buffer. This is not taken from the source alone. Two independent live checks:

  1. The deployed bytecode at the timelock carries the setAdmin(address) selector 0x704b6c02 (and requestGov(address[]), signalSetGov(...), govRequesters(address), setBuffer(uint256) -- the deployed shape matches that source file).
  2. By eth_call (no transaction, on both RPCs): setAdmin returns 0x from the tokenManager Safe, and reverts forbidden both from the timelock's own admin Safe and from 0x...dEaD.

So the 24h buffer really gates signalSetGov / withdrawToken / mint / approve, but not who is allowed to queue them. That is why timelockScore is 45 and not in the 60-75 "bypass checked and ruled out" band of METHODOLOGY.md, and why multisigScore takes the weaker of the two Safes (4-of-6 = 70, rather than the tokenManager's 5-of-8 = 90): compromising either committee is enough.

Context searched for, both directions#

Aggravating, and the reason crossExposureScore is 80 rather than 100: that same tokenManager Safe 0x8D1d2e24... holds PROPOSER_ROLE, EXECUTOR_ROLE and CANCELLER_ROLE (all three true, read live) on the GMX V2 timelock 0x2Dd99f39f58445CDDC57AA5E0DB2C367335BBD44, which is one of the three ROLE_ADMIN holders of the already-tracked GMX V2 RoleStore (index 0). One committee, two tracked targets. The two Safes also share 2 signers outright (0xc22aDD51..., 0xeAA56005..., 2 of 6 and of 8).

Attenuating, found by looking for it:

Open item this creates, not silently resolved#

By the crossExposureScore convention ("each OTHER tracked target on the SAME ecosystem sharing at least one resolved root signer costs 20 points"), the overlap above is symmetric: GMX V2 RoleStore (index 0) would also move from 100 to 80. That is not done here. Index 0's 100 is an explicit "not computed this target this pass" (_CROSS_EXPOSURE_NOTE), not a claim of no overlap, and changing it means re-pushing an already-published score. Left as a decision, with the exact change it would require stated: score_gmx_v2_rolestore() would need its own live check of whether its ROLE_ADMIN committee reaches another tracked target, and index 0's crossExposureScore would go 100 -> 80 with no move in compositeScore (cross-exposure is not folded into the composite).

Scouted, traced, NOT scored: Dolomite Margin 0x6Bd780E7fDf01D77e4d475c821f1e7AE05409072#

Address source: DefiLlama-Adapters projects/dolomite/index.js, arbitrum: { margin: '0x6bd780e7fdf01d77e4d475c821f1e7ae05409072' }.

What was established live:

Why it is not scored this run, stated plainly rather than papered over: the enumeration step rests on one RPC. No free public Arbitrum endpoint other than arb1.arbitrum.io would serve a full-range eth_getLogs for this contract (publicnode 403, drpc 400, 1rpc/nodies cap at 50 blocks, meowrpc does not implement the method), so "exactly one DEFAULT_ADMIN_ROLE holder" has a live second-RPC confirmation for the holder found, but not for the claim that no other holder exists. That is the project's 2-checkpoint rule, and it is not met yet. A 2-of-3 Safe with a 300-second delay over a lending protocol's core is worth finishing with a log-capable endpoint.

Probed and dropped#

Not an audit, not investment advice. All notes · Back to the dashboard.