Task: P3 Wiring Completion — Per-Model Stake Enforcement, Dispute-Bond Treasury Forwarding, Network-Fee Floor (#37, #38 partial, #43, #78)
ID: task_100926_12
Assigned to: Robbert Abimbola (@robertocarlous)
Created: 2026-09-10
Status: Open (assigned)
Repo: https://github.com/InfiniteZeroFoundation/DevNet
Base branch: develop — spec written against commit ef41a0d (2026-09-10); re-pull before starting regardless of when you begin.
Roadmap ref: P3-5.1 (staking), P3-4.2/4.3 (slashing treasury routing), P3-5.3 (fees/treasury), P3 gas floor
Tracking: #37 (closes, pending review), #43 (closes, pending review), #78 (closes, pending review), #38 (partial — this task closes only its treasury-forwarding item; see task_100926_13 for the rest) · Discussion #137 (progress updates, questions, PR links)
This task and its sibling, task_100926_13.md, come out of a 2026-09-10 audit of ten open P3 issues (#36-43, #75, #78) — the full audit is appended at the bottom of both files, unchanged. The audit found the pending work clusters into two groups: things where the design decision is already made and only the contract wiring is missing (this task), and things that still need real slashing-economics/simulation work before or alongside the code (task_100926_13). Splitting on that line keeps this task fast and low-risk, and keeps the heavier judgment calls together in one place for review.
All three items here share a property: the DAO-settable storage or destination contract already exists on develop — nothing here is greenfield design, it's finishing wiring that a prior PR explicitly deferred as follow-up work.
Background: DinValidatorStake.sol already has modelMinStakeBounds[modelId] (a {min, max} struct, onlyOwner-settable via setModelStakeBounds) and maxConcurrentRegistrationsPerStakeUnit (onlyOwner-settable via setMaxConcurrentRegistrationsPerStakeUnit) — both landed in #65 as inert storage, explicitly flagged as follow-up in that PR's description and in Umer's reply to you on this issue (2026-07-25): "DINTaskCoordinator doesn't need to know its own modelId or look anything up yet; wiring that lookup (and solving the 'task contract doesn't know its registry ID' problem) is explicit follow-up work." This task is that follow-up.
a) Per-model stake floor. registerDINaggregator (DINTaskCoordinator.sol:286) and registerDINAuditor (DINTaskAuditor.sol:607) currently only check dinvalidatorStakeContract.isValidatorActive(msg.sender). Add a stake-floor check reading modelMinStakeBounds[modelId]:
- Give each task contract a way to know its own
modelId(an immutable set in the constructor is the simplest fix, since the model owner already knows the IDDINModelRegistry.registerModelreturned before deploying the task contracts — checkfoundry/script/for where task contracts get deployed and thread it through there). - Open question to resolve in your PR description, not silently: the design (
MECHANISM_DESIGN §3) describes the model owner setting their own per-model requirement, bounded by DAO min/max — but the only setter that exists today,setModelStakeBounds, isonlyOwneronDinValidatorStake(i.e., DAO-set, not model-owner-set), and there's no separate "chosen value within bounds" storage slot. The simplest correct wiring is to treatmodelMinStakeBounds[modelId].mindirectly as the effective per-model floor in the registration check (getStake(msg.sender) >= max(minStake(), modelMinStakeBounds[modelId].min)), leaving a model-owner-facing setter (bounded by.min/.max) as an explicit fast-follow rather than building it here. If you think it's small enough to include now, propose it — otherwise note the deferral explicitly. - When
modelMinStakeBounds[modelId]is unset (min == 0 && max == 0), behavior must be unchanged from today (no floor beyondminStake()) — this is the same "not yet configured" sentinel pattern #65 already uses elsewhere.
b) Concurrent-registration cap. This is harder because it's cross-contract: a validator's concurrent registrations span every model's DINTaskCoordinator/DINTaskAuditor, not just one. There's no shared counter today. Proposed approach — confirm or refine in your PR description:
- Add a per-validator counter on
DinValidatorStake(e.g.,activeRegistrationCount[address]), incremented/decremented by calls from task contracts. - Reuse the existing slasher-authorization trust boundary (
onlySlasherContract, wired viaDinCoordinator.setDINTaskCoordinatorAsSlasher/setDINTaskAuditorAsSlasher) rather than inventing a new allowlist — a task contract already authorized as a slasher is also trusted to report its own registrations. - Increment in
registerDINaggregator/registerDINAuditor; decrement when that GI's participation for the validator concludes (endGI, or wherever the equivalent lifecycle end is for aggregators/auditors — check both contracts, they may need different hook points). - Cap check:
activeRegistrationCount[msg.sender] < (getStake(msg.sender) / minStake()) * maxConcurrentRegistrationsPerStakeUnitis the shape implied by the parameter's name and MECHANISM_DESIGN §3's example ("one 10-DIN stake cannot back 20 tasks") — confirm the exact formula in your PR description since this isn't specified to the bit anywhere. - When
maxConcurrentRegistrationsPerStakeUnit == 0(unset), behavior must be unchanged (no cap).
Tests: round-trip + bounds-validation tests already exist from #65 for the storage itself — add enforcement tests: a validator below the model floor cannot register; a validator at the concurrent-registration cap cannot register for a further GI; existing registration behavior is unchanged when either value is left at its zero/unset default.
Background: Three TODO(task_210726_5): forward to DinTreasury stubs remain live in source, confirmed today:
DINTaskCoordinator.sol:131—treasuryAccrued(dispute-bond forfeiture accumulator)DINTaskCoordinator.sol:1067—resolveDispute's upheld-dispute bounty top-upDINTaskCoordinator.sol:1079—resolveDispute's frivolous-dispute forfeitureDINTaskAuditor.sol:58—treasuryAccrued(reward-settlement treasury bps share)
DinTreasury.sol has existed since #51 (merged 2026-08-07) and the dispute bond's DIN denomination was confirmed by #133 (merged 2026-09-10) — nothing is blocking this except that nobody's wired it yet. This closes both #38's dispute-payout-stub item and all of #43's remaining scope, since they're the same code path.
a) Frivolous-dispute forfeiture (DINTaskCoordinator.sol:1079) and DINTaskAuditor's reward-settlement treasury share (DINTaskAuditor.sol:58) — straightforward, mirror the existing pattern. DinValidatorStake.slash() already does exactly this split (DinValidatorStake.sol:238-245): burnAmount = actualAmount / 2, IBurnableToken(DIN_TOKEN).burn(burnAmount), then DIN_TOKEN.safeTransfer(slashTreasury, treasuryAmount). Both task contracts already hold the tokens in question (the bond, the reward pool's treasury share) directly — no cross-contract pull is needed, just burn+push using the same call shape. Keep treasuryAccrued as a running cumulative counter for observability (emit an event on each forward), but the actual tokens should move immediately rather than sit in the contract.
b) Upheld-dispute bounty top-up (DINTaskCoordinator.sol:1067) — not a simple push, flag this explicitly. Unlike (a), this needs DinTreasury to pay out to reward a correct disputer, and DinTreasury.withdrawETH/withdrawERC20 are both onlyOwner (DinTreasury.sol:43,55) — a task contract can't call either. This is a real access-control gap, not a wiring oversight. Options to weigh (pick one, or propose another, and say why in the PR description):
- Extend
DinTreasurywith an authorized-withdrawer role (mirroring the slasher-authorization pattern already used elsewhere) so registered task contracts can pull a bounty. - Fund the bounty from a different source entirely (e.g., a small carve-out from the GI reward pool, or the disputed party's own forfeited stake) rather than
DinTreasury. - Keep bounty as an off-chain/manual DAO action for now and just make the on-chain state (
disputeBondClaimablecredit) correct — i.e., treat the bounty top-up as genuinely out of scope for this task and say so. This sub-item touchesDinTreasury.sol's access control, which is shared platform infrastructure — flag it for Umer's review regardless of which option you pick, before merging.
Tests: conservation checks (forfeited bond amount always reaches burn+treasury, never stranded or double-counted); confirm SlashingInvariants.t.sol-style fuzz coverage extends cleanly to the new forwarding calls; a test exercising the upheld-bounty path with whichever option (b) resolves to.
Background: Developer/design/gas-simulation-network-fee.md is complete except one line it flags itself: commitAuditScore's gas cost (the commit half of commit-then-reveal, added by #63 after this doc's baseline) isn't priced, only revealAuditScore is. The recommended floor (275,916 gas × gas_price_ETH, auditor path) also isn't consumed anywhere in contracts/config yet.
- Extend
foundry/test/GasSimulation.t.sol(see its existingtest_gas_s2_auditScore_*tests,GasSimulation.t.sol:307-350) with acommitAuditScoremeasurement at the same LOW/MID/HIGH participation scale as the rest of the suite. - Update
Developer/design/gas-simulation-network-fee.md's recommended floor to include the commit-half cost, and mark its "Open items" caveat resolved. - Land the resulting number as an actual DAO-settable parameter (a
networkFee/feeFloor-style constant with a setter, matching the existing governable-parameter pattern fromMIN_STAKE/UNBONDING_PERIODin #65) somewhere it's actually read — confirm with Umer where that consumption point should be if it isn't obvious from the existing fee-router/model-registration flow.
Tests: new gas-measurement test passes and reports a number in the PR description; if a setter is added, a round-trip test for it (matching the setMinStake/setUnbondingPeriod test shape).
-
modelIdknown toDINTaskCoordinator/DINTaskAuditor; per-model stake floor enforced inregisterDINaggregator/registerDINAuditor - Concurrent-registration cap enforced via a new per-validator counter on
DinValidatorStake, gated by the existing slasher-authorization trust boundary - Both unset-default cases (
modelMinStakeBoundsunset,maxConcurrentRegistrationsPerStakeUnit == 0) leave registration behavior unchanged from today - Frivolous-dispute forfeiture and
DINTaskAuditor's reward-settlement treasury share both forward toDinTreasuryimmediately (burn/treasury split mirroringDinValidatorStake.slash()), not just accrue locally - Upheld-dispute bounty top-up: a resolved approach (new
DinTreasuryrole, alternate funding source, or explicit out-of-scope call), flagged for Umer's review given it touchesDinTreasury's access control -
commitAuditScoregas cost measured and folded into the network-fee-floor recommendation; the floor lands as a real DAO-settable contract parameter -
forge build && forge testgreen, full suite - PR description(s) reference #37, #38, #43, #78 as appropriate and note every open-question resolution called out above
Three largely-independent pieces of work touching different files — submit as separate PRs, one per issue closed (#37, then #43/#38's shared treasury-forwarding piece, then #78), rather than one large PR. This matches how #124-127 were handled and makes review tractable given (2)'s DinTreasury access-control question needs its own focused look.
- Flag any change to
DINTaskCoordinator.sol/DINTaskAuditor.sol/DinValidatorStake.sol/DinTreasury.solfor Umer's review given cryptoeconomic sensitivity, same as prior tasks in this area (task_210726_5, task_210726_6, task_100926_11). - Re-pull
developand re-runforge testright before opening each PR — this area of the codebase has had five merges in the last 48 hours (#110, #117, #129, #131, #133, #134). - If either open question in (1b) or (2b) turns out to be bigger than expected once you're in the code, say so early rather than guessing — both are flagged above as genuinely undecided, not just unwritten.
Scope of this audit: #36, #37, #38, #39, #40, #41, #42, #43, #75, #78 — all OPEN as of 2026-09-10 (before this task split #38 and closed #40).
Method: Read each issue's full body + comment history, cross-checked against git log/merged PRs since the 2026-09-08 status-check round, current develop source (foundry/src/*.sol), and a live forge test run of SlashingInvariants.t.sol.
Status-check comments posted 2026-09-10 (Sept 8 comment was stale on these four): #36, #38, #40, #41. #37/#39/#42/#43/#78's 2026-09-08 comments are still accurate — not re-posted. #75 already had a fresh 2026-09-10 comment (PR #77 merge) before this audit started.
Five PRs merged in that window that matter to this issue set:
| PR | Merged | Title | Issues affected |
|---|---|---|---|
| #110 | 2026-09-09 | Real encrypted test-data key distribution + staged dispute resolution (Closes #40) |
#40 (directly), #38 (adjacent — new test-data-specific dispute path, not the general one #38 tracks) |
| #116 | 2026-09-09 | import-deployments drops dinTreasury/dinFeeRouter (BL-8) |
none of these 10 directly |
| #117 | 2026-09-10 | Realign SlashingInvariants.t.sol with burn/treasury split |
#38 (clears its "3 failing tests" item — confirmed 13/13 passing via a fresh forge test run) |
| #129 | 2026-09-10 | Nonce collision fix (BL-1) | none of these 10 |
| #133 | 2026-09-10 | Dispute bond denomination → DIN (BL-12, docs-only) | #38, #36 (§9 item 7 denomination half) |
| #134 | 2026-09-10 | Claim-pull reward settlement, O(1) endGI (BL-10) |
#41, #36 |
| #131 | 2026-09-10 | Weighted aggregator reward settlement + docs/gas-benchmark (#126, #127) | #41, #36 — fixes a real stranded-funds bug in #134's interim isRewardableAggregator flat-split |
Confirmed by direct inspection (not just PR descriptions): foundry/src/DINTaskCoordinator.sol now has aggregatorWeight/totalAggregatorWeight mappings incremented per finalized batch, isRewardableAggregator is gone; DINTaskCoordinator.sol/DINTaskAuditor.sol still carry three TODO(task_210726_5): forward to DinTreasury stubs (PR #133 only touched a NatSpec comment, no functional change).
Verdict: stays open, umbrella issue. Not independently closable — closes when all seven children close. Real movement this round: #38 and #41 both cleared concrete items; #40 is one item away from closable. MECHANISM_DESIGN §9 open-decision count: 6 of 14 now open (was 7) — item 7 (dispute bond) split-resolved, denomination done, size/window still open.
Verdict: no change, stays open. No merged PR touched this since 2026-09-08. Outstanding, confirmed unchanged: per-model stake bounds (modelMinStakeBounds) and the concurrent-registration cap exist as storage on DinValidatorStake but are read nowhere in DINTaskCoordinator/DINTaskAuditor registration — zero enforcement; withdrawal queue still single-pending-only (accepted gap, not a blocker). Task candidate: wire the two existing-but-unenforced storage values into GI registration checks. Self-contained, contract-only, well-specified — good task_12/13 candidate. → task_100926_12, §1.
Verdict: stays open, but two items closed this round (see comment posted today). Confirmed via direct forge test that SlashingInvariants.t.sol is 13/13 passing (was 3 failing). Dispute-bond denomination resolved. Scope also now explicitly includes #40's leftover line (aggregator auditing / S4 recomputation dispute — folded in 2026-09-10, #40 closed as a result). Still outstanding and unchanged: S1/S2 flat-minStake() slash (not a DAO-settable fraction), S3 is fully wired but gated behind s3SlashingEnabled (default false) pending empirical threshold validation — an enablement decision, not missing code, S5/S6 spec-only (no code, need a per-validator counter), general dispute path (DINTaskCoordinator.openDispute/resolveDispute) still onlyOwner, no recomputation-based verification (the freshly-folded S4 gap), and its bond-forfeiture/bounty payout still stubbed (confirmed: treasuryAccrued + 2 more TODO(task_210726_5) comments live in source today). BL-11 (grindable redo entropy), BL-15 (resolved disputes can't reopen) also still open. Split across both tasks: treasury-forwarding piece → task_100926_12 §2; partial-slash tiers, S5/S6, and the S4 recomputation-dispute item → task_100926_13 §1-2.
Verdict: no change, stays open. No merged PR touched this since 2026-09-08. P3-SCR validation workstream is done (#62); scenario-driven redesign (separating eligibility from utility) hasn't started beyond a design doc (Developer/issues/scoring-mechanism.md) and a reference contribution_mode implementation that #62 explicitly scoped as not-production. Not a good near-term task candidate — this is a design-first item (redesign needs agreement before code), not a wiring/bugfix task like the others. Left out of both task_12 and task_13.
CLOSED 2026-09-10. PR #110 (Closes #40) merged 2026-09-09 — real encrypted-key scheme now on develop, matching the issue's own scope item verbatim. The one remaining scope line ("aggregator auditing: T1/T2 outputs verifiable by recomputation, S4 dispute") was architecturally #38's territory (DINTaskCoordinator's dispute path), not something specific to auditor evaluation — PR #110 never touched that contract. Per Umer's direction, folded that line explicitly into #38's Scope section (edited 2026-09-10) and closed #40 with a closing comment pointing to #38 for the rest. No standalone task_12/13 work — its only open line is now task_100926_13 §2.
Verdict: stays open, but the headline gap closed this round. BL-10 (unbounded settlement loop, this issue's own biggest flagged risk) is fully resolved via #134 + #131, including a real stranded-funds bug fix in the aggregator path found along the way (task_100926_11's scope). Remaining: BL-14 (rounding dust not swept to treasury — cosmetic) and the reward-split-percentage simulation (60/20/15/5 defaults were never validated against the P3-5.2 income-stability model this issue calls for). → task_100926_13, §3.
Verdict: no change, stays open. No merged PR touched this since 2026-09-08. Supply/burn/treasury mechanics are done; no emission contract exists in foundry/src/ at all — the "geometric decay per epoch" design decision in MECHANISM_DESIGN §9 has no corresponding code. Task candidate — the largest single gap in this whole set: a new emission contract (per-GI reward subsidy on a decay schedule) is greenfield work, not a fix to existing code. → task_100926_13, §4 (sequenced after §3's simulation, since the emission contract shouldn't be built before the split percentages it feeds are at least sketched).
Verdict: no change, stays open. #133 touched a fee-table doc line (dispute bond → DIN, cosmetic given #38 already covers the substance) but didn't touch this issue's real outstanding item. Confirmed still live in source: DINTaskCoordinator.treasuryAccrued and its two TODO(task_210726_5): forward to DinTreasury stubs — dispute-bond forwarding to DinTreasury still isn't wired despite the contract existing since #51 (merged 2026-08-07, over a month ago). No dynamic-fee base rate/multiplier code exists anywhere. → task_100926_12, §2 (same code path as #38's treasury-forwarding item).
Verdict: no change needed, already current. Comment from earlier today (2026-09-10, before this audit) already covers PR #77's merge. Long-running tracking issue by design — next slice (indexer-backed eligibility snapshot) is explicitly blocked on P4-IDX1/IDX2, out of scope for either task.
Verdict: no change, stays open, low urgency. No merged PR touched this since 2026-09-08. The simulation itself is complete and documented as such; what's missing is (a) pricing commitAuditScore gas (added by #63 after the simulation's baseline) and (b) actually wiring the recommended floor into a contract constant. → task_100926_12, §3.
- Closed 2026-09-10: #40 — one leftover line folded into #38's scope, then closed.
- Not closable yet, real work remains: #37, #38, #41, #42, #43, #78 — see task_100926_12/13 for the plan.
- Not closable, not a near-term task either: #39 (needs a design decision before code, not a wiring task) — left out of both tasks.
- No action, tracking issue by design: #75.