Task: P3 Deploy Blocker + Open Security/dincli Findings — EIP-170 Size Gate (#201 A), onlyCurrentGI (#206), Sender-Bound Audit Commit (#192), Auditor Commit Retry (#202), Test-Data Dispute Drain (#205), Wiki DIN-Rep (#207)
ID: task_021026_19
Assigned to: Umer Majeed (@umermjd11)
Created: 2026-10-02
Status: Closed (completed 2026-10-05). All six Parts landed (PRs #211, #212, #214, #215; wiki revision on #207). Closed in Discussion #216
Dates: Oct 2 – Oct 12, 2026 (about 9 working days, per-Part estimates below). Parts A+B and C can start immediately. D and E are gated on earlier PRs merging (see §Sequencing). If those slip, say so early in the tracking discussion.
Repo: https://github.com/InfiniteZeroFoundation/DevNet
Base branch: develop — spec written against commit 84d8c38 (2026-10-02); re-pull before starting regardless of when you begin. foundry/, dincli/, tests/ and .github/ are unchanged from 6ccc28c, the commit the source plan was verified against.
Roadmap ref: P3-6.3a (gas/size, deploy blocker), P3-6.3b (audit preparation, open findings), P3-4.3 (dispute resolution), P3-SCR (auditor commit-reveal)
Source plan: task-plan-021026-1.md (reviewed and merged in PR #209; verification review, reviewer decisions)
Tracking:
- Parts A + B: #201 Part A (Part B of the issue stays open; see Out of scope) and #206 (closes, pending review). #201 is a hard blocker per the #201 decision comment.
- Part C: #192 (closes, pending review). Scope agreed in the #192 approval review (amendments 1–6).
- Part D: #202 (closes, pending review).
- Part E: #205 (closes, pending review). Design agreed in PR #209 Decision 3.
- Part F: #207 (closes once the wiki revision is linked).
- Discussion #216 (progress updates, questions, PR links)
None of the six is implemented on develop:
| Issue | State at 84d8c38 |
|---|---|
| #201 A | DINTaskCoordinator is 24,585 B runtime, 9 B over EIP-170. DINTaskAuditor is 22,567 B (margin 2,009 B). Both figures come from foundry/out/*.json deployedBytecode, via_ir, 200 runs. ci.yml runs plain forge build/forge test (:60-66) with no size check, and forge build --sizes leaves DINTaskAuditor out of its table. foundry/anvil.sh:6 lifts the code-size limit without saying why |
| #206 | registerDINaggregator(uint _GI) public (DINTaskCoordinator.sol:397) has no onlyCurrentGI (the modifier is at :246). The auditor side has it (DINTaskAuditor.sol:660) |
| #192 | revealAuditScore checks keccak256(abi.encodePacked(score, vote, salt)) (DINTaskAuditor.sol:1160). The formula is also in the NatSpec at :244, :1080, :1091. dincli uses Web3.solidity_keccak(["uint256","bool","bytes32"], …) (dincli/cli/auditor.py:534) |
| #202 | evaluate_lms makes a fresh salt for every LM on every run (auditor.py:533) and calls _save_commit unconditionally after the send (:550). aggregate_t2 reads bid from getTier2Batch (aggregator.py:525), then rebinds it in the T1 loop (:547) |
| #205 | openTestDataDispute (DINTaskAuditor.sol:1461) and resolveTestDataDispute (:1495) have no caller check. disputeBondAmount is 0 at deploy (:283). test_resolveDispute_upheld_returnsBondAndPenalises (EncryptedTestData.t.sol:319) encodes the attack as intended behaviour |
| #207 | The wiki DIN-Representative page still describes dincli dindao, three proxies, set-admin, withdraw, and a kill switch the task contracts don't read |
| Part | PR | Touches | Conflicts with | Rule |
|---|---|---|---|---|
| A | PR 1, commit 1 | DINTaskCoordinator.sol (getters, slashAggregators), .github/workflows/ci.yml + new .github/scripts/ gate, foundry/anvil.sh, Developer/CONTRIBUTING.md, dincli/cli/aggregator.py, dincli/cli/modelownerd/aggregation.py, dincli/abis/DINTaskCoordinator.json, tests/test_aggregator_commit_retry.py, coordinator foundry tests, DINTaskCoordinator.md |
B (same contract and byte budget), D (aggregator.py) |
Start immediately. |
| B | PR 1, commit 2 | DINTaskCoordinator.registerDINaggregator + tests |
A | Same PR as A. It can't merge alone: it would take develop to 24,594 B and fail the gate. |
| C | PR 2 | DINTaskAuditor.revealAuditScore + NatSpec, dincli/cli/auditor.py, the foundry tests that commit scores, DINTaskAuditor.md, DINShared.md |
D (auditor.py), E (DINTaskAuditor.sol, tests) |
Start immediately, in parallel with PR 1. It touches a different contract. |
| D | PR 3 | dincli/cli/auditor.py evaluate_lms, dincli/cli/aggregator.py aggregate_t2, new pytest files |
C (hash helper), A (aggregator.py getter switch) |
After PR 1 and PR 2 merge. Part D2 could go earlier on its own if A hasn't touched aggregate_t2. |
| E | PR 4 | DINTaskAuditor test-data dispute functions + default bond, EncryptedTestData.t.sol, DINTaskAuditor.md §13 |
C (same contract and test files) | After PR 2 merges, rebased. |
| F | — (wiki) | GitHub wiki only | — | Anytime. |
Rebase each PR on develop after the previous one merges, rather than stacking branches. Long-running drafts PR #31/#32 touch DINTaskAuditor.sol and dincli/cli/auditor.py; they rebase on these changes, not the other way round (#192 approval review, Conflicts).
Don't add a library, a second contract, or a deploy step. All three levers below were prototyped twice against 6ccc28c: once for the source plan, and once independently in the PR #209 review. Runtime bytes, via_ir, optimizer_runs = 200:
| Lever | Plan prototype | Review prototype |
|---|---|---|
A1a. Fold slashAggregators' duplicated T1/T2 loops (:1119-1195) into one internal _slashBatch(GI, batchId, aggregators, finalCID, minStakeAmt, s2Amount, bool t2) |
−471 B | −515 B |
A1b. Make the 10 per-aggregator getters internal and add one view (below) |
−355 B | −355 B |
A1c. Make the tier1Batches/tier2Batches auto-getters internal. getTier1Batch/getTier2Batch already cover them |
— | −214 B (both) |
| Combined | 23,542 B (margin 1,034 B) | 23,498 B (margin 1,078 B) |
| Combined + Part B | 23,551 B (margin 1,025 B) | 23,507 B (margin 1,069 B) |
Rejected (measured):
- folding T1/T2
commit…/reveal…into shared internals: +177 B, becausevia_irinlines them - folding
finalize…the same way: +166 B - lowering
optimizer_runsto 1: only −76 B
Don't retry these.
A1a — _slashBatch:
- Keep the reason codes as
bytes32:AGG_T1_NO_SUBMISSION,AGG_T2_NO_SUBMISSION,AGG_T1_BAD_CONSENSUS,AGG_T2_BAD_CONSENSUS. - Keep every
AggregatorSlashedevent argument, the S2 partial slash with itss2Amount > 0guard, and the full-minStakebad-consensus slash. - Keep the T1-then-T2 order.
- Behaviour must be identical, and the existing slashing tests must pass unchanged.
A1b — getAggregatorSubmission:
function getAggregatorSubmission(uint _GI, TierKind tier, uint _batchId, address aggregator)
external view
returns (bool committed, bytes32 commitHash, bool submitted, bytes32 cid, uint votes);- It replaces the public getters
t1/t2×SubmissionCID,Submitted,Votes,CommitHash,Committed. The storage stays as it is; only the getters go. votesis the vote count for that aggregator's own revealed CID (tNVotes[GI][batchId][cid]), and 0 before a reveal.t1Votes/t2Votesare keyed by CID (:66,85), so per-arbitrary-CID lookup goes away. Nothing calls it today (PR #209 Decision 4).- Callers to move:
- dincli:
dincli/cli/aggregator.py:213,265,322,532anddincli/cli/modelownerd/aggregation.py:109,154. These are the only Python callers. - pytest:
tests/test_aggregator_commit_retry.py:53, which mockst1Committed/t2Committedby name. - foundry:
AggregatorCommitReveal.t.sol:299,300,599,600(t1Submitted,t1SubmissionCID), plus any other readerforge buildflags.
- dincli:
- Subgraph: PR #29's mappings only
.bind()DINModelRegistryand never call these getters. Only its bundled ABI goes stale, so post a note on PR #29 with the ABI change. Don't push to that branch.
The margin after Part B is thin (1,025–1,069 B against a 1,024 B fail line). Measure these two extra candidates and report them in the PR even if you don't take them:
- fold
tier1FinalizedAt/tier2FinalizedAtinto a view.TreasuryForwarding.t.sol:275readstier1FinalizedAt - merge tier-duplicated custom errors (
TC_T1/T2EmptyCommitHash,…AlreadyCommitted,…NoCommitFound,…RevealHashMismatch) where the function already implies the tier
- A2. CI size gate. Add a step right after
forge buildin.github/workflows/ci.yml, backed by a script in.github/scripts/(next tocheck_doc_links.py). The script:- reads every
foundry/out/<File>.sol/<Contract>.jsonfor deployable contracts underfoundry/srcdirectly, not the--sizestable, which omitsDINTaskAuditor. Skip interfaces, libraries and abstract contracts (emptydeployedBytecode) - prints a table of contract, runtime size, margin and initcode size
- fails if any runtime margin is below 1,024 B, and warns (doesn't fail) below 2,048 B (PR #209 Decision 1). Both thresholds are named constants at the top of the script
- fails if any initcode exceeds EIP-3860's 49,152 B
- reads every
- A3.
foundry/anvil.sh: keep--code-size-limit, but add a comment saying it hides EIP-170 locally, and that the CI gate is what enforces the limit. - A4.
Developer/CONTRIBUTING.md: record both thresholds, how to run the gate locally, and that a PR touching either task contract must keep the gate green. - A5. Docs + ABI:
- update the state-variable and view tables in
Documentation/technical/contracts/DINTaskCoordinator.md - regenerate
dincli/abis/DINTaskCoordinator.jsonin the same PR (Decision 4)
- update the state-variable and view tables in
- A1a–c implemented, with no change to state-changing functions, events or storage layout
- A per-change and combined size table, from one build of the actual implementation, plus the two extra candidates' measured sizes
- The gate is in CI. The PR shows it failing on a deliberately oversized build and printing the 2,048 B warning
- dincli call sites and
tests/test_aggregator_commit_retry.pymoved.pytest -m "not integration"is green -
cd foundry && npm ci && forge build && forge testis green across the full suite, includingUpgradeValidation.t.sol -
anvil.shcomment,CONTRIBUTING.mdthresholds,DINTaskCoordinator.mdand the bundled ABI updated - ABI note posted on PR #29
Estimate: 3 days.
Ship it as a separate commit in PR 1, so the security fix can be reviewed and reverted on its own (PR #209 Decision 2).
- Add
onlyCurrentGI(_GI)toregisterDINaggregator(DINTaskCoordinator.sol:397), matchingregisterDINAuditor(DINTaskAuditor.sol:660). - Regression tests:
registerDINaggregator(GI + 1)andregisterDINaggregator(GI - 1)revert withTC_WrongGI- the current-GI path still registers and increments
activeRegistrationCount
- Cost: +9 B (measured). The gate must still pass with A + B together.
- Update
DINTaskCoordinator.md§10 if it lists this as a caveat.
- Modifier added, with the three tests above
- The gate is green, and the PR shows the final size with B included
Estimate: 0.5 day.
Implements amendments 1–6 of the #192 approval review:
- Contract:
revealAuditScorecheckskeccak256(abi.encode(score, vote, salt, msg.sender, gi, batchId, modelIndex))instead ofabi.encodePacked(score, vote, salt)(DINTaskAuditor.sol:1160).- Update the formula in the NatSpec at
:244,:1080(commitAuditScore@dev) and:1091. - Function signatures and the
bytes32 commitHashparameter don't change, so the bundled ABI stays the same. Confirm that.
- dincli:
- Add
_audit_commit_hash(score, vote, salt, sender, gi, batch_id, model_index)indincli/cli/auditor.py, modelled on_agg_commit_hash(dincli/cli/aggregator.py:65). - It uses
eth_abi.encode(["uint256","bool","bytes32","address","uint256","uint256","uint256"], …)+Web3.keccak, with the auditor's own account address. - Use it at
:534.reveal_lmssends the stored(score, vote, salt)and doesn't recompute the hash, so it's unchanged.
- Add
- Per-auditor hashing in every test that commits scores. Most tests compute one
commitHashbefore the auditor loop, which only works while the hash is sender-independent.- Put the formula in one shared test helper.
- Use it in
GasSimulation.t.sol,LifecycleEvents.t.sol,PR146SlashingRegression.t.sol,SecurityFindings.t.sol,StakingEnforcement.t.sol,TreasuryForwarding.t.sol,DisputeResolution.t.sol,RewardEngine.t.sol,AggregatorCommitReveal.t.sol(its auditor phase also callscommitAuditScore), and the_commitScorehelpers inAuditorCommitReveal.t.solandScoringValidation.t.sol. - Line numbers have moved since the approval review; find the call sites by grep.
- Regression tests in
AuditorCommitReveal.t.sol:- auditor B commits A's hash, then reveals A's
(score, vote, salt):TA_RevealHashMismatch - a hash built for model X doesn't reveal for model Y, or for another batch or GI
- the honest path still finalizes
- auditor B commits A's hash, then reveals A's
- Docs: update
Documentation/technical/contracts/DINTaskAuditor.md:123,199andDINShared.md:216,401(including theTA_RevealHashMismatchrow). - Size: report
DINTaskAuditorbefore and after in the PR, using the Part A gate once it has merged.
hardhat/contracts/DINTaskAuditor.sol is a stale mirror, so updating it is optional.
- Contract hash, NatSpec, dincli helper, the shared test helper and every affected test updated
- The three regression tests
- Docs updated. Bundled ABI confirmed unchanged
-
forge testandpytest -m "not integration"green.DINTaskAuditorsize reported
Estimate: 2 days (most of it is test churn).
Starts after PR 1 and PR 2 merge. It uses Part C's _audit_commit_hash, and Part A's getter switch in aggregator.py.
This is the auditor-side copy of PR #197's aggregator fix. Mirror aggregate_t1/aggregate_t2:
- Before evaluating an LM, read
hasCommittedLM(GI, batchId, account, modelIndex)(a public mapping,DINTaskAuditor.sol:257). If it's already committed, skip that LM and leave its commit cache untouched. - Write the commit cache before sending the commit tx.
build_and_send_tx(..., exit_on_failure=False)returnsNoneboth on a revert and on a failed receipt wait for a tx that still mines, so saving only on success can lose the preimage. - Tests, following
tests/test_aggregator_commit_retry.py:- an already-committed LM leaves its cache byte-identical and sends no tx
- on a failed send, the cache existed at send time and hashes, via
_audit_commit_hash, to exactly the commit that was sent
- Rename the inner T1 loop's unpacked variables (
:547), e.g.(t1_bid, _, _, _, t1_cid). ThenbidfromgetTier2Batch(:525) is what names the T2 models path, the worker job (aggregator_t2_gi_{GI}_batch_{bid}), the container, and the log lines. - Switch the
batch_idtruthiness checks tois not None, as inaggregate_t1. - Test: with
tier1BatchCount > 1, the job and container names passed towrite_worker_job/run_worker_containercarry the T2 batch id.
- D1 skip-if-committed and cache-before-send, with both tests
- D2 rename and
is not Nonechecks, with the naming test -
pytest -m "not integration"green
Estimate: 1 day.
Starts after PR 2 merges (same contract and test files).
- Only the model owner's reveal can resolve.
resolveTestDataDisputebecomes owner-only.- A reveal that matches the stored commitment clears the dispute, and the bond is forfeited as today.
- A reveal that doesn't match upholds it.
- Silence loses. If the owner doesn't reveal within
disputeWindowBlocks,closeExpiredDispute(:1540) upholds the dispute instead of forfeiting the bond: bond back,disputePenaltyBpspenalty via_burnAndForward, batch set topendingReassignment. - Only batch auditors can open.
openTestDataDisputerequiresisBatchAuditor(gi, batchId, msg.sender)(a public mapping,DINTaskAuditor.sol:230). - Non-zero default bond.
disputeBondAmountdefaults to100 * 1e18DIN, matchingDINTaskCoordinator.disputeBond(DINTaskCoordinator.sol:149).- It stays settable through the existing
setDisputeBondAmount(:1429). Revisit the value alongside #155's tokenomics values.
- Out of scope: option 3 (committing
keccak256(K)on its own) goes to #181/#38. The owner still judges disputes about their own test data, which is the trust assumption #181 tracks; say so in the NatSpec.
- a non-owner
resolveTestDataDisputereverts - an owner reveal with the true
Kand plaintext hash clears the dispute - the owner staying silent past the window upholds it via
closeExpiredDispute(bond returned, penalty applied, reassignment pending) - a non-batch-auditor
openTestDataDisputereverts - the deployed default
disputeBondAmount == 100 * 1e18 - rewrite
test_resolveDispute_upheld_returnsBondAndPenalisesandtest_resolveDispute_upheld_blocksFurtherOpen(EncryptedTestData.t.sol:319,341), which currently encode the attack - the #205 failure scenario: an outsider with junk
K, repeated after each reassignment, can no longer reducegiRewardPool
- Owner-only resolve, silence-upholds expiry, batch-auditor-only open,
100 * 1e18default bond - All tests above.
forge testgreen -
DINTaskAuditor.md§13 No. 1 caveat replaced with the new flow. Any dincli command calling these functions updated, with pytest green -
DINTaskAuditorsize before and after in the PR. The Part A gate must stay green; Parts C and E both grow this contract
Estimate: 2 days.
Rewrite the DIN-Representative wiki page against Documentation/public/roles/dinrep.md, as merged in PR #204. Cover every row of #207's table:
-
dincli dindao …→dincli dinrep …, with the current command names (approve-registration-request,reject-registration-request,approve-manifest-update,reject-manifest-update, …) - the command-reference link points to
roles/dinrep.md(roles/dindao.mddoesn't exist) - platform deploy: seven proxies (adding
DinTreasury,DinToken,DinFeeRouter,DinEmission), deployed byfoundry/script/DeployPlatform.s.sol, thendincli system import-deployments - fees:
DinCoordinator.withdraw()is gone. Faucet and registry ETH go toDinFeeRouterviasweep-fees - there's no
set-admin. Ownership istransferOwnershipon each contract - minting:
depositAndMint()andDinCoordinator.mintEmission()(callable only byDinEmission), both undermintCap. The owner setsdinPerEth,mintCapandretireFaucet - DIN-DAO governance is deferred to post-mainnet (DD-3)
- kill switch: the task contracts don't read
modelDisabled; disabling only blocks manifest updates
Keep the wiki conventions: blob/develop/... links, no assignee names or week-level dates. Link the wiki revision on #207.
Estimate: 0.5 day.
- #201 Part B (the slash reason for committed-but-unrevealed), #193, #194 and #180: each needs a mechanism decision or design note first.
- #181, #178: mainnet-grade.
- Everything else is listed in the source plan's Deferred table.
- Don't split either task contract to win bytes. If the Part A gate can't be met with the levers above plus the two extra candidates, stop and raise it in the tracking discussion.