You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Three dormant/no-op mechanisms awaiting a product decision: disableModel kill-switch, rejectModel fee handling, S6 slashing (M-2, L-4, BL-31) #224
Three separate findings, batched because they share the same shape: each is a mechanism that exists in some form (an admin function, a fee flow, a slashing path) but nobody has made — or at least documented — the decision on whether/how it's actually meant to be used. None are exploitable bugs; all three just need someone with product/mechanism-design authority to decide, so the code (or the docs) can be made to match.
M-2: DINModelRegistry.disableModel() doesn't reach the live task contracts
modelDisabled[modelId] (DINModelRegistry.sol:119) only gates requestModelRegistration/requestManifestUpdate inside the registry itself (L151, L330). Confirmed by inspection against develop @ 740a613: DINTaskCoordinator.sol has zero references to DINModelRegistry, and DINTaskAuditor.sol's only two references are doc comments, not functional checks. A model the DIN-Representative has disabled — e.g., because its task contracts have a bug wrongfully slashing honest validators — keeps running full GIs completely unaffected. The task contracts' own doc table calls this a "kill-switch," which it currently isn't.
Decision needed: wire the task contracts to check DINModelRegistry.modelDisabled(modelId) before state-changing calls (real fix, requires threading the model ID + registry address into contracts that don't currently know about the registry at all), or explicitly document disableModel as registry-metadata-only so operators stop treating it as a circuit breaker.
L-4: rejectModel() never refunds the registration fee
Checked the full flow: feePaid is recorded at requestModelRegistration() (DINModelRegistry.sol:197) and is never refunded on either outcome — not on rejectModel() (L261-271) and not on approveModel() either (L217-258 doesn't touch feePaid at all). This consistency suggests the fee is intentionally a pay-to-apply cost regardless of outcome (anti-spam), not an oversight specific to rejection — but this was never written down anywhere as a deliberate choice.
Decision needed: confirm non-refundable-regardless-of-outcome is the intended design and document it (in DINModelRegistry.md and/or the dinrep role docs), so a future reader doesn't "fix" it into an inconsistent partial-refund state.
BL-31: S6 no-participation slashing is implemented, tested, but never called
DinValidatorStake.recordNoParticipation() (S6: escalating 10%-per-breach slash past a threshold, L333-354) is fully implemented and has its own tests. Every call site in both task contracts is a comment explaining why it's deliberately not called (DINTaskAuditor.sol:1391, DINTaskCoordinator.sol:1185) — slashPartial (S1/S2) already penalizes the same missed-work behavior, and calling both would double-penalize past MIN_STAKE. Confirmed: zero functional call sites anywhere in foundry/src/.
Decision needed: either S6 is meant to cover a gap S1/S2 don't (e.g., a validator registered but never assigned to any batch, so S1/S2 never trigger on them at all) and needs wiring to that specific case, or it's superseded dead code that should be removed along with its threshold parameter and event, per the slashing taxonomy (#38).
References
Developer/BACK_LOG.md BL-31
Documentation/technical/audits/foundry-src-security-review.md M-2, L-4 (confirmed still accurate against develop @ 740a613; several of that doc's other findings — M-3, M-4, L-1, L-5, L-7 — are already fixed but the doc was never updated to say so, a separate minor doc-hygiene item not covered by this issue)
Three separate findings, batched because they share the same shape: each is a mechanism that exists in some form (an admin function, a fee flow, a slashing path) but nobody has made — or at least documented — the decision on whether/how it's actually meant to be used. None are exploitable bugs; all three just need someone with product/mechanism-design authority to decide, so the code (or the docs) can be made to match.
M-2:
DINModelRegistry.disableModel()doesn't reach the live task contractsmodelDisabled[modelId](DINModelRegistry.sol:119) only gatesrequestModelRegistration/requestManifestUpdateinside the registry itself (L151, L330). Confirmed by inspection againstdevelop@740a613:DINTaskCoordinator.solhas zero references toDINModelRegistry, andDINTaskAuditor.sol's only two references are doc comments, not functional checks. A model the DIN-Representative has disabled — e.g., because its task contracts have a bug wrongfully slashing honest validators — keeps running full GIs completely unaffected. The task contracts' own doc table calls this a "kill-switch," which it currently isn't.Decision needed: wire the task contracts to check
DINModelRegistry.modelDisabled(modelId)before state-changing calls (real fix, requires threading the model ID + registry address into contracts that don't currently know about the registry at all), or explicitly documentdisableModelas registry-metadata-only so operators stop treating it as a circuit breaker.L-4:
rejectModel()never refunds the registration feeChecked the full flow:
feePaidis recorded atrequestModelRegistration()(DINModelRegistry.sol:197) and is never refunded on either outcome — not onrejectModel()(L261-271) and not onapproveModel()either (L217-258 doesn't touchfeePaidat all). This consistency suggests the fee is intentionally a pay-to-apply cost regardless of outcome (anti-spam), not an oversight specific to rejection — but this was never written down anywhere as a deliberate choice.Decision needed: confirm non-refundable-regardless-of-outcome is the intended design and document it (in
DINModelRegistry.mdand/or the dinrep role docs), so a future reader doesn't "fix" it into an inconsistent partial-refund state.BL-31: S6 no-participation slashing is implemented, tested, but never called
DinValidatorStake.recordNoParticipation()(S6: escalating 10%-per-breach slash past a threshold, L333-354) is fully implemented and has its own tests. Every call site in both task contracts is a comment explaining why it's deliberately not called (DINTaskAuditor.sol:1391,DINTaskCoordinator.sol:1185) —slashPartial(S1/S2) already penalizes the same missed-work behavior, and calling both would double-penalize pastMIN_STAKE. Confirmed: zero functional call sites anywhere infoundry/src/.Decision needed: either S6 is meant to cover a gap S1/S2 don't (e.g., a validator registered but never assigned to any batch, so S1/S2 never trigger on them at all) and needs wiring to that specific case, or it's superseded dead code that should be removed along with its threshold parameter and event, per the slashing taxonomy (#38).
References
Developer/BACK_LOG.mdBL-31Documentation/technical/audits/foundry-src-security-review.mdM-2, L-4 (confirmed still accurate againstdevelop@740a613; several of that doc's other findings — M-3, M-4, L-1, L-5, L-7 — are already fixed but the doc was never updated to say so, a separate minor doc-hygiene item not covered by this issue)dinrep deploy, fix add-slasher crash; refresh contract docs (#203) #204 review (BL-31's source)