The formulas behind the fields — opening the box deferred in L6. ~13 min.
L6 told you risk fields exist; L12 showed rules fire on them. Now we open the box: how is
admin_risk_usd = $8.4M actually computed? The answer is satisfyingly concrete — and it turns
on one core decision the system makes twice, two different ways: how do you combine many partial risks
into one number?
The engine computes two kinds of risk number, and they're combined differently because they mean different things:
| Quantity | Unit | Combined by | Why |
|---|---|---|---|
Extractable value (admin_risk_usd) | USD | max over channels | An attacker picks their best single attack — worst case = the most they could steal. |
Node risk (node_risk_score) | 0..1 | noisy-OR over channels | Independent risk factors compound — more weak spots = higher probability something goes wrong. |
Before the channels, the ingredient they all use. An admin role carries a severity — a minter that can inflate supply is catastrophic; a pauser is mild. Four tiers map to weights (pkg/types/role_severity.go):
These weights gate and scale the attack channels: a minter (critical, 1.0) is treated as controlling
100% of supply; a "rug pull" channel only counts admins with severity ≥ 0.7; the lending channel
needs ≥ 0.3. Severity is how the graph's ADMIN_CTRL edges (L2/L5) get a dollar teeth.
Source: pkg/types/role_severity.go (SeverityWeight: critical 1.0 / high 0.7 / moderate 0.3 / low 0.1), with role→severity registrations (DEFAULT_ADMIN_ROLE = critical, …) carefully kept at parity with the Python regexes.
admin_risk_usd answers a question you ask instinctively: "if this key turns evil, what's the
most it could steal of this token?" For each (focus_token, wallet) the computer evaluates
6 attack channels (pkg/risk/admin_risk.go, "matches extractable.py"):
| # | Channel | Extractable USD = |
|---|---|---|
| 1 | Direct holdings | HOLDS(token→wallet) USD — what it simply owns |
| 2 | Admin control | ADMIN_CTRL to token × severity; minter ⇒ 100% of supply |
| 3 | Pool drain | co-token minter in a shared pool |
| 4 | Rug pull | pool/vault admin with severity ≥ 0.7 |
| 5 | Oracle manipulation | oracle admin (mis-price → drain) |
| 6 | Lending control | lending-market admin with severity ≥ 0.3 |
node_risk_score is a 0..1 likelihood-flavoured score from component risks like
governance_risk and longevity_risk. Here the real code does not take a max
or a sum — it combines them as independent failure probabilities (node_risk_score.go):
This is noisy-OR: "the probability something is wrong = 1 − probability everything
is fine." It's monotonic (any factor ↑ pushes the score ↑), bounded in 0..1, and rewards being clean on
all axes. governance_risk itself blends admin-role severity + multisig threshold + key-rotation
history; longevity_risk penalises young / unverified / unaudited contracts. EOAs get
governance_risk = 0 — they are the key, not a thing controlled by one.
rule-fields.json) describes node_risk_score as "max across 6 channels."
The code is the noisy-OR product above over four terms — and right now audit and
firewall are hardcoded 0.5 placeholders. So today: composite = 1 − 0.25·(1−gov)·(1−lon).
This is exactly why this course's refrain is "verify against source — the code is the truth": the
human-facing doc is a simplification, and two channels are stubs awaiting implementation. A deep reader checks the formula, not the description.
vault_concentration asks the curator's question: is this vault's capital dangerously piled into
one strategy? It uses the Herfindahl-Hirschman Index over each strategy's USD share
(vault_concentration.go computeHHI):
If all capital sits in one strategy, every other share is 0 and HHI = 1²= 1 (max concentration);
spread evenly across n, each share is 1/n and HHI = n·(1/n)² = 1/n. The reciprocal
1/HHI is beautifully intuitive: the effective number of equally-sized strategies. HHI
0.5 → "effectively 2 strategies"; 0.1 → "effectively 10." One number, instantly readable.
Source: pkg/risk/vault_concentration.go (computeHHI = sum of squared shares; EffectiveStrategies = 1/HHI; top-strategies list capped at 20 for JSON, scalars cover all priced strategies).
Two structural facts tie this back to the architecture:
LoadPartialGraph: the token's admin / oracle / spine neighbours + non-dust holders) — which is
exactly the node set the rules engine reads
(address, portfolio_holders, portfolio_admins, top_n). The math is
computed where it will be queried. Anchored, bounded — L2's rules, again.node_risk_score.py /
extractable.py exactly." Recall L6's warning — a change here
must keep parity (the Graph-Parity dashboard, L11, watches it). The severity registrations even carry comments
noting where they were tuned to match the Python regexes to the percentage point.ADMIN_CTRL edges → run each focus token's neighbourhood through attack channels
(max for USD extractable) and risk factors (noisy-OR for 0..1 scores) plus HHI for
concentration → stamp the fields → the rules engine fires on them. Threat-model math over an anchored subgraph,
locked to Python parity.
admin_risk_usd the max over attack channels, not the sum?node_risk_score = 1 − (1−gov)(1−lon)(1−audit)(1−firewall) is which kind of combination?rule-fields.json calls node_risk_score "max across 6 channels," but the code is a 4-term noisy-OR with audit/firewall = 0.5 stubs. What's the lesson?HHI = 0.25 has roughly how many "effective" equally-sized strategies?LoadPartialGraph neighbourhood because…ADMIN_CTRL edges dollar teeth.admin_risk_usd is their max.node_risk_score noisy-OR formula — and spot the doc-vs-code discrepancy + the 0.5 stubs.1/HHI (effective strategy count), and explain partial-graph + parity constraints.Grounded in: pkg/risk/admin_risk.go (6 attack channels, max extractable, "matches extractable.py"),
pkg/risk/node_risk_score.go (noisy-OR composite, audit/firewall=0.5 stubs, EOA governance=0),
pkg/risk/vault_concentration.go (computeHHI = Σ share², EffectiveStrategies=1/HHI),
pkg/types/role_severity.go (severity weights 1.0/0.7/0.3/0.1), docs/rule-fields.json. Verify against source — the code is the truth.