From 2a9c546fb013557eddb6e5496092aada4ddecce6 Mon Sep 17 00:00:00 2001 From: santiagocetran Date: Thu, 29 Oct 2026 22:58:43 -0300 Subject: [PATCH 1/7] fix: prevent disabled models from starting new training rounds --- Documentation/public/roles/dinrep.md | 4 +- .../public/workflows/model-workflow.md | 9 +- .../audits/foundry-src-security-review.md | 6 +- .../technical/contracts/DINModelRegistry.md | 30 ++-- .../technical/contracts/DINShared.md | 5 + .../technical/contracts/DINTaskAuditor.md | 13 +- .../technical/contracts/DINTaskCoordinator.md | 6 +- .../technical/contracts/DinValidatorStake.md | 4 +- .../technical/mechanisms/staking-mechanism.md | 2 +- dincli/abis/DINTaskAuditor.json | 52 +++++++ dincli/abis/DINTaskCoordinator.json | 5 - dincli/cli/modelownerd/deploy.py | 63 +++++++- foundry/src/DINModelRegistry.sol | 3 +- foundry/src/DINShared.sol | 13 ++ foundry/src/DINTaskAuditor.sol | 26 +++- foundry/src/DINTaskCoordinator.sol | 5 +- foundry/test/AggregatorCommitReveal.t.sol | 3 +- foundry/test/AuditorCommitReveal.t.sol | 3 +- foundry/test/DeployPlatform.t.sol | 3 +- foundry/test/DisputeResolution.t.sol | 3 +- foundry/test/EncryptedTestData.t.sol | 3 +- foundry/test/GasSimulation.t.sol | 3 +- foundry/test/LifecycleEvents.t.sol | 3 +- foundry/test/PR146SlashingRegression.t.sol | 3 +- foundry/test/RewardEngine.t.sol | 146 +++++++++++++++++- foundry/test/ScoringValidation.t.sol | 3 +- foundry/test/SecurityFindings.t.sol | 9 +- foundry/test/StakingEnforcement.t.sol | 5 +- foundry/test/TreasuryForwarding.t.sol | 3 +- foundry/test/utils/EnabledModelRegistry.sol | 14 ++ tests/test_model_disable_deployment.py | 75 +++++++++ 31 files changed, 460 insertions(+), 65 deletions(-) create mode 100644 foundry/test/utils/EnabledModelRegistry.sol create mode 100644 tests/test_model_disable_deployment.py diff --git a/Documentation/public/roles/dinrep.md b/Documentation/public/roles/dinrep.md index 4e518960..dde46ca9 100644 --- a/Documentation/public/roles/dinrep.md +++ b/Documentation/public/roles/dinrep.md @@ -165,7 +165,7 @@ dincli dinrep registry approve-registration-request dincli dinrep registry reject-registration-request ``` -The registration fee is retained by the contract in both cases. +The required registration fee is a nonrefundable, pay-to-apply anti-spam cost: neither approval nor rejection refunds it. This is intentional (Abraham, [issue #224, 26 October 2026](https://github.com/InfiniteZeroFoundation/DevNet/issues/224#issuecomment-6406239550)). Any amount paid above the required fee is still refunded in the request transaction. --- @@ -203,7 +203,7 @@ dincli dinrep registry enable-model ``` > [!CAUTION] -> Disabling a model does not delete it. All on-chain history is preserved. Today the task contracts (`DINTaskCoordinator`, `DINTaskAuditor`) do **not** check `modelDisabled(modelId)`, so disabling only blocks manifest updates. It does not stop the model's running GIs, submissions or slashing. +> Disabling a model does not delete it. All on-chain history is preserved. With the current compatible Foundry task pair and canonical registry wiring, disabling blocks both new-GI start overloads, even when already funded, as well as manifest updates and emission funding. Registry approval is required before any GI starts. In-flight GIs, including slashing, settlement and claims, continue; this is not a GI abort. Existing deployed pairs are unchanged and require redeployment for the new guard. Review task code, pairing and canonical registry wiring before authorization/approval; immutable wiring alone does not establish trust. Reenable restores start eligibility. Pre-funded pools have no new refund or recovery path. --- diff --git a/Documentation/public/workflows/model-workflow.md b/Documentation/public/workflows/model-workflow.md index affdd3c0..4ef549f0 100644 --- a/Documentation/public/workflows/model-workflow.md +++ b/Documentation/public/workflows/model-workflow.md @@ -32,7 +32,14 @@ dincli system register-wallet --account > More on wallet configuration can be found in the [DIN CLI Documentation](../cli-reference.md). -In current DIN Protocol, the model owner needs to deploy a taskCoordinator contract and a taskAuditor contract specific to each model. +In current DIN Protocol, the model owner needs to deploy a taskCoordinator contract and a taskAuditor contract specific to each model. + +For current Foundry artifacts, pass `--model-id ` to both `model-owner deploy task-coordinator` and `model-owner deploy task-auditor`; their stake-floor IDs must match. Configure the canonical platform registry address in this network's `din_info.json` (`registry`) before deploying the auditor. Its fourth constructor argument is immutable registry wiring. The CLI supports explicit legacy constructor signatures too, but those older contracts do not gain the guard described below; unsupported custom constructor signatures are rejected. + +The DIN-Representative must approve the model before **any** GI can start. Both start overloads check the registry-assigned ID by auditor address; do not assume the anticipated constructor ID establishes registration. Coordinate ID allocation with the DIN-Representative for correct stake floors. + +Disabling a model prevents future GIs, including those with a pre-funded or privately funded pool. An in-flight GI continues through work, slashing, settlement and reward claims. There is no GI abort or new pool refund/recovery path. The guard requires a newly deployed compatible coordinator/auditor pair with canonical registry wiring and reviewed task code; existing deployments remain unchanged. See [kill-switch scope](../../technical/contracts/DINModelRegistry.md#11-kill-switch). + # Workflow diff --git a/Documentation/technical/audits/foundry-src-security-review.md b/Documentation/technical/audits/foundry-src-security-review.md index 6667a654..5d3d1cb4 100644 --- a/Documentation/technical/audits/foundry-src-security-review.md +++ b/Documentation/technical/audits/foundry-src-security-review.md @@ -165,7 +165,9 @@ This doesn't cause direct fund loss, but it undermines the core assumption that ### M-2. `DINModelRegistry.disableModel()` kill-switch doesn't reach the live task contracts -**Still open — tracked in [#224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224) / BL-34** (re-confirmed against `develop` @ `740a613`, 2026-10). +**Resolution (#224, Oct 26 decision):** the current Foundry coordinator calls auditor-side registry validation from both `startGI` overloads. Approved/enabled status is required before each new GI, including pre-funded pools. New compatible task pairs with reviewed canonical registry wiring are required; old deployments and in-flight GIs are unchanged. The historical scenario and broader recommendation below are retained as audit context; GI abort is outside this fix. + +**Original finding — tracked in [#224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224) / BL-34** (re-confirmed against `develop` @ `740a613`, 2026-10; superseded for new compatible pairs by the resolution above). **Contracts / functions:** `DINModelRegistry.sol`, `disableModel()`/`enableModel()` (L404-416); contrast with `DINTaskCoordinator.sol` and `DINTaskAuditor.sol`, neither of which references `DINModelRegistry` at all. @@ -212,7 +214,7 @@ This isn't exploitable, but it either indicates a missing feature (a `jail()` fu | L-1 | **Fixed** (current `stake()` updates `activeStake` before calling `DIN_TOKEN.safeTransferFrom` — correct checks-effects-interactions order). | `DinValidatorStake.sol` L113-126 | | L-2 | **Fixed — [PR #176](https://github.com/InfiniteZeroFoundation/DevNet/pull/176).** `depositAndMint()` has no minimum-deposit / non-zero-mint check. If the owner ever sets `dinPerEth` to a value that isn't a clean multiple of `1e18`, a small enough `msg.value` can round `mintAmount` to 0 via integer division while the ETH is still retained by the contract. *(Fix note: the zero-mint is only reachable when `dinPerEth < 1e18`, i.e. `msg.value * dinPerEth < 1e18` — any `dinPerEth >= 1e18` mints at least 1 wei for `msg.value >= 1`, clean multiple or not.)* | `DinCoordinator.sol` L92-101 | | L-3 | **Fixed — [PR #176](https://github.com/InfiniteZeroFoundation/DevNet/pull/176).** `requestModelRegistration` doesn't refund `msg.value` above the required fee — any overpayment is silently kept. *(Fix note: `requestManifestUpdate` had the same gap and is fixed the same way — `feePaid` records the required fee and the excess is refunded.)* | `DINModelRegistry.sol` L166-211 (`requestManifestUpdate` L281-320) | -| L-4 | `rejectModel()` never refunds the fee paid in the corresponding `requestModelRegistration` — a rejected requester loses their fee permanently. `approveModel()` doesn't touch `feePaid` either, so this looks like an intentional pay-to-apply design rather than an oversight, but it was never confirmed/documented as such. Tracked in [#224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224) / BL-35. | `DINModelRegistry.sol` L244-254 | +| L-4 | **Confirmed intentional — [#224, Oct 26 decision](https://github.com/InfiniteZeroFoundation/DevNet/issues/224#issuecomment-6406239550) / BL-35.** The required registration fee is a nonrefundable pay-to-apply anti-spam cost on both approval and rejection. Registry and DIN-Representative docs explain the rationale and distinguish excess-payment refunds. | `DINModelRegistry.sol` L244-254 | | L-5 | **Fixed/obsolete** — `withdrawFees` no longer exists in `DINModelRegistry.sol`; superseded by `setFees`/`sweepFeesToRouter` (commit `06190e8`, "scope DINModelRegistry fee path to ETH-only, accumulate-then-sweep"), neither of which has this gap. | ~~`DINModelRegistry.sol` L472-477~~ | | L-6 | **Fixed — [PR #176](https://github.com/InfiniteZeroFoundation/DevNet/pull/176).** `DINTaskCoordinator`/`DINTaskAuditor` constructors accept `dinvalidatorStakeContract_address` / `dintaskcoordinator_contract_address` with no zero-address check. Self-inflicted misconfiguration risk only (deployer controls the args), not attacker-triggered. | `DINTaskCoordinator.sol` L256-264 (+ `setDINTaskAuditorContract` L272-283), `DINTaskAuditor.sol` L399-424 | | L-7 | **Partly fixed** (task_210726_6 §3) — the dead-code half: `totalDepositedRewards` replaced by a real per-GI `giRewardPool` mapping ("funded per-GI, settled per-GI" model), and `RewardDeposited` is now actually emitted (`DINTaskAuditor.sol:487`). The stray-ETH half still applies as originally written: the contract still has no `receive()`/`fallback()` or any ETH withdrawal path, so ETH forced into it (e.g. via `selfdestruct`) is still permanently unrecoverable (re-verified against `develop`). | `DINTaskAuditor.sol` | diff --git a/Documentation/technical/contracts/DINModelRegistry.md b/Documentation/technical/contracts/DINModelRegistry.md index 34984224..4bd4c322 100644 --- a/Documentation/technical/contracts/DINModelRegistry.md +++ b/Documentation/technical/contracts/DINModelRegistry.md @@ -18,7 +18,7 @@ Core capabilities: - **Two-phase model registration** — submit a request, the DIN-Representative approves or rejects. - **Two-phase manifest updates** — same request/approval flow. -- **Kill switch** — the DIN-Representative can instantly disable any model. +- **Kill switch** — disabling blocks future GIs on compatible task pairs, manifest updates and emission funding. - **Dynamic fee governance** — all four fee parameters are adjustable by the DIN-Representative. Requests keep exactly the required fee; any overpayment is refunded to the caller. - **Fee routing** — accumulated ETH fees are swept to `DinFeeRouter` (`sweepFeesToRouter`), which splits them across validator pool / treasury / storage / public goods. - **Transferable admin** — the owner role can be handed to a multisig or timelock (`transferOwnership`). @@ -294,7 +294,7 @@ function requestModelRegistration( 8. **Emit** `ModelRegistrationRequested`. 9. **Refund:** if `msg.value > requiredFee`, send the overpayment back to `msg.sender` with a low-level `call` — revert `RefundFailed` if it fails. This runs last (checks-effects-interactions) and the function is `nonReentrant`. -> **Note:** The required fee is held in the contract regardless of whether the request is approved or rejected. +> **Fee policy:** The required registration fee is a nonrefundable, pay-to-apply anti-spam cost: neither approval nor rejection refunds it. This is intentional (Abraham, [issue #224, 26 October 2026](https://github.com/InfiniteZeroFoundation/DevNet/issues/224#issuecomment-6406239550)). Any amount paid above the required fee is still refunded in the request transaction. --- @@ -372,18 +372,14 @@ function disableModel(uint256 modelId) external onlyOwner function enableModel(uint256 modelId) external onlyOwner ``` -- Sets / clears `modelDisabled[modelId]`. -- `modelDisabled` is `public`, so downstream contracts *could* read it (`modelRegistry.modelDisabled(modelId)`), but today neither `DINTaskCoordinator` nor `DINTaskAuditor` does. -- Emits `ModelDisabled` / `ModelEnabled`. -- **Disabled ≠ Deleted.** History, ownership, and manifest are preserved for auditability. +- Sets / clears `modelDisabled[modelId]` and emits `ModelDisabled` / `ModelEnabled`. +- Preserves history, ownership and manifest. -What disabling actually does today: +For the current Foundry task pair, both `startGI(gi)` and `startGI(gi, score)` call the auditor's `validateGIStart(gi)` before changing the pass score, GI counter or state. The auditor requires an approved registry entry for its own address, checks that entry's `modelDisabled` flag, and requires a funded pool. It uses the registry-assigned ID, including ID zero, rather than the constructor's anticipated stake-floor ID. An unregistered model or failed registry read prevents starting a GI. Pre-funding or private funding cannot bypass a disabled flag; reenabling permits starting again if the other prerequisites hold. -| Effect | Enforced? | -|--------|-----------| -| Blocks `requestManifestUpdate` (`notDisabled` modifier) | ✅ | -| Blocks `approveManifestUpdate` for that model | ✅ | -| Stops GIs, LM submissions, aggregation, audits or slashing in the task contracts | ❌ — not checked by `DINTaskCoordinator` / `DINTaskAuditor` (§19 No. 6) | +Disabling blocks subsequent GI starts, manifest updates and emission funding. It does **not** abort or freeze an in-flight GI: submissions, audits, aggregation, slashing, settlement and reward claims continue. A start ordered before disable may succeed. Existing funds remain in their pools; this change adds no cancellation, refund or recovery path. + +Both task contracts are non-upgradeable. Existing deployed pairs keep their previous behavior and need a newly deployed compatible pair to gain this protection; there is no automatic retrofit. Deploy the new coordinator together with the new auditor wired to the canonical platform registry proxy. An old coordinator can bypass the new auditor guard, and a new coordinator cannot call this guard on an old auditor. Immutable wiring prevents later reassignment but does not prove the supplied registry is canonical. The DIN-Representative must review the task code, pairing and registry address before slasher authorization and approval; arbitrary custom or malicious task code is outside this enforcement guarantee. --- @@ -464,7 +460,7 @@ DINModelRegistry └── calls → DinFeeRouter.routeFeeETH{value}(this) [sweepFeesToRouter] ``` -No contract currently reads `modelDisabled`: `DINTaskCoordinator` and `DINTaskAuditor` do not check it, so the kill switch only blocks manifest updates in the registry itself (see §19 No. 6). +`DinEmission` checks `modelDisabled` before funding. Compatible task pairs check approval and disabled status through the auditor before starting a GI; see §11 for deployment and in-flight limitations. --- @@ -477,9 +473,9 @@ No contract currently reads `modelDisabled`: `DINTaskCoordinator` and `DINTaskAu | Slasher status revoked between request and approval | `isSlasherContract()` revalidated inside `approveModel()` | | Coordinator / auditor reused across models | `_modelIdByTaskCoordinator` / `_modelIdByTaskAuditor` uniqueness enforced at approval | | Silent manifest change by model owner | Manifest updates require DIN-Representative approval | -| Malicious approved model | Kill switch (`disableModel`) provides instant remediation | +| Malicious approved model | `disableModel` blocks future GIs on reviewed compatible task pairs; in-flight work continues | | Disabled model manifest still approved | `approveManifestUpdate` checks `modelDisabled` before writing | -| Fee spam on registration | Fee required at request time; retained on rejection | +| Fee spam on registration | Nonrefundable pay-to-apply anti-spam fee on approval and rejection | | Overpayment kept by the registry | Anything above the required fee is refunded in the same call; `feePaid` records only the required fee | | Re-entrancy through the refund call | The refund is the last step (checks-effects-interactions) and both request functions are `nonReentrant` (`ReentrancyGuardTransient`) | | Admin key compromise | `transferOwnership` enables migration to multisig / timelock | @@ -494,7 +490,7 @@ No contract currently reads `modelDisabled`: `DINTaskCoordinator` and `DINTaskAu - `taskCoordinator` and `taskAuditor` addresses are permanent after approval — no mechanism to update them. - Pending requests never expire — a stale request remains approvable indefinitely (a `uint256 expiresAt` field could address this). - Single admin key (the DIN-Representative) — no multi-sig quorum or on-chain voting yet (migrate via `transferOwnership`). -- The kill switch (`modelDisabled`) is not enforced by the task contracts (see §19 No. 6). +- The future-GI guard depends on compatible, reviewed task code wired to the canonical registry; it does not abort in-flight work (§11). --- @@ -532,4 +528,4 @@ Observations from the PR 13 review worth tracking: - **No. 3 — Single-step ownership transfer:** `transferOwnership` is one-step; a typoed address is unrecoverable. `Ownable2StepUpgradeable` would make handover safer. - **No. 4 — Repurposed error names:** `CoordinatorNoLongerSlasher` / `AuditorNoLongerSlasher` now also fire on first-time request validation, where "no longer" is a misnomer. Selector-stable but slightly misleading in traces. - **No. 5 — Admin revert selector changed:** unauthorized admin calls revert with `OwnableUnauthorizedAccount` instead of `NotDINDAOAdmin` — anything decoding revert reasons (tests, dincli error handling) must use the new selector. -- **No. 6 — Kill switch is not enforced downstream:** neither `DINTaskCoordinator` nor `DINTaskAuditor` reads `modelDisabled`, so disabling a model does not stop its GIs, submissions, or slashing — it only blocks manifest-update requests/approvals in the registry. +- **No. 6 — Future-GI guard implemented (#224):** compatible current task pairs reject new GIs for unregistered or disabled models. Existing deployments and in-flight work retain their behavior; abort remains outside this scope (§11). diff --git a/Documentation/technical/contracts/DINShared.md b/Documentation/technical/contracts/DINShared.md index 55e74c15..a0160e4e 100644 --- a/Documentation/technical/contracts/DINShared.md +++ b/Documentation/technical/contracts/DINShared.md @@ -403,3 +403,8 @@ The `TA_` and `TC_` prefixes make it immediately clear in stack traces and event ### Commit-Then-Reveal Auditor Scoring `LMSevaluationStarted` and `LMSevaluationRevealStarted` split what was previously a single evaluation phase into two: auditors first commit `keccak256(abi.encode(score, vote, salt, auditor, gi, batchId, modelIndex))` (hiding their vote from other auditors until everyone has committed), then, once the model owner closes the commit window via `DINTaskCoordinator.startLMsubmissionsEvaluationReveal`, reveal the underlying `(score, vote, salt)` for it to be counted. An auditor who commits but never reveals is simply excluded from quorum/median counting, and remains slashable via the existing "missed vote" check in `slashAuditors` — no separate non-reveal handling needed. + + +### Future-GI registry guard (issue #224) + +`ITaskModelRegistry` exposes `getModelIdByTaskAuditor(address) -> (bool exists, uint256 modelId)` and `modelDisabled(uint256) -> bool`. `IDINTaskAuditor.validateGIStart(uint256 gi)` is the coordinator-only view gate used by both coordinator start overloads. `TA_ModelNotRegistered()` and `TA_ModelDisabled(uint256 modelId)` describe registry failures; `TC_GIRewardPoolNotFunded()` retains the existing funding selector when raised by this auditor-side gate. See [auditor deployment and scope](DINTaskAuditor.md#future-gi-guard-and-deployment). diff --git a/Documentation/technical/contracts/DINTaskAuditor.md b/Documentation/technical/contracts/DINTaskAuditor.md index 1b6b5757..3d785431 100644 --- a/Documentation/technical/contracts/DINTaskAuditor.md +++ b/Documentation/technical/contracts/DINTaskAuditor.md @@ -43,6 +43,7 @@ Phase transitions are driven by the paired [`DINTaskCoordinator`](DINTaskCoordin |----------|-------------| | `dinvalidatorStakeContract` | `DinValidatorStake` proxy (constructor) | | `dintaskcoordinatorContract` | Paired coordinator (constructor) | +| `modelRegistry` (`immutable`) | Canonical platform registry proxy (fourth constructor argument; must contain code; no setter) | | `modelId` (`immutable`) | Registry model ID for the stake floor (constructor) | | `dinToken` | DIN token (`setDinToken`); required before `depositRewards`, claims or bonded disputes | @@ -92,7 +93,7 @@ Mappings marked *(internal)* have no auto-getter; read them through the view nam | Caller | Functions | |--------|-----------| -| Paired coordinator (`onlyTaskCoordinator`) | `updatePassScore`, `createAuditorsBatches`, `setTestDataAssignedFlag`, `finalizeEvaluation`, `slashAuditors`, `settleRewards`, `decrementAuditorRegistrations` | +| Paired coordinator (`onlyTaskCoordinator`) | `validateGIStart`, `updatePassScore`, `createAuditorsBatches`, `setTestDataAssignedFlag`, `finalizeEvaluation`, `slashAuditors`, `settleRewards`, `decrementAuditorRegistrations` | | `owner()` (model owner) | `assignAuditTestDataset`, `reassignAuditTestDataset`, all setters | | Assigned auditor (`onlyAssignedAuditor`) | `commitAuditScore`, `revealAuditScore` | | Auditor of the disputed batch (`isBatchAuditor`) | `openTestDataDispute` | @@ -101,6 +102,16 @@ Mappings marked *(internal)* have no auto-getter; read them through the view nam --- +### Future-GI guard and deployment + +Constructor: `DINTaskAuditor(stake, coordinator, modelId, modelRegistry)`. A zero/EOA registry reverts `TA_InvalidAddress`; a wrong contract or unavailable registry fails closed at GI start. `validateGIStart(gi)` is coordinator-only (`TA_NotTaskCoordinator`) and reverts `TA_ModelNotRegistered`, `TA_ModelDisabled(registeredId)` or the existing `TC_GIRewardPoolNotFunded` selector. The public `giRewardPool` getter remains an unconditional accounting read. + +For the current Foundry task pair, both `startGI(gi)` and `startGI(gi, score)` call the auditor's `validateGIStart(gi)` before changing the pass score, GI counter or state. The auditor requires an approved registry entry for its own address, checks that entry's `modelDisabled` flag, and requires a funded pool. It uses the registry-assigned ID, including ID zero, rather than the constructor's anticipated stake-floor ID. An unregistered model or failed registry read prevents starting a GI. Pre-funding or private funding cannot bypass a disabled flag; reenabling permits starting again if the other prerequisites hold. + +Disabling blocks subsequent GI starts, manifest updates and emission funding. It does **not** abort or freeze an in-flight GI: submissions, audits, aggregation, slashing, settlement and reward claims continue. A start ordered before disable may succeed. Existing funds remain in their pools; this change adds no cancellation, refund or recovery path. + +Both task contracts are non-upgradeable. Existing deployed pairs keep their previous behavior and need a newly deployed compatible pair to gain this protection; there is no automatic retrofit. Deploy the new coordinator together with the new auditor wired to the canonical platform registry proxy. An old coordinator can bypass the new auditor guard, and a new coordinator cannot call this guard on an old auditor. Immutable wiring prevents later reassignment but does not prove the supplied registry is canonical. The DIN-Representative must review the task code, pairing and registry address before slasher authorization and approval; arbitrary custom or malicious task code is outside this enforcement guarantee. + ## 5. Registration & Submissions - **`registerDINAuditor(gi)`** — requires the coordinator state `DINauditorsRegistrationStarted`. Checks: not already registered, not already an aggregator for the same GI (`TA_DualRoleNotAllowed`, read from the coordinator's `isDINAggregator`; issue No. 180), fewer than 300 registered (`TA_RegistrationCapReached`), `isValidatorActive` (`TA_AuditorNotActive`), stake ≥ the model floor (`TA_StakeBelowModelFloor`), and the concurrent-registration cap (`TA_ConcurrentRegistrationCapReached`). Then calls `incrementActiveRegistration` and emits `DINAuditorRegistered`. diff --git a/Documentation/technical/contracts/DINTaskCoordinator.md b/Documentation/technical/contracts/DINTaskCoordinator.md index 65487cf4..faaa2fcb 100644 --- a/Documentation/technical/contracts/DINTaskCoordinator.md +++ b/Documentation/technical/contracts/DINTaskCoordinator.md @@ -121,6 +121,8 @@ setDinToken(dinToken) (needed for dispute bonds) --- +Registry approval is required after setup and before the first GI. Both start overloads share the auditor's `validateGIStart(gi)` call after the state/index checks and before any mutation. This replaces the direct reward-pool lookup to preserve the coordinator's size budget. See [auditor wiring and future-GI guard](DINTaskAuditor.md#future-gi-guard-and-deployment) for required pair compatibility and registry trust. + ## 6. GI Lifecycle The numbers are `GIstates` ordinals (see [DINShared §2.1](DINShared.md)). @@ -152,7 +154,7 @@ Every owner transition after `startGI` takes the expected `_GI` and reverts `TC_ The three reveal-opening calls (`startLMsubmissionsEvaluationReveal`, `startT1AggregationReveal`, `startT2AggregationReveal`) are manual owner steps. Forgetting one stalls the GI in the commit window (finalize reverts) without corrupting state. ### 6.1 `startGI` -Requires `_GI == GI + 1` and a funded reward pool: `dinTaskAuditorContract.giRewardPool(_GI) > 0`, otherwise `TC_GIRewardPoolNotFunded`. The two-argument overload also calls `updatePassScore`. Then `GI++`. +Requires `_GI == GI + 1` and the genesis/GI-ended state, then calls `dinTaskAuditorContract.validateGIStart(_GI)`: registry approval, enabled status and a nonzero reward pool are required. The auditor preserves `TC_GIRewardPoolNotFunded` for an empty pool. Both overloads check before mutation; the two-argument overload then calls `updatePassScore`, and both increment `GI` and enter `GIstarted`. ### 6.2 `registerDINaggregator` In `DINaggregatorsRegistrationStarted` only, and `onlyCurrentGI`: `_GI` must be the current GI (`TC_WrongGI`), as for `DINTaskAuditor.registerDINAuditor` (issue #206). Checks in order: `isValidatorActive` (`TC_AggregatorNotActive`), not already registered, fewer than 300 registered (`TC_RegistrationCapReached`), stake ≥ `getModelStakeMin(modelId)` when non-zero (`TC_StakeBelowModelFloor`), and — when `maxConcurrentRegistrationsPerStakeUnit > 0` — `activeRegistrationCount < (stake / minStake) × cap` (`TC_ConcurrentRegistrationCapReached`). Then records the aggregator, calls `incrementActiveRegistration`, emits `DINValidatorRegistered`. @@ -256,7 +258,7 @@ DINTaskCoordinator │ getModelStakeMin, maxConcurrentRegistrationsPerStakeUnit, │ activeRegistrationCount, increment/decrementActiveRegistration, │ slashPartial (S2), slash (bad consensus, S4), slashTreasury - ├── DINTaskAuditor: giRewardPool, updatePassScore, createAuditorsBatches (with the audit seed), + ├── DINTaskAuditor: validateGIStart, updatePassScore, createAuditorsBatches (with the audit seed), │ setTestDataAssignedFlag, finalizeEvaluation, approvedModelIndexes, │ slashAuditors, settleRewards, decrementAuditorRegistrations └── DinToken: safeTransferFrom / safeTransfer / burn (dispute bonds) diff --git a/Documentation/technical/contracts/DinValidatorStake.md b/Documentation/technical/contracts/DinValidatorStake.md index 86a9428e..63363dc2 100644 --- a/Documentation/technical/contracts/DinValidatorStake.md +++ b/Documentation/technical/contracts/DinValidatorStake.md @@ -173,7 +173,7 @@ The per-slasher ring is namespaced by the **calling task contract** because `giI Increments `s6NoParticipationCount[validator]` and emits `S6NoParticipationRecorded`. Below `s6NoParticipationThreshold` it returns `0`. At or above it, it slashes `MIN_STAKE × (count − threshold + 1) / 10`, capped at `MIN_STAKE` (10% more per breach), and emits `S6PartialSlashFired`. The count never resets. -> No contract in `foundry/src` currently calls `recordNoParticipation` — the task contracts deliberately skip it where `slashPartial` already applies ("No S6 recordNoParticipation here…"), so S6 is implemented but not wired (§14 No. 1). +> **Parked for testnet** ([Abraham, 26 October 2026, #224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224#issuecomment-6406239550)): S1/S2 plus S5 already cover missed work. Retain S6 storage, parameters, API, events and tests without activating it; revisit after testnet only if a real gap appears, with a definition that avoids double penalties. No contract in `foundry/src` currently calls `recordNoParticipation` — the task contracts deliberately skip it where `slashPartial` already applies ("No S6 recordNoParticipation here…"), so S6 is implemented but not wired (§14 No. 1). ### 7.5 Jailing — `jailValidator(validator, duration, reason)` @@ -314,7 +314,7 @@ Until step 8, slasher management through the coordinator reverts `ValidatorStake ## 14. Review Notes & Open Caveats -- **No. 1 — S6 is not wired:** `recordNoParticipation` exists and is tested, but no task contract calls it, so the S6 counter never moves in practice. +- **No. 1 — S6 parked for testnet (intentional):** `recordNoParticipation` exists and is tested, but no task contract calls it, so the S6 counter never moves in practice. - **No. 2 — S5 escalation on a blacklisted validator reverts the whole slash:** escalation calls `_jailInternal`, which reverts `ValidatorIsBlacklisted`. A task contract's slashing loop hitting a blacklisted repeat offender would revert, not just skip that validator. - **No. 3 — Jail exit does not require `reactivate()`:** after `jailedUntil` passes, any status-syncing call (e.g. `stake`) recomputes the status, bypassing `reactivate()`'s `StakeBelowFloor` check (the later sync still requires `≥ MIN_STAKE` for `Active`). - **No. 4 — Stale NatSpec (partly fixed, issue No. 180):** the `setModelStakeBounds` / `setMaxConcurrentRegistrationsPerStakeUnit` NatSpec now says where the task contracts enforce both (§8), `activeRegistrationCount` names `releaseGIRegistrationSlots` as the decrement, and `getModelStakeMin` names the `onlyOwner` (DIN-Representative) setter instead of "the model owner". Still open: `modelMinStakeBounds[].max` is never read. diff --git a/Documentation/technical/mechanisms/staking-mechanism.md b/Documentation/technical/mechanisms/staking-mechanism.md index bf38f4c5..e79d125a 100644 --- a/Documentation/technical/mechanisms/staking-mechanism.md +++ b/Documentation/technical/mechanisms/staking-mechanism.md @@ -58,7 +58,7 @@ stateDiagram-v2 | Liveness faults (S1/S2) | `slashPartial`: an audit vote that was never revealed (S1) or a T1/T2 aggregation CID that was never revealed (S2) costs `minStake × s1/s2SlashFractionBps` (default 30%). Committing without revealing counts the same as not committing; whether it should cost more is open in issue #201 | | Recidivism (S5) | The same task contract recording `s5RecidivismThreshold` (3) partial slashes within `s5RecidivismWindow` (5) GIs escalates to a full `MIN_STAKE` slash plus a `s5JailDuration` (7 days) jail | | Full-severity faults | `slash(minStake)`: aggregator bad consensus, S3 audit-score deviation (behind `s3SlashingEnabled`, off by default), S4 invalid aggregation / fresh-subgroup timeout | -| No-participation (S6) | `recordNoParticipation` exists (escalating 10%-per-breach slash past a threshold of 3) but **no task contract calls it** | +| No-participation (S6) | `recordNoParticipation` exists (escalating 10%-per-breach slash past a threshold of 3) but **no task contract calls it**; parked for testnet per [#224, Oct 26 decision](https://github.com/InfiniteZeroFoundation/DevNet/issues/224#issuecomment-6406239550), because S1/S2 plus S5 cover missed work. Storage and parameters remain intact; revisit after testnet only for a demonstrated gap without double penalties | | Eligibility gates | Task contracts require `isValidatorActive()` at registration, commit/reveal and submission, and filter inactive registrants at batch creation. When configured they also enforce a per-model stake floor (`getModelStakeMin`) and a concurrent-registration cap (`(stake / minStake) × maxConcurrentRegistrationsPerStakeUnit`) | | Blacklist | `blacklistValidator`/`unblacklistValidator` are direct `owner()` (DIN-Representative) actions; blacklist freezes `stake`/`unstake`/`claimUnstaked` while funds remain slashable | diff --git a/dincli/abis/DINTaskAuditor.json b/dincli/abis/DINTaskAuditor.json index 2a6bb6c9..08c8aa75 100644 --- a/dincli/abis/DINTaskAuditor.json +++ b/dincli/abis/DINTaskAuditor.json @@ -17,6 +17,11 @@ "name": "modelId_", "type": "uint256", "internalType": "uint256" + }, + { + "name": "modelRegistry_", + "type": "address", + "internalType": "address" } ], "stateMutability": "nonpayable" @@ -977,6 +982,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "modelRegistry", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract ITaskModelRegistry" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "openTestDataDispute", @@ -1568,6 +1586,19 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "validateGIStart", + "inputs": [ + { + "name": "gi", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "view" + }, { "type": "event", "name": "AuditScoreCommitted", @@ -2543,6 +2574,22 @@ "name": "TA_MaxLMSubmissionsReached", "inputs": [] }, + { + "type": "error", + "name": "TA_ModelDisabled", + "inputs": [ + { + "name": "modelId", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "TA_ModelNotRegistered", + "inputs": [] + }, { "type": "error", "name": "TA_NoActiveDispute", @@ -2627,6 +2674,11 @@ "type": "error", "name": "TA_WrongGI", "inputs": [] + }, + { + "type": "error", + "name": "TC_GIRewardPoolNotFunded", + "inputs": [] } ] } \ No newline at end of file diff --git a/dincli/abis/DINTaskCoordinator.json b/dincli/abis/DINTaskCoordinator.json index 20af9c1e..e95468c7 100644 --- a/dincli/abis/DINTaskCoordinator.json +++ b/dincli/abis/DINTaskCoordinator.json @@ -2437,11 +2437,6 @@ "name": "TC_GICannotBeStarted", "inputs": [] }, - { - "type": "error", - "name": "TC_GIRewardPoolNotFunded", - "inputs": [] - }, { "type": "error", "name": "TC_GenesisModelHashCannotBeSet", diff --git a/dincli/cli/modelownerd/deploy.py b/dincli/cli/modelownerd/deploy.py index afb19960..2daedde1 100644 --- a/dincli/cli/modelownerd/deploy.py +++ b/dincli/cli/modelownerd/deploy.py @@ -6,13 +6,42 @@ from dincli.cli.utils import (build_and_send_tx, get_env_key, load_din_info, resolve_task_coordinator_address, set_env_key) + +def _task_constructor_args(contract, role, stake, *, coordinator=None, + model_id=None, registry=None): + """Support explicit released task signatures; refuse unknown custom shapes.""" + constructors = [item for item in contract.abi if item.get("type") == "constructor"] + signature = ( + tuple(item["type"] for item in constructors[0]["inputs"]) + if len(constructors) == 1 else () + ) + supported = { + "coordinator": {("address",), ("address", "uint256")}, + "auditor": {("address", "address"), ("address", "address", "uint256"), + ("address", "address", "uint256", "address")}, + } + if signature not in supported[role]: + raise typer.BadParameter(f"Unsupported {role} constructor signature: {signature}") + args = [stake] if role == "coordinator" else [stake, coordinator] + if "uint256" in signature: + if model_id is None or model_id < 0 or model_id >= 2**256: + raise typer.BadParameter("This artifact requires --model-id in the uint256 range") + args.append(model_id) + if role == "auditor" and len(signature) == 4: + if not registry: + raise typer.BadParameter("Configure the canonical platform registry for this network before deployment") + args.append(registry) + return args + + # Deploy sub-app (for 'dincli modelowner deploy ...') deploy_app = typer.Typer(help="Deploy task-level smart contracts") @deploy_app.command() def task_coordinator( ctx: typer.Context, - artifact_path: str = typer.Option(..., "--artifact", help="Path to DINTaskCoordinator contract artifact JSON (Hardhat format). "), + artifact_path: str = typer.Option(..., "--artifact", help="Task coordinator artifact JSON (Foundry or Hardhat)."), + model_id: int = typer.Option(None, "--model-id", help="Anticipated registry ID; required by current Foundry artifacts."), ): """ Deploy the DINTaskCoordinator contract. @@ -24,15 +53,19 @@ def task_coordinator( # Resolve DINValidatorStake address din_info = load_din_info() - if effective_network in din_info and "stake" in din_info[effective_network]: - din_validator_stake_address = din_info[effective_network]["stake"] + din_validator_stake_address = din_info.get(effective_network, {}).get("stake") + if not din_validator_stake_address: + raise typer.BadParameter("Configure the platform stake contract before deployment") console.print(f"[bold green]Deploying DINTaskCoordinator on network:[/bold green] {effective_network}") console.print(f"[cyan]Using DINValidatorStake:[/cyan] {din_validator_stake_address}") tx_receipt = build_and_send_tx( ctx, - DINTaskCoordinator_contract.constructor(din_validator_stake_address), + DINTaskCoordinator_contract.constructor(*_task_constructor_args( + DINTaskCoordinator_contract, "coordinator", din_validator_stake_address, + model_id=model_id, + )), "Deploying DINTaskCoordinator", "DINTaskCoordinator contract deployed successfully", "Failed to deploy DINTaskCoordinator" @@ -48,7 +81,8 @@ def task_coordinator( @deploy_app.command() def task_auditor( ctx: typer.Context, - artifact_path: str = typer.Option(..., "--artifact", help="Path to DINTaskAuditor contract artifact JSON (Hardhat format)."), + artifact_path: str = typer.Option(..., "--artifact", help="Task auditor artifact JSON (Foundry or Hardhat)."), + model_id: int = typer.Option(None, "--model-id", help="Anticipated registry ID; must match paired coordinator."), task_coordinator: str = typer.Option(None, "--taskCoordinator", help="DINTaskCoordinator contract address"), ): """ @@ -60,21 +94,34 @@ def task_auditor( DINTaskAuditor_contract = get_contract_instance(artifact_path, effective_network) din_info = load_din_info() - if effective_network in din_info and "stake" in din_info[effective_network]: - din_validator_stake_address = din_info[effective_network]["stake"] + din_validator_stake_address = din_info.get(effective_network, {}).get("stake") + if not din_validator_stake_address: + raise typer.BadParameter("Configure the platform stake contract before deployment") # Resolve DINTaskCoordinator address task_coordinator_address = resolve_task_coordinator_address( effective_network, task_coordinator, console ) + constructor_args = _task_constructor_args( + DINTaskAuditor_contract, "auditor", din_validator_stake_address, + coordinator=task_coordinator_address, model_id=model_id, + registry=din_info.get(effective_network, {}).get("registry"), + ) + if len(constructor_args) >= 3: + model_id_abi = [{"type": "function", "name": "modelId", "inputs": [], + "outputs": [{"type": "uint256"}], "stateMutability": "view"}] + paired = w3.eth.contract(address=task_coordinator_address, abi=model_id_abi) + if paired.functions.modelId().call() != model_id: + raise typer.BadParameter("--model-id must match the paired coordinator modelId()") + console.print(f"[bold green]Deploying DINTaskAuditor on network:[/bold green] {effective_network}") console.print(f"[cyan]Using DINValidatorStake address:[/cyan] {din_validator_stake_address}") console.print(f"[cyan]Using DINTaskCoordinator address:[/cyan] {task_coordinator_address}") tx_receipt = build_and_send_tx( ctx, - DINTaskAuditor_contract.constructor(din_validator_stake_address, task_coordinator_address), + DINTaskAuditor_contract.constructor(*constructor_args), "Deploying DINTaskAuditor", "DINTaskAuditor contract deployed successfully", "Failed to deploy DINTaskAuditor" diff --git a/foundry/src/DINModelRegistry.sol b/foundry/src/DINModelRegistry.sol index 0b0f1291..48e2f327 100644 --- a/foundry/src/DINModelRegistry.sol +++ b/foundry/src/DINModelRegistry.sol @@ -427,7 +427,8 @@ contract DINModelRegistry is Initializable, OwnableUpgradeable, ReentrancyGuardT return (true, val - 1); } - /// @notice Disables a model, blocking manifest updates and new GI registrations. + /// @notice Disables a model, blocking manifest updates and emission funding. + /// @dev Compatible task pairs also check this flag before new GIs; in-flight GIs continue. /// @param modelId ID of the model to disable. function disableModel(uint256 modelId) external onlyOwner { if (modelId >= models.length) revert InvalidModelId(); diff --git a/foundry/src/DINShared.sol b/foundry/src/DINShared.sol index a27f1e81..565633b3 100644 --- a/foundry/src/DINShared.sol +++ b/foundry/src/DINShared.sol @@ -126,7 +126,15 @@ interface IDINTaskCoordinator { ) external view returns (uint256); } +/// @notice Registry reads required by the task's future-GI kill switch. +interface ITaskModelRegistry { + function getModelIdByTaskAuditor(address auditor) external view returns (bool exists, uint256 modelId); + function modelDisabled(uint256 modelId) external view returns (bool); +} + interface IDINTaskAuditor { + function validateGIStart(uint256 gi) external view; + function createAuditorsBatches( uint _GI, bytes32 seed @@ -475,3 +483,8 @@ error TC_T2EmptyCommitHash(); error TC_T2NoCommitFound(); /// @dev The revealed (cid, salt) does not hash to the stored T2 commitment. error TC_T2RevealHashMismatch(); + +/// @notice This auditor has not been approved in its configured registry. +error TA_ModelNotRegistered(); +/// @notice The registry has disabled the model; no new GI may start. +error TA_ModelDisabled(uint256 modelId); diff --git a/foundry/src/DINTaskAuditor.sol b/foundry/src/DINTaskAuditor.sol index 342222a0..6e2b9037 100644 --- a/foundry/src/DINTaskAuditor.sol +++ b/foundry/src/DINTaskAuditor.sol @@ -18,6 +18,9 @@ interface IBurnableDinToken { contract DINTaskAuditor is Ownable, ReentrancyGuardTransient { using SafeERC20 for IERC20; + /// @notice Canonical platform registry, fixed at deployment; review wiring before approval. + ITaskModelRegistry public immutable modelRegistry; + IDinValidatorStake public dinvalidatorStakeContract; IDINTaskCoordinator public dintaskcoordinatorContract; @@ -403,14 +406,17 @@ contract DINTaskAuditor is Ownable, ReentrancyGuardTransient { /// @param _dinvalidatorStakeContract_address Address of the DinValidatorStake proxy. /// @param _dintaskcoordinator_contract_address Address of the paired DINTaskCoordinator. /// @param modelId_ Model registry ID for this deployment, used for per-model stake enforcement. + /// @param modelRegistry_ Canonical DINModelRegistry proxy; cannot be changed after deployment. constructor( address _dinvalidatorStakeContract_address, address _dintaskcoordinator_contract_address, - uint256 modelId_ + uint256 modelId_, + address modelRegistry_ ) Ownable(msg.sender) { if ( _dinvalidatorStakeContract_address == address(0) || - _dintaskcoordinator_contract_address == address(0) + _dintaskcoordinator_contract_address == address(0) || + modelRegistry_.code.length == 0 ) revert TA_InvalidAddress(); dinvalidatorStakeContract = IDinValidatorStake( _dinvalidatorStakeContract_address @@ -419,6 +425,7 @@ contract DINTaskAuditor is Ownable, ReentrancyGuardTransient { _dintaskcoordinator_contract_address ); modelId = modelId_; + modelRegistry = ITaskModelRegistry(modelRegistry_); params = Params({ auditorsPerBatch: 3, @@ -430,6 +437,19 @@ contract DINTaskAuditor is Ownable, ReentrancyGuardTransient { }); } + /// @notice Checks registry approval, enabled status and funding before a new GI. + /// @dev Only the paired coordinator calls this, from both startGI overloads, + /// before any mutation. Reverse lookup uses the approved ID rather than + /// the constructor's anticipated stake-floor ID. Registry failures revert + /// closed. In-flight phases, settlement and claims remain available. + /// @param gi Next GI whose reward pool must be funded. + function validateGIStart(uint256 gi) external view onlyTaskCoordinator { + (bool exists, uint256 registeredId) = modelRegistry.getModelIdByTaskAuditor(address(this)); + if (!exists) revert TA_ModelNotRegistered(); + if (modelRegistry.modelDisabled(registeredId)) revert TA_ModelDisabled(registeredId); + if (giRewardPool[gi] == 0) revert TC_GIRewardPoolNotFunded(); + } + /// @notice Wires the DIN token used for reward deposits and payouts. /// @dev onlyOwner, freely re-settable (mirrors DINModelRegistry.setDinToken /// / the existing setFees-style setters elsewhere in this codebase -- @@ -465,7 +485,7 @@ contract DINTaskAuditor is Ownable, ReentrancyGuardTransient { /// pool (typically the model owner, but not restricted to them -- /// matches the "market-set pool" framing in MECHANISM_DESIGN §5, /// nothing stops a third party topping up a pool they care about). - /// DINTaskCoordinator._startGI checks giRewardPool(_GI) > 0 as a + /// DINTaskCoordinator._startGI calls validateGIStart(_GI) as a /// precondition before that GI can start. /// /// `gi` must be the current GI or a future one: GI only ever moves diff --git a/foundry/src/DINTaskCoordinator.sol b/foundry/src/DINTaskCoordinator.sol index 709cd542..04af36aa 100644 --- a/foundry/src/DINTaskCoordinator.sol +++ b/foundry/src/DINTaskCoordinator.sol @@ -376,8 +376,9 @@ contract DINTaskCoordinator is Ownable, ReentrancyGuardTransient { // task_210726_6 §3: a GI cannot start unless its reward pool has // been funded via DINTaskAuditor.depositRewards(_GI, ...) first. // Prevents a GI running to completion with nothing to settle/claim. - if (dinTaskAuditorContract.giRewardPool(_GI) == 0) - revert TC_GIRewardPoolNotFunded(); + // The auditor also checks authoritative registry approval/enabled status. + // Keeping these reads there preserves this contract's runtime size budget. + dinTaskAuditorContract.validateGIStart(_GI); if (updatePassScore) { dinTaskAuditorContract.updatePassScore(score); } diff --git a/foundry/test/AggregatorCommitReveal.t.sol b/foundry/test/AggregatorCommitReveal.t.sol index dff98776..0a880bf8 100644 --- a/foundry/test/AggregatorCommitReveal.t.sol +++ b/foundry/test/AggregatorCommitReveal.t.sol @@ -19,6 +19,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import { GIstates, @@ -142,7 +143,7 @@ contract AggregatorCommitRevealTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); diff --git a/foundry/test/AuditorCommitReveal.t.sol b/foundry/test/AuditorCommitReveal.t.sol index d1963ce1..ce755f11 100644 --- a/foundry/test/AuditorCommitReveal.t.sol +++ b/foundry/test/AuditorCommitReveal.t.sol @@ -16,6 +16,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {GIstates, TA_RevealHashMismatch} from "../src/DINShared.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -123,7 +124,7 @@ contract AuditorCommitRevealTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); diff --git a/foundry/test/DeployPlatform.t.sol b/foundry/test/DeployPlatform.t.sol index c51b16c9..c4841cda 100644 --- a/foundry/test/DeployPlatform.t.sol +++ b/foundry/test/DeployPlatform.t.sol @@ -10,6 +10,7 @@ import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DinTreasury} from "../src/DinTreasury.sol"; import {DinFeeRouter} from "../src/DinFeeRouter.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; import {TA_InvalidSlashFraction, TC_InvalidSlashFraction} from "../src/DINShared.sol"; @@ -706,7 +707,7 @@ contract SlashFractionZeroBpsTest is Test { function setUp() public { // Deploy a stub stake contract address — the setter guards don't call it. tc = new DINTaskCoordinator(address(0x1), 1); - ta = new DINTaskAuditor(address(0x1), address(tc), 1); + ta = new DINTaskAuditor(address(0x1), address(tc), 1, address(new EnabledModelRegistry())); } function test_setS1SlashFractionBps_rejectsZero() public { diff --git a/foundry/test/DisputeResolution.t.sol b/foundry/test/DisputeResolution.t.sol index 97efb264..5c0f7bb1 100644 --- a/foundry/test/DisputeResolution.t.sol +++ b/foundry/test/DisputeResolution.t.sol @@ -18,6 +18,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import "../src/DINShared.sol" as Shared; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -129,7 +130,7 @@ contract DisputeResolutionTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); diff --git a/foundry/test/EncryptedTestData.t.sol b/foundry/test/EncryptedTestData.t.sol index 586537fd..d8b66139 100644 --- a/foundry/test/EncryptedTestData.t.sol +++ b/foundry/test/EncryptedTestData.t.sol @@ -30,6 +30,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {GIstates, TA_NoCommitmentStored, TA_DisputeAlreadyActive, TA_NoActiveDispute, TA_DisputeWindowClosed, TA_NotAssignedAuditor, TA_RewardsAlreadySettled} from "../src/DINShared.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -126,7 +127,7 @@ contract EncryptedTestDataTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); diff --git a/foundry/test/GasSimulation.t.sol b/foundry/test/GasSimulation.t.sol index 32b1c8c1..36940a4d 100644 --- a/foundry/test/GasSimulation.t.sol +++ b/foundry/test/GasSimulation.t.sol @@ -10,6 +10,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DinTreasury} from "../src/DinTreasury.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -79,7 +80,7 @@ contract GasSimulationTest is Test { // Task contracts tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); // Wire state machine: auditor → coordinator slasher → auditor slasher → genesis tc.setDINTaskAuditorContract(address(ta)); diff --git a/foundry/test/LifecycleEvents.t.sol b/foundry/test/LifecycleEvents.t.sol index 75f443f7..593a9615 100644 --- a/foundry/test/LifecycleEvents.t.sol +++ b/foundry/test/LifecycleEvents.t.sol @@ -21,6 +21,7 @@ import {DinToken} from "../src/DinToken.sol"; import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {GIstates} from "../src/DINShared.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -110,7 +111,7 @@ contract LifecycleEventsTest is Test { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); tc.setDinToken(address(token)); ta.setDinToken(address(token)); diff --git a/foundry/test/PR146SlashingRegression.t.sol b/foundry/test/PR146SlashingRegression.t.sol index dfb2c587..31d6ba42 100644 --- a/foundry/test/PR146SlashingRegression.t.sol +++ b/foundry/test/PR146SlashingRegression.t.sol @@ -20,6 +20,7 @@ import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DinTreasury} from "../src/DinTreasury.sol"; import {DinFeeRouter} from "../src/DinFeeRouter.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -82,7 +83,7 @@ contract PR146SlashingRegressionTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); diff --git a/foundry/test/RewardEngine.t.sol b/foundry/test/RewardEngine.t.sol index 572b98f9..892daaf3 100644 --- a/foundry/test/RewardEngine.t.sol +++ b/foundry/test/RewardEngine.t.sol @@ -18,7 +18,8 @@ import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; -import {GIstates} from "../src/DINShared.sol"; +import {GIstates, TA_ModelDisabled, TA_ModelNotRegistered, TA_InvalidAddress, + TA_NotTaskCoordinator, TC_GIRewardPoolNotFunded} from "../src/DINShared.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; contract RewardEngineTest is Test { @@ -36,6 +37,8 @@ contract RewardEngineTest is Test { DINTaskCoordinator tc; DINTaskAuditor ta; + bool skipRegistration; + bool disableDuringGI; address admin = makeAddr("admin"); address modelOwner = makeAddr("modelOwner"); @@ -126,7 +129,7 @@ contract RewardEngineTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(registry)); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); @@ -140,7 +143,17 @@ contract RewardEngineTest is Test { tc.setDINTaskAuditorAsSlasher(); tc.setGenesisModelIpfsHash(bytes32(uint256(1))); ta.setDinToken(address(token)); + if (skipRegistration) { + vm.stopPrank(); + return; + } + vm.deal(modelOwner, 1 ether); + uint256 requestId = registry.requestModelRegistration{value: registry.openSourceFee()}( + bytes32(uint256(1)), address(tc), address(ta), true + ); vm.stopPrank(); + vm.prank(admin); + registry.approveModel(requestId); } /// @dev Drives an entire honest GI from a funded pool through to @@ -168,6 +181,10 @@ contract RewardEngineTest is Test { vm.prank(modelOwner); tc.startGI(1); + if (disableDuringGI) { + vm.prank(admin); + registry.disableModel(0); + } vm.startPrank(modelOwner); tc.startDINaggregatorsRegistration(1); @@ -410,13 +427,136 @@ contract RewardEngineTest is Test { ta.depositRewards(1, 1 ether); } + // Issue #224: real registry integration, including model ID zero. The + // fixture's anticipated constructor ID is deliberately 1, not approved ID 0. + function _killSwitchFixture() internal { + _deployPlatform(); + _deployTaskPair(); + _fundDinBalance(modelOwner, 3 ether); + vm.prank(modelOwner); + ta.depositRewards(1, 1 ether); + } + + function test_disabledModel_blocksBothOverloadsAtomically_andReenable() public { + _killSwitchFixture(); + (bool exists, uint256 approvedId) = registry.getModelIdByTaskAuditor(address(ta)); + assertTrue(exists); + assertEq(approvedId, 0); + assertEq(ta.modelId(), 1); + vm.prank(admin); + registry.disableModel(0); + (, bytes memory beforeParams) = address(ta).staticcall(abi.encodeWithSignature("params()")); + vm.startPrank(modelOwner); + vm.expectRevert(abi.encodeWithSelector(TA_ModelDisabled.selector, 0)); + tc.startGI(1); + vm.expectRevert(abi.encodeWithSelector(TA_ModelDisabled.selector, 0)); + tc.startGI(1, 77); + vm.stopPrank(); + (, bytes memory afterParams) = address(ta).staticcall(abi.encodeWithSignature("params()")); + assertEq(afterParams, beforeParams); + assertEq(tc.GI(), 0); + assertEq(uint256(tc.GIstate()), uint256(GIstates.GenesisModelCreated)); + assertEq(ta.giRewardPool(1), 1 ether); + vm.prank(admin); + registry.enableModel(0); + vm.prank(modelOwner); + tc.startGI(1, 77); + assertEq(tc.GI(), 1); + } + + function test_disabledModel_blocksPrivateFundingAfterDisable() public { + _killSwitchFixture(); + vm.prank(admin); + registry.disableModel(0); + vm.startPrank(modelOwner); + ta.depositRewards(1, 1 ether); + vm.expectRevert(abi.encodeWithSelector(TA_ModelDisabled.selector, 0)); + tc.startGI(1); + vm.stopPrank(); + assertEq(ta.giRewardPool(1), 2 ether); + } + + function test_unregisteredModel_blocksBothOverloads() public { + _deployPlatform(); + skipRegistration = true; + _deployTaskPair(); + _fundDinBalance(modelOwner, 1 ether); + vm.startPrank(modelOwner); + ta.depositRewards(1, 1 ether); + vm.expectRevert(TA_ModelNotRegistered.selector); + tc.startGI(1); + vm.expectRevert(TA_ModelNotRegistered.selector); + tc.startGI(1, 77); + vm.stopPrank(); + assertEq(tc.GI(), 0); + } + + function test_registryReadFailure_failsClosed() public { + _killSwitchFixture(); + vm.mockCallRevert(address(registry), + abi.encodeWithSignature("getModelIdByTaskAuditor(address)", address(ta)), bytes("registry unavailable")); + vm.startPrank(modelOwner); + vm.expectRevert(bytes("registry unavailable")); + tc.startGI(1); + vm.expectRevert(bytes("registry unavailable")); + tc.startGI(1, 77); + vm.stopPrank(); + assertEq(tc.GI(), 0); + } + + function test_registryWiring_rejectsZeroAndEOA() public { + vm.expectRevert(TA_InvalidAddress.selector); + new DINTaskAuditor(address(1), address(2), 0, address(0)); + vm.expectRevert(TA_InvalidAddress.selector); + new DINTaskAuditor(address(1), address(2), 0, makeAddr("notRegistry")); + } + + function test_wrongRegistryContract_failsClosed() public { + _deployPlatform(); + DINTaskAuditor wrong = new DINTaskAuditor(address(stake), address(this), 0, address(token)); + vm.expectRevert(); // token has no registry selector + wrong.validateGIStart(1); + } + + function test_validateGIStart_onlyPairedCoordinator() public { + _killSwitchFixture(); + vm.expectRevert(TA_NotTaskCoordinator.selector); + ta.validateGIStart(1); + } + + function test_disabledInFlight_completesSettlesClaims_butCannotStartNext() public { + disableDuringGI = true; + _runFullHonestGI(1000 ether); + assertTrue(registry.modelDisabled(0)); + vm.prank(modelOwner); + tc.endGI(1); + vm.prank(client1); + ta.claimReward(1); + uint256 amount = ta.claimable(client1); + assertGt(amount, 0); + uint256 balance = token.balanceOf(client1); + vm.prank(client1); + ta.claimRewards(); + assertEq(token.balanceOf(client1), balance + amount); + _fundDinBalance(modelOwner, 1 ether); + vm.startPrank(modelOwner); + ta.depositRewards(2, 1 ether); + vm.expectRevert(abi.encodeWithSelector(TA_ModelDisabled.selector, 0)); + tc.startGI(2); + vm.expectRevert(abi.encodeWithSelector(TA_ModelDisabled.selector, 0)); + tc.startGI(2, 77); + vm.stopPrank(); + assertEq(tc.GI(), 1); + assertEq(uint256(tc.GIstate()), uint256(GIstates.GIended)); + } + function test_startGI_revertsWithoutFundedPool() public { _deployPlatform(); _deployTaskPair(); // No depositRewards call at all. vm.prank(modelOwner); - vm.expectRevert(); // TC_GIRewardPoolNotFunded + vm.expectRevert(TC_GIRewardPoolNotFunded.selector); tc.startGI(1); } diff --git a/foundry/test/ScoringValidation.t.sol b/foundry/test/ScoringValidation.t.sol index 1b4444d9..d00e0493 100644 --- a/foundry/test/ScoringValidation.t.sol +++ b/foundry/test/ScoringValidation.t.sol @@ -16,6 +16,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {GIstates} from "../src/DINShared.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -119,7 +120,7 @@ contract ScoringValidationTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); diff --git a/foundry/test/SecurityFindings.t.sol b/foundry/test/SecurityFindings.t.sol index a36626f1..3bc4e497 100644 --- a/foundry/test/SecurityFindings.t.sol +++ b/foundry/test/SecurityFindings.t.sol @@ -18,6 +18,7 @@ import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DinTreasury} from "../src/DinTreasury.sol"; import {DinFeeRouter} from "../src/DinFeeRouter.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import { GIstates, @@ -214,7 +215,7 @@ contract SecurityFindingsTest is Test { function _deployTaskPair() internal { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); vm.stopPrank(); @@ -621,14 +622,16 @@ contract SecurityFindingsTest is Test { function test_DINTaskAuditor_constructor_rejectsZeroStakeAddress() public { _deployPlatform(); DINTaskCoordinator tcLocal = new DINTaskCoordinator(address(stake), 1); + address enabledRegistry = address(new EnabledModelRegistry()); vm.expectRevert(TA_InvalidAddress.selector); - new DINTaskAuditor(address(0), address(tcLocal), 1); + new DINTaskAuditor(address(0), address(tcLocal), 1, enabledRegistry); } function test_DINTaskAuditor_constructor_rejectsZeroCoordinatorAddress() public { _deployPlatform(); + address enabledRegistry = address(new EnabledModelRegistry()); vm.expectRevert(TA_InvalidAddress.selector); - new DINTaskAuditor(address(stake), address(0), 1); + new DINTaskAuditor(address(stake), address(0), 1, enabledRegistry); } function test_setDINTaskAuditorContract_rejectsZeroAddress() public { diff --git a/foundry/test/StakingEnforcement.t.sol b/foundry/test/StakingEnforcement.t.sol index 38cf069d..2471a703 100644 --- a/foundry/test/StakingEnforcement.t.sol +++ b/foundry/test/StakingEnforcement.t.sol @@ -16,6 +16,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {GIstates} from "../src/DINShared.sol"; import { @@ -82,7 +83,7 @@ contract StakingEnforcementTest is Test { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), MODEL_ID); - ta = new DINTaskAuditor(address(stake), address(tc), MODEL_ID); + ta = new DINTaskAuditor(address(stake), address(tc), MODEL_ID, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); ta.setDinToken(address(token)); vm.stopPrank(); @@ -232,7 +233,7 @@ contract StakingEnforcementTest is Test { // Deploy a second coordinator for the same stake contract vm.startPrank(modelOwner); DINTaskCoordinator tc2 = new DINTaskCoordinator(address(stake), 2); - DINTaskAuditor ta2 = new DINTaskAuditor(address(stake), address(tc2), 2); + DINTaskAuditor ta2 = new DINTaskAuditor(address(stake), address(tc2), 2, address(new EnabledModelRegistry())); tc2.setDINTaskAuditorContract(address(ta2)); ta2.setDinToken(address(token)); vm.stopPrank(); diff --git a/foundry/test/TreasuryForwarding.t.sol b/foundry/test/TreasuryForwarding.t.sol index 1674341e..9845dedb 100644 --- a/foundry/test/TreasuryForwarding.t.sol +++ b/foundry/test/TreasuryForwarding.t.sol @@ -19,6 +19,7 @@ import {DinCoordinator} from "../src/DinCoordinator.sol"; import {DinValidatorStake} from "../src/DinValidatorStake.sol"; import {DINModelRegistry} from "../src/DINModelRegistry.sol"; import {DINTaskCoordinator} from "../src/DINTaskCoordinator.sol"; +import {EnabledModelRegistry} from "./utils/EnabledModelRegistry.sol"; import {DINTaskAuditor} from "../src/DINTaskAuditor.sol"; import {GIstates} from "../src/DINShared.sol"; import {auditCommitHash} from "./utils/AuditCommitHash.sol"; @@ -79,7 +80,7 @@ contract TreasuryForwardingTest is Test { vm.startPrank(modelOwner); tc = new DINTaskCoordinator(address(stake), 1); - ta = new DINTaskAuditor(address(stake), address(tc), 1); + ta = new DINTaskAuditor(address(stake), address(tc), 1, address(new EnabledModelRegistry())); tc.setDINTaskAuditorContract(address(ta)); tc.setDinToken(address(token)); ta.setDinToken(address(token)); diff --git a/foundry/test/utils/EnabledModelRegistry.sol b/foundry/test/utils/EnabledModelRegistry.sol new file mode 100644 index 00000000..3133a3f2 --- /dev/null +++ b/foundry/test/utils/EnabledModelRegistry.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +/// @dev Enabled/approved registry fixture for isolated task tests. Kill-switch +/// integration tests in RewardEngine use the actual registry proxy instead. +contract EnabledModelRegistry { + function getModelIdByTaskAuditor(address) external pure returns (bool, uint256) { + return (true, 0); + } + + function modelDisabled(uint256) external pure returns (bool) { + return false; + } +} diff --git a/tests/test_model_disable_deployment.py b/tests/test_model_disable_deployment.py new file mode 100644 index 00000000..4d076482 --- /dev/null +++ b/tests/test_model_disable_deployment.py @@ -0,0 +1,75 @@ +"""Local constructor compatibility checks; no chain or wallet used.""" +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest +import typer + +from dincli.cli.modelownerd import deploy + + +def artifact(*types): + return SimpleNamespace(abi=[{"type": "constructor", "inputs": [ + {"type": kind} for kind in types + ]}]) + + +@pytest.mark.parametrize("role,types,expected", [ + ("coordinator", ("address",), ["stake"]), + ("coordinator", ("address", "uint256"), ["stake", 0]), + ("auditor", ("address", "address"), ["stake", "coordinator"]), + ("auditor", ("address", "address", "uint256"), ["stake", "coordinator", 0]), + ("auditor", ("address", "address", "uint256", "address"), + ["stake", "coordinator", 0, "registry"]), +]) +def test_supported_constructor_shapes(role, types, expected): + assert deploy._task_constructor_args( + artifact(*types), role, "stake", coordinator="coordinator", + model_id=0, registry="registry", + ) == expected + + +@pytest.mark.parametrize("model_id", [None, -1, 2**256]) +def test_current_artifact_requires_valid_model_id(model_id): + with pytest.raises(typer.BadParameter, match="--model-id"): + deploy._task_constructor_args(artifact("address", "uint256"), + "coordinator", "stake", model_id=model_id) + + +def test_new_auditor_requires_configured_registry(): + with pytest.raises(typer.BadParameter, match="canonical platform registry"): + deploy._task_constructor_args(artifact("address", "address", "uint256", "address"), + "auditor", "stake", model_id=0) + + +@pytest.mark.parametrize("types", [(), ("uint256",), ("address", "address", "address")]) +def test_unknown_signature_rejected(types): + with pytest.raises(typer.BadParameter, match="Unsupported"): + deploy._task_constructor_args(artifact(*types), "auditor", "stake", model_id=0) + + +def test_auditor_command_supplies_canonical_registry_and_checks_pair(monkeypatch): + contract = Mock(abi=artifact("address", "address", "uint256", "address").abi) + paired = Mock() + paired.functions.modelId.return_value.call.return_value = 7 + w3 = Mock() + w3.eth.contract.return_value = paired + obj = Mock() + obj.get_en_w3_account_console.return_value = ("local", w3, Mock(), Mock()) + ctx = SimpleNamespace(obj=obj) + monkeypatch.setattr(deploy, "get_contract_instance", lambda *a: contract) + monkeypatch.setattr(deploy, "load_din_info", lambda: {"local": {"stake": "stake", "registry": "registry"}}) + monkeypatch.setattr(deploy, "resolve_task_coordinator_address", lambda *a: "coordinator") + send = Mock(return_value=SimpleNamespace(contractAddress="auditor")) + monkeypatch.setattr(deploy, "build_and_send_tx", send) + monkeypatch.setattr(deploy, "set_env_key", Mock()) + monkeypatch.setattr(deploy.time, "sleep", Mock()) + deploy.task_auditor(ctx, "artifact.json", 7, "coordinator") + contract.constructor.assert_called_once_with("stake", "coordinator", 7, "registry") + assert send.call_count == 2 + contract.constructor.reset_mock() + send.reset_mock() + with pytest.raises(typer.BadParameter, match="match the paired"): + deploy.task_auditor(ctx, "artifact.json", 8, "coordinator") + contract.constructor.assert_not_called() + send.assert_not_called() From dfc5bd290644335ffd883cecec923735d45fcf16 Mon Sep 17 00:00:00 2001 From: santiagocetran Date: Fri, 30 Oct 2026 10:30:26 -0300 Subject: [PATCH 2/7] fix(deploy): explain paired coordinator model ID read failures --- dincli/cli/modelownerd/deploy.py | 10 +- tests/test_model_disable_deployment.py | 133 ++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 13 deletions(-) diff --git a/dincli/cli/modelownerd/deploy.py b/dincli/cli/modelownerd/deploy.py index 2daedde1..48e2d574 100644 --- a/dincli/cli/modelownerd/deploy.py +++ b/dincli/cli/modelownerd/deploy.py @@ -1,6 +1,7 @@ import os import time import typer +from web3.exceptions import ContractLogicError from dincli.cli.contract_utils import get_contract_instance from dincli.cli.utils import (build_and_send_tx, get_env_key, load_din_info, @@ -112,7 +113,14 @@ def task_auditor( model_id_abi = [{"type": "function", "name": "modelId", "inputs": [], "outputs": [{"type": "uint256"}], "stateMutability": "view"}] paired = w3.eth.contract(address=task_coordinator_address, abi=model_id_abi) - if paired.functions.modelId().call() != model_id: + try: + paired_model_id = paired.functions.modelId().call() + except ContractLogicError as exc: + raise typer.BadParameter( + "Cannot read modelId() from the paired coordinator; verify the address " + "and that it is a compatible task coordinator." + ) from exc + if paired_model_id != model_id: raise typer.BadParameter("--model-id must match the paired coordinator modelId()") console.print(f"[bold green]Deploying DINTaskAuditor on network:[/bold green] {effective_network}") diff --git a/tests/test_model_disable_deployment.py b/tests/test_model_disable_deployment.py index 4d076482..4a69715e 100644 --- a/tests/test_model_disable_deployment.py +++ b/tests/test_model_disable_deployment.py @@ -4,6 +4,7 @@ import pytest import typer +from web3.exceptions import ContractLogicError from dincli.cli.modelownerd import deploy @@ -48,8 +49,9 @@ def test_unknown_signature_rejected(types): deploy._task_constructor_args(artifact(*types), "auditor", "stake", model_id=0) -def test_auditor_command_supplies_canonical_registry_and_checks_pair(monkeypatch): - contract = Mock(abi=artifact("address", "address", "uint256", "address").abi) +@pytest.fixture +def auditor_command(monkeypatch): + contract = Mock() paired = Mock() paired.functions.modelId.return_value.call.return_value = 7 w3 = Mock() @@ -62,14 +64,121 @@ def test_auditor_command_supplies_canonical_registry_and_checks_pair(monkeypatch monkeypatch.setattr(deploy, "resolve_task_coordinator_address", lambda *a: "coordinator") send = Mock(return_value=SimpleNamespace(contractAddress="auditor")) monkeypatch.setattr(deploy, "build_and_send_tx", send) - monkeypatch.setattr(deploy, "set_env_key", Mock()) - monkeypatch.setattr(deploy.time, "sleep", Mock()) - deploy.task_auditor(ctx, "artifact.json", 7, "coordinator") - contract.constructor.assert_called_once_with("stake", "coordinator", 7, "registry") - assert send.call_count == 2 - contract.constructor.reset_mock() - send.reset_mock() + write_env = Mock() + sleep = Mock() + monkeypatch.setattr(deploy, "set_env_key", write_env) + monkeypatch.setattr(deploy.time, "sleep", sleep) + return SimpleNamespace( + contract=contract, paired=paired, w3=w3, ctx=ctx, send=send, + write_env=write_env, sleep=sleep, + ) + + +def assert_no_deployment(command): + command.contract.constructor.assert_not_called() + command.send.assert_not_called() + command.write_env.assert_not_called() + command.ctx.obj.get_deployed_din_task_coordinator_contract.assert_not_called() + command.sleep.assert_not_called() + + +@pytest.mark.parametrize("with_registry", [False, True]) +def test_auditor_command_checks_pair_and_deploys(auditor_command, with_registry): + command = auditor_command + types = ("address", "address", "uint256") + (("address",) if with_registry else ()) + command.contract.abi = artifact(*types).abi + + deploy.task_auditor(command.ctx, "artifact.json", 7, "coordinator") + + expected = ["stake", "coordinator", 7] + (["registry"] if with_registry else []) + command.contract.constructor.assert_called_once_with(*expected) + command.paired.functions.modelId.assert_called_once_with() + command.paired.functions.modelId.return_value.call.assert_called_once_with() + assert command.w3.eth.contract.call_args.kwargs["address"] == "coordinator" + assert command.send.call_count == 2 + assert command.send.call_args_list[0].args[1] is command.contract.constructor.return_value + setter = command.ctx.obj.get_deployed_din_task_coordinator_contract.return_value + setter.functions.setDINTaskAuditorContract.assert_called_once_with("auditor") + assert command.send.call_args_list[1].args[1] is setter.functions.setDINTaskAuditorContract.return_value + command.write_env.assert_called_once_with( + "LOCAL_coordinator_DINTaskAuditor_Contract_Address", "auditor", + ) + command.sleep.assert_called_once_with(10) + + +@pytest.mark.parametrize("with_registry", [False, True]) +def test_auditor_command_rejects_model_id_mismatch(auditor_command, with_registry): + command = auditor_command + types = ("address", "address", "uint256") + (("address",) if with_registry else ()) + command.contract.abi = artifact(*types).abi + with pytest.raises(typer.BadParameter, match="match the paired"): - deploy.task_auditor(ctx, "artifact.json", 8, "coordinator") - contract.constructor.assert_not_called() - send.assert_not_called() + deploy.task_auditor(command.ctx, "artifact.json", 8, "coordinator") + + assert_no_deployment(command) + + +@pytest.mark.parametrize("with_registry", [False, True]) +def test_auditor_command_reports_model_id_revert(auditor_command, with_registry): + command = auditor_command + types = ("address", "address", "uint256") + (("address",) if with_registry else ()) + command.contract.abi = artifact(*types).abi + revert = ContractLogicError("execution reverted") + command.paired.functions.modelId.return_value.call.side_effect = revert + + with pytest.raises(typer.BadParameter) as caught: + deploy.task_auditor(command.ctx, "artifact.json", 7, "coordinator") + + assert str(caught.value) == ( + "Cannot read modelId() from the paired coordinator; verify the address " + "and that it is a compatible task coordinator." + ) + assert caught.value.__cause__ is revert + assert_no_deployment(command) + + +@pytest.mark.parametrize("with_registry", [False, True]) +@pytest.mark.parametrize("error_type", [ConnectionError, ValueError]) +def test_auditor_command_preserves_unrelated_read_errors(auditor_command, with_registry, error_type): + command = auditor_command + types = ("address", "address", "uint256") + (("address",) if with_registry else ()) + command.contract.abi = artifact(*types).abi + error = error_type("modelId read failed") + command.paired.functions.modelId.return_value.call.side_effect = error + + with pytest.raises(error_type) as caught: + deploy.task_auditor(command.ctx, "artifact.json", 7, "coordinator") + + assert caught.value is error + assert_no_deployment(command) + + +def test_auditor_command_does_not_wrap_contract_creation_error(auditor_command): + command = auditor_command + command.contract.abi = artifact("address", "address", "uint256").abi + error = ContractLogicError("contract creation failed") + command.w3.eth.contract.side_effect = error + + with pytest.raises(ContractLogicError) as caught: + deploy.task_auditor(command.ctx, "artifact.json", 7, "coordinator") + + assert caught.value is error + assert_no_deployment(command) + + +def test_legacy_auditor_deploys_without_model_id_read(auditor_command): + command = auditor_command + command.contract.abi = artifact("address", "address").abi + command.paired.functions.modelId.return_value.call.side_effect = AssertionError( + "legacy deployment must not read modelId" + ) + + deploy.task_auditor(command.ctx, "artifact.json", None, "coordinator") + + command.contract.constructor.assert_called_once_with("stake", "coordinator") + command.w3.eth.contract.assert_not_called() + command.paired.functions.modelId.assert_not_called() + assert command.send.call_count == 2 + command.write_env.assert_called_once_with( + "LOCAL_coordinator_DINTaskAuditor_Contract_Address", "auditor", + ) From 6970f5d2ebf99df78ff1806b2c430958a3f0307f Mon Sep 17 00:00:00 2001 From: Santiagocetran Date: Mon, 2 Nov 2026 14:03:59 +0000 Subject: [PATCH 3/7] docs: mark BL-34 resolved by model-disable guard --- Developer/BACK_LOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Developer/BACK_LOG.md b/Developer/BACK_LOG.md index 9579aae6..4ff809ef 100644 --- a/Developer/BACK_LOG.md +++ b/Developer/BACK_LOG.md @@ -40,5 +40,5 @@ | BL-31 | Solidity/foundry (slashing) | S6 no-participation slashing is implemented but not wired | `DinValidatorStake.recordNoParticipation` (escalating 10%-per-breach slash past `s6NoParticipationThreshold`) exists and is tested, but no task contract calls it. They deliberately skip it where `slashPartial` (S1/S2, with S5 escalation) already penalises the same missed work, to avoid stacking past `MIN_STAKE`. So S6 never fires. Decide whether S6 should cover something S1/S2 don't (e.g. registered but never assigned or never active), or whether to remove it and its parameters. | P3 | PR No. 204 review (`DinValidatorStake.md` §14 No. 1), Sep 30, 2026; slashing taxonomy S6 (issue #38) | ⏸️ Parked for testnet — Abraham (Oct 26, [#224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224)): S1/S2 plus S5 already cover missed work; leave S6 unwired (or remove it and its parameters) and revisit after testnet. Needs a removal PR or a "parked" note in the docs | | BL-32 | Tooling / onboarding | Contributor environment preflight check | No single command checks a dev machine before work starts. Each of these has already cost time: (1) Python ≥ 3.12; (2) `forge`/`anvil` installed and solc 0.8.28 available; (3) `foundry/node_modules` present — without `npm ci`, `forge test`'s parallel tests race to fetch `@openzeppelin/upgrades-core` over npx (see [CLAUDE.md](../CLAUDE.md)); (4) `eth-account` and `py-multibase` import; (5) `torch` imports in the active venv (full `pytest` run); (6) `.env`/`.env.` exist, the RPC endpoint answers, and `resolve_ipfs_config()` resolves. | P3/P4 (onboarding; small, ~half a day) | `preflight.sh` pattern from [Osomudeya/devops-scripting-labs](https://github.com/Osomudeya/devops-scripting-labs) (reviewed 2026-10-02); no existing equivalent (note: the only "preflight" in `dincli/cli/system.py` is the bridge's tx preflight — unrelated) | 🟡 Mostly fixed — [PR #255](https://github.com/InfiniteZeroFoundation/DevNet/pull/255) added `scripts/preflight.py` (Python/forge/anvil/node_modules/package checks, `--network` RPC/IPFS checks, 13 tests in `tests/test_preflight.py`, a preflight section in `Documentation/public/getting-started.md`). Remaining (exit non-zero on `node_modules`, eth-account/py-multibase, `--network` RPC/IPFS failures) tracked in [#245](https://github.com/InfiniteZeroFoundation/DevNet/issues/245) | | BL-33 | Tooling / deploy | On-chain deployment drift check | Nothing verifies that `foundry/deployments/.json` (and the `din_info` that `dincli system import-deployments` writes) still matches the chain. Proposal: a read-only check, either a forge script or `dincli system verify-deployment`, that for each recorded contract compares (1) the proxy's EIP-1967 implementation and admin slots against the recorded implementation/`proxyAdmin*` addresses; (2) the deployed runtime code hash against the current `forge build` artifact (immutables masked); (3) owner / DIN-Representative addresses; (4) wiring: `DinCoordinator` ↔ `DinToken`/`DinValidatorStake`/`DinModelRegistry`/fee router/treasury links and slasher authorizations on `DinValidatorStake`. Report per-item `OK` / `DRIFT` with expected vs. actual. Caveat: it only covers what the deployments JSON records, so a clean run doesn't prove nothing else was deployed or authorized. | P3 (before the DevNet 3.0 deploy; re-run after every `UpgradePlatform.s.sol` run) | Terraform state-vs-live drift pattern from [Osomudeya/devops-scripting-labs](https://github.com/Osomudeya/devops-scripting-labs) `03-drift-detection` (reviewed 2026-10-02); no existing code reads EIP-1967 slots or code hashes in `dincli/` or `foundry/script/` | 💭 Idea | -| BL-34 | Solidity/foundry (task contracts) | `DINModelRegistry.disableModel()` kill-switch doesn't reach the live task contracts | `modelDisabled[modelId]` only gates `requestModelRegistration`/`requestManifestUpdate` inside the registry itself. `DINTaskCoordinator`/`DINTaskAuditor` have no dependency on, or awareness of, the registry (confirmed: zero functional references; the auditor has two doc comments naming `DINModelRegistry` by name (L31, L434) plus two more referencing "model registry" generically without naming the type (L393, L405) — none are functional checks). A model the DIN-Representative has disabled — e.g. because its task contracts are wrongfully slashing honest validators — keeps running full GIs completely unaffected. The task contracts' own doc table calls this a "kill-switch," which it isn't. Needs a decision: wire the task contracts to check `modelDisabled` before state-changing calls (requires threading the model ID + registry address into contracts that don't know about the registry today), or document `disableModel` as registry-metadata-only. | P3/P4 | 2026-07 security review M-2 (`Documentation/technical/audits/foundry-src-security-review.md`) | 🆕 Decided, unimplemented — Abraham (Oct 26, [#224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224)): make the kill-switch real before the public testnet (at minimum stop new GIs on a disabled model's task contracts), preferably in `DINTaskAuditor` or a small external guard given `DINTaskCoordinator`'s size headroom | +| BL-34 | Solidity/foundry (task contracts) | `DINModelRegistry.disableModel()` kill-switch doesn't reach the live task contracts | `modelDisabled[modelId]` only gates `requestModelRegistration`/`requestManifestUpdate` inside the registry itself. `DINTaskCoordinator`/`DINTaskAuditor` have no dependency on, or awareness of, the registry (confirmed: zero functional references; the auditor has two doc comments naming `DINModelRegistry` by name (L31, L434) plus two more referencing "model registry" generically without naming the type (L393, L405) — none are functional checks). A model the DIN-Representative has disabled — e.g. because its task contracts are wrongfully slashing honest validators — keeps running full GIs completely unaffected. The task contracts' own doc table calls this a "kill-switch," which it isn't. Needs a decision: wire the task contracts to check `modelDisabled` before state-changing calls (requires threading the model ID + registry address into contracts that don't know about the registry today), or document `disableModel` as registry-metadata-only. | P3/P4 | 2026-07 security review M-2 (`Documentation/technical/audits/foundry-src-security-review.md`) | ✅ Resolved — `validateGIStart` wired in `DINTaskAuditor.sol`; both `startGI` overloads check registry approval and disabled status via `_startGI`. Compatible task pairs required; existing deployed pairs unaffected. [PR No. 305](https://github.com/InfiniteZeroFoundation/DevNet/pull/305). | | BL-35 | Solidity/foundry (registry) | `rejectModel()`'s non-refund of the registration fee was never confirmed as intentional | `feePaid` (set at `requestModelRegistration`) is never refunded on `rejectModel()`, but it's also never refunded on `approveModel()` either — the fee appears to be a deliberate pay-to-apply cost regardless of outcome (anti-spam), not an oversight specific to rejection. Never written down as a deliberate choice anywhere. Needs confirmation and a doc note in `DINModelRegistry.md` / the dinrep role docs, so a future reader doesn't "fix" it into an inconsistent partial-refund state. | P4 (doc-only once confirmed) | 2026-07 security review L-4 (`Documentation/technical/audits/foundry-src-security-review.md`) | ✅ Decided — keep as intended (Abraham, Oct 26, [#224](https://github.com/InfiniteZeroFoundation/DevNet/issues/224)): the registration fee is a pay-to-apply anti-spam cost, refunded on neither approval nor rejection. Docs pending (`DINModelRegistry.md`, dinrep role docs) | From 33d5f83dc11ec15d18ef66a3e9f21c369d538bc9 Mon Sep 17 00:00:00 2001 From: Santiagocetran Date: Mon, 2 Nov 2026 16:46:51 +0000 Subject: [PATCH 4/7] style(deploy): remove inherited trailing whitespace --- dincli/cli/modelownerd/deploy.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/dincli/cli/modelownerd/deploy.py b/dincli/cli/modelownerd/deploy.py index 21308da6..c819702e 100644 --- a/dincli/cli/modelownerd/deploy.py +++ b/dincli/cli/modelownerd/deploy.py @@ -56,16 +56,16 @@ def task_coordinator( # Load contract instance DINTaskCoordinator_contract = get_contract_instance(artifact_path, effective_network) - + # Resolve DINValidatorStake address din_info = load_din_info() din_validator_stake_address = din_info.get(effective_network, {}).get("stake") if not din_validator_stake_address: raise typer.BadParameter("Configure the platform stake contract before deployment") - + console.print(f"[bold green]Deploying DINTaskCoordinator on network:[/bold green] {effective_network}") console.print(f"[cyan]Using DINValidatorStake:[/cyan] {din_validator_stake_address}") - + tx_receipt = build_and_send_tx( ctx, DINTaskCoordinator_contract.constructor(*_task_constructor_args( @@ -76,11 +76,11 @@ def task_coordinator( "DINTaskCoordinator contract deployed successfully", "Failed to deploy DINTaskCoordinator" ) - + dintaskcoordinator_contract_address = tx_receipt.contractAddress - + console.print(f"[bold green]✓ DINTaskCoordinator contract deployed at:[/bold green] {dintaskcoordinator_contract_address}") - + set_env_key(effective_network.upper()+"_DINTaskCoordinator_Contract_Address", dintaskcoordinator_contract_address) console.print(f"[green]✓ Saved DINTaskCoordinator address to {os.getcwd()}/.env as {effective_network.upper()}_DINTaskCoordinator_Contract_Address[/green]") @@ -95,10 +95,10 @@ def task_auditor( Deploy the DINTaskAuditor contract. """ effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() - + # Load contract instance DINTaskAuditor_contract = get_contract_instance(artifact_path, effective_network) - + din_info = load_din_info() din_validator_stake_address = din_info.get(effective_network, {}).get("stake") if not din_validator_stake_address: @@ -108,7 +108,7 @@ def task_auditor( task_coordinator_address = resolve_task_coordinator_address( effective_network, task_coordinator, console ) - + constructor_args = _task_constructor_args( DINTaskAuditor_contract, "auditor", din_validator_stake_address, coordinator=task_coordinator_address, model_id=model_id, @@ -130,7 +130,7 @@ def task_auditor( console.print(f"[bold green]Deploying DINTaskAuditor on network:[/bold green] {effective_network}") console.print(f"[cyan]Using DINValidatorStake address:[/cyan] {din_validator_stake_address}") console.print(f"[cyan]Using DINTaskCoordinator address:[/cyan] {task_coordinator_address}") - + tx_receipt = build_and_send_tx( ctx, DINTaskAuditor_contract.constructor(*constructor_args), @@ -138,17 +138,17 @@ def task_auditor( "DINTaskAuditor contract deployed successfully", "Failed to deploy DINTaskAuditor" ) - + dintaskauditor_contract_address = tx_receipt.contractAddress - + console.print(f"[bold green]✓ DINTaskAuditor contract deployed at:[/bold green] {dintaskauditor_contract_address}") - + set_env_key(effective_network.upper() + "_"+task_coordinator_address+"_DINTaskAuditor_Contract_Address", dintaskauditor_contract_address) console.print(f"[green]✓ Saved DINTaskAuditor address to {os.getcwd()}/.env as {effective_network.upper()}_"+task_coordinator_address+"_DINTaskAuditor_Contract_Address[/green]") - + # Set DINTaskAuditor in DINTaskCoordinator console.print(f"[cyan]Setting DINTaskAuditor in DINTaskCoordinator...[/cyan]") - + DINTaskCoordinator_contract = ctx.obj.get_deployed_din_task_coordinator_contract(verbose=True, model_id=None, taskCoordinator_address=task_coordinator_address) time.sleep(10) From e8e734927910f100b9489542ef52ec96f5643538 Mon Sep 17 00:00:00 2001 From: Santiagocetran Date: Mon, 2 Nov 2026 17:21:24 +0000 Subject: [PATCH 5/7] fix(deploy): reject incompatible task contract generations --- .../public/workflows/model-workflow.md | 2 +- .../technical/contracts/DINTaskAuditor.md | 4 +- .../technical/contracts/DINTaskCoordinator.md | 1 + cache_model_0/abis/DINTaskCoordinator.json | 15 ++- cache_model_0/manifest.json | 2 +- dincli/abis/DINTaskCoordinator.json | 13 +++ dincli/cli/modelownerd/deploy.py | 35 +++++- foundry/src/DINTaskCoordinator.sol | 7 ++ foundry/test/RewardEngine.t.sol | 1 + tests/test_model_disable_deployment.py | 108 +++++++++++++++++- tests/test_task_auditor_model_id_guard.py | 3 +- 11 files changed, 177 insertions(+), 14 deletions(-) diff --git a/Documentation/public/workflows/model-workflow.md b/Documentation/public/workflows/model-workflow.md index 060deaff..671f2e2a 100644 --- a/Documentation/public/workflows/model-workflow.md +++ b/Documentation/public/workflows/model-workflow.md @@ -34,7 +34,7 @@ dincli system register-wallet --account In current DIN Protocol, the model owner needs to deploy a taskCoordinator contract and a taskAuditor contract specific to each model. -For current Foundry artifacts, pass `--model-id ` to both `model-owner deploy task-coordinator` and `model-owner deploy task-auditor`; their stake-floor IDs must match. Configure the canonical platform registry address in this network's `din_info.json` (`registry`) before deploying the auditor. Its fourth constructor argument is immutable registry wiring. The CLI supports explicit legacy constructor signatures too, but those older contracts do not gain the guard described below; unsupported custom constructor signatures are rejected. +For current Foundry artifacts, pass `--model-id ` to both `model-owner deploy task-coordinator` and `model-owner deploy task-auditor`; their stake-floor IDs must match. Configure the canonical platform registry address in this network's `din_info.json` (`registry`) before deploying the auditor. Its fourth constructor argument is immutable registry wiring. The coordinator reports `taskProtocolVersion() == 1`, identifying the interface that requires the auditor GI-start guard. Before deploying an auditor, the CLI checks this version as well as matching model IDs: version 1 requires the registry-aware four-argument auditor. Released unversioned coordinators remain supported with matching legacy auditors: coordinators without `modelId()` use the two-argument auditor, and those with `modelId()` use the three-argument auditor; unknown versions and mixed generations are rejected before deployment or pairing. Legacy pairs do not gain the guard described below. Unsupported custom constructor signatures are rejected. The checked-in `cache_model_0` artifacts and manifest describe the current reference pair. The `cache-default-artifacts` command instead downloads the manifest and ABIs selected by this network's `din_info.json` (`default_manifest` and `default_abis`); those historical bootstrap CIDs are not refreshed by this change. Verify the downloaded constructor signatures and registry wiring before using them for a new deployment. diff --git a/Documentation/technical/contracts/DINTaskAuditor.md b/Documentation/technical/contracts/DINTaskAuditor.md index 54d932c6..f9f64b01 100644 --- a/Documentation/technical/contracts/DINTaskAuditor.md +++ b/Documentation/technical/contracts/DINTaskAuditor.md @@ -110,7 +110,7 @@ For the current Foundry task pair, both `startGI(gi)` and `startGI(gi, score)` c Disabling blocks subsequent GI starts, manifest updates and emission funding. It does **not** abort or freeze an in-flight GI: submissions, audits, aggregation, slashing, settlement and reward claims continue. A start ordered before disable may succeed. Existing funds remain in their pools; this change adds no cancellation, refund or recovery path. -Both task contracts are non-upgradeable. Existing deployed pairs keep their previous behavior and need a newly deployed compatible pair to gain this protection; there is no automatic retrofit. Deploy the new coordinator together with the new auditor wired to the canonical platform registry proxy. An old coordinator can bypass the new auditor guard, and a new coordinator cannot call this guard on an old auditor. Immutable wiring prevents later reassignment but does not prove the supplied registry is canonical. The DIN-Representative must review the task code, pairing and registry address before slasher authorization and approval; arbitrary custom or malicious task code is outside this enforcement guarantee. +Both task contracts are non-upgradeable. Existing deployed pairs keep their previous behavior and need a newly deployed compatible pair to gain this protection; there is no automatic retrofit. Deploy the new coordinator together with the new auditor wired to the canonical platform registry proxy. An old coordinator can bypass the new auditor guard, and a new coordinator cannot call this guard on an old auditor. The CLI therefore reads the deployed coordinator's `taskProtocolVersion()` before auditor deployment: version `1` requires this registry-aware four-argument auditor, while released unversioned coordinators accept only matching legacy auditor signatures (two arguments without `modelId()`, three arguments with it). Mixed or unknown generations fail before deployment, address writes or one-shot pairing. Direct deployments still require review; the version getter is a compatibility declaration, not proof of trusted task code. Immutable wiring prevents later reassignment but does not prove the supplied registry is canonical. The DIN-Representative must review the task code, pairing and registry address before slasher authorization and approval; arbitrary custom or malicious task code is outside this enforcement guarantee. ## 5. Registration & Submissions @@ -218,7 +218,7 @@ Read alongside the [foundry/src security review](../audits/foundry-src-security- - **No. 5 — `setTestDataAssignedFlag` gates nothing:** scoring can open before any test data is assigned. - **No. 6 — Stale NatSpec and reused errors:** `slashAuditors` says S1 and S3 are both `minStake()` (S1 is now a fraction). Several comments call parameters "DAO-settable"; they are `onlyOwner`, i.e. set by the model owner. `setDisputePenaltyBps` reuses `TA_InvalidDisputeBond`, and `closeExpiredDispute` reuses `TA_DisputeWindowClosed` for "window still open". - **No. 7 — `modelId` is fixed at construction**, before the registry assigns it (see [DINTaskCoordinator §10 No. 4](DINTaskCoordinator.md#10-review-notes--open-caveats)). -- **No. 8 — Fixed: dincli supports the current constructor.** `dincli model-owner deploy task-auditor` passes the anticipated `--model-id` and configured canonical registry, and checks that the paired coordinator exposes the same `modelId()`. Legacy constructor support does not add the guard to older contracts. +- **No. 8 — Fixed: dincli supports the current constructor.** `dincli model-owner deploy task-auditor` passes the anticipated `--model-id` and configured canonical registry, and checks that the paired coordinator exposes the same `modelId()` and protocol version `1`. Legacy constructor support does not add the guard to older contracts. - **No. 9 — Fixed: a dincli auditor commit retry no longer loses the committed salt.** Rerunning `dincli auditor lms-evaluation evaluate --submit` used to generate a new salt and overwrite the local commit cache even when the on-chain commit already existed. The later reveal then failed the hash check, and the auditor was slashed for a missed vote. dincli now skips any LM whose `hasCommittedLM` is already set, leaving its cache untouched, and writes the cache before sending the commit tx (issue No. 202 Part 1). A rerun is safe. --- diff --git a/Documentation/technical/contracts/DINTaskCoordinator.md b/Documentation/technical/contracts/DINTaskCoordinator.md index 2c1936de..5238cbaf 100644 --- a/Documentation/technical/contracts/DINTaskCoordinator.md +++ b/Documentation/technical/contracts/DINTaskCoordinator.md @@ -43,6 +43,7 @@ Auditor registration, client submissions, scoring and rewards live in the paired |----------|-------------| | `dinvalidatorStakeContract` | `DinValidatorStake` proxy (constructor) | | `dinTaskAuditorContract` | Paired auditor (`setDINTaskAuditorContract`) | +| `taskProtocolVersion()` | Pure getter returning `1`: requires a registry-aware auditor implementing `validateGIStart`; used by CLI deployment preflight | | `modelId` (`immutable`) | Registry model ID, used for the per-model stake floor (constructor) | | `GI` | Current GI counter (starts at 0; the first GI is 1) | | `GIstate` | Current `GIstates` value | diff --git a/cache_model_0/abis/DINTaskCoordinator.json b/cache_model_0/abis/DINTaskCoordinator.json index 11189f20..e025aadf 100644 --- a/cache_model_0/abis/DINTaskCoordinator.json +++ b/cache_model_0/abis/DINTaskCoordinator.json @@ -1441,6 +1441,19 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "taskProtocolVersion", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "pure" + }, { "type": "function", "name": "tier1BatchCount", @@ -2663,5 +2676,5 @@ "inputs": [] } ], - "bytecode": "0x60a03461016957601f615cdd38819003918201601f19168301916001600160401b0383118484101761016d5780849260409485528339810103126101695780516001600160a01b038116919082900361016957602001513315610156575f8054336001600160a01b0319821681178355604051949290916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a35f60035568056bc75e2d63100000601b55601c80546001600160c01b0319167007000000000002a3000000000000015180179055610bb8602355801561014757600180546001600160a01b0319169190911790556080526004805460ff191690555f807fa8f52d7b4405100a39ccc552ccc9426d860b1e822c71e97675d1623b49db6bab8180a3615b5b90816101828239608051818181611e9e0152612c8d0152f35b63f70949a160e01b5f5260045ffd5b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f3560e01c90816304501eec14614fdc5750806309adabf514614bd05780630a2b338214614aaa5780630ac04340146144115780630e84af23146141be578063117f5f92146141985780631511c3a6146140c557806317baaf20146140a8578063199efdf41461407e5780631a336bfc146139935780631c2c56bc146139695780631d9e7295146135305780631dfa1ac2146134d15780631eb30bfe1461348d5780631f1761d01461341f5780631f83192c146133f05780632059a89914613337578063245092201461331a578063246116e61461327f57806324e168af146132335780632a9cafbe146132125780632b143f03146131a45780632d8d2d63146130a257806331823a221461308757806332280eed14612eb757806337d274c914612e845780633a0ea70414612e5b5780633b0ff6bb14612e3e5780633b6dffe514612deb5780633ce58bba14612d7d57806342f7750214612cb057806343cdaad214612c7657806346bf820a14612c085780634774b52414612b8c5780634e649ca7146127ad578063530a9fb114612726578063576a91b3146125f557806358aed7bc146125cd578063595e34111461255f5780635b0283ef146122f75780635bfd7d3a146122425780635fec207c14611de75780636336894114611d79578063715018a614611d22578063747a21fe14611d0657806378737a3914611cde5780637c4596c714611b33578063868276ab14611b1657806388c2978514611a75578063894f4dd114611a3d5780638b1de7be146119cf5780638cfbd515146119a95780638da5cb5b14611982578063914b92cd146116b75780639de2f2a01461159c578063a120c41a14611576578063a7e10e281461153e578063aafebd501461146e578063ac0c7d5114611445578063ac9fc1001461142c578063acaf042a1461049a578063adabd801146113f9578063b4b9db4114610f89578063b593d2d514611394578063b61998b61461129d578063b6f104a814611273578063b759977014611244578063b7696bd6146111d3578063b7a0e41c14610f8e578063bee3fe1714610f89578063c0aeb10d14610f1b578063c6e8fc6f14610d61578063c9ad03c814610d29578063cad783b214610aca578063cb404a75146109cf578063cc279009146109b2578063d6f353a11461098a578063d86795ac1461091c578063e2852e3f146108f3578063e547118e14610885578063e57c943e1461083c578063e6f5e11914610689578063e9c8bb5214610660578063ead1df17146105c3578063f0f95f6414610549578063f2fde38b146104c4578063f34db55e1461049a5763f4eed253146103e4575f80fd5b34610496576020366003190112610496576103fd61507d565b6104056156fb565b60ff60045416601a81101561048257610473576001600160a01b03168015610464576bffffffffffffffffffffffff60a01b6002541617600255600160ff19600454161760045560016003545f516020615ac65f395f51905f525f80a3005b63f70949a160e01b5f5260045ffd5b631f85a85960e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b34610496576020366003190112610496576004355f526012602052602060405f2054604051908152f35b34610496576020366003190112610496576104dd61507d565b6104e56156fb565b6001600160a01b03168015610536575f80546001600160a01b03198116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b631e4fbdf760e01b5f525f60045260245ffd5b3461049657610557366150e5565b906105606156fb565b60035481036105b45760ff60045416601a81101561048257601681141590816105a8575b50610599575f52601260205260405f20555f80f35b63c7a0e6e160e01b5f5260045ffd5b60049150141583610584565b6347ca2dcf60e01b5f5260045ffd5b34610496575f366003190112610496576105db6159ca565b335f52602860205260405f2054801561065257335f81815260286020526040812055601a546106149183916001600160a01b03166159ff565b6040519081527fa09dd3797281514bc434a54ae8b69a6d02ae2fdbfab7c86dc8975163fd0045d060203392a25f5f516020615b065f395f51905f525d005b621db6b160e21b5f5260045ffd5b34610496575f3660031901126104965760ff60045416604051601a821015610482576020918152f35b346104965760803660031901126104965760243560043560028210156104965760643590604435906001600160a01b03831683036104965760a093610790575f818152600f6020908152604080832085845282528083205f196001891b019690961680845295825280832054848452600e83528184208685528352818420878552835281842054858552600c84528285208786528452828520888652845282852054868652600b8552838620888752855283862098865297845282852054958552600d84528285209685529583528184208585529092529091205460ff9182169490939116905b604051941515855260208501521515604084015260608301526080820152f35b5f81815260176020908152604080832085845282528083205f196001891b01969096168084529582528083205484845260168352818420868552835281842087855283528184205485855260148452828520878652845282852088865284528285205486865260138552838620888752855283862098865297845282852054958552601584528285209685529583528184208585529092529091205460ff918216949093911690610770565b3461049657604036600319011261049657610855615067565b6004355f52600760205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b346104965760203660031901126104965761089e6156fb565b600354600435036105b45760ff60045416601a811015610482576005036108e457600660ff19600454161760045560066003545f516020615ac65f395f51905f525f80a3005b6321012b6760e01b5f5260045ffd5b34610496576020366003190112610496576004355f5260208052602060405f2054604051908152f35b34610496576020366003190112610496576109356156fb565b600354600435036105b45760ff60045416601a8110156104825760130361097b57601460ff19600454161760045560146003545f516020615ac65f395f51905f525f80a3005b631416b1a360e01b5f5260045ffd5b34610496575f366003190112610496576001546040516001600160a01b039091168152602090f35b34610496575f366003190112610496576020602154604051908152f35b34610496576020366003190112610496576004356109eb6156fb565b60035481036105b45760ff60045416601a81101561048257601603610abb5760025460405163cb404a7560e01b81526004810192909252602090829060249082905f906001600160a01b03165af1908115610ab0575f91610a81575b5015610a7257601760ff19600454161760045560176003545f516020615ac65f395f51905f525f80a3005b634b058d6560e11b5f5260045ffd5b610aa3915060203d602011610aa9575b610a9b81836151b4565b8101906151d5565b81610a47565b503d610a91565b6040513d5f823e3d90fd5b63b52ae37160e01b5f5260045ffd5b3461049657602036600319011261049657600435610ae66156fb565b60035481036105b45760ff60045416601a81101561048257601503610d1a575f81815260106020526040812091426001600160401b0316915b8354811015610cf557610b328185615206565b505f5f905f935f9760018501985b8954811015610c5257610b53818b615130565b60018060a01b0391549060031b1c16885f52601460205260405f20875490815f5260205260405f2060018060a01b0383165f5260205260ff60405f205416610be2575b5090600191895f52601860205260405f2090838060a01b03165f5260205260405f20610bc2815461521f565b9055885f52601960205260405f20610bda815461521f565b905501610b40565b97610bec9061521f565b97895f52601360205260405f20815f5260205260405f2060018060a01b0383165f5260205260405f2054908a5f52601560205260405f20905f5260205260405f20815f5260205260405f2054878111610c46575b50610b96565b9650945060018c610c40565b50969750929150928315610ce757600211610cd8577f9bca595da6d2d1ae0f0ef7abe316afdd467890adf8eb5a2234a8af80f8d151b760206001948360028895018760ff19825416179055816003820155845f526025835260405f2090545f52825260405f20896001600160401b0319825416179055604051908152a201929192610b1f565b6380c2216560e01b5f5260045ffd5b62b5305b60e51b5f5260045ffd5b601660ff19600454161760045560166003545f516020615ac65f395f51905f525f80a3005b63c4d5525560e01b5f5260045ffd5b34610496576020366003190112610496576001600160a01b03610d4a61507d565b165f526028602052602060405f2054604051908152f35b3461049657610d6f366150fb565b9160035481036105b45760ff60045416601a81101561048257601403610f0c5781610efd575f818152601160209081526040808320858452825280832033845290915290205460ff1615610eee576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610ab0575f91610ecf575b5015610ec0578215610eb1575f818152601760209081526040808320858452825280832033845290915290205460ff16610ea2575f818152601660209081526040808320858452825280832033808552908352818420879055848452601783528184208685528352818420818552835292819020805460ff191660011790555194855290937f430cf27dfb66aa7260b606dbba6d3d2600983f065b1e30ffcab0e6457f3776c39190a4005b6343a311af60e01b5f5260045ffd5b630e660de560e11b5f5260045ffd5b636936304560e01b5f5260045ffd5b610ee8915060203d602011610aa957610a9b81836151b4565b84610df7565b6328e28e8360e01b5f5260045ffd5b6327fda01d60e11b5f5260045ffd5b636582261960e11b5f5260045ffd5b3461049657602036600319011261049657610f346156fb565b600354600435036105b45760ff60045416601a81101561048257600603610f7a57600760ff19600454161760045560076003545f516020615ac65f395f51905f525f80a3005b6301876fed60e21b5f5260045ffd5b615115565b3461049657602036600319011261049657600435610faa6156fb565b60035481036105b45760ff60045416601a811015610482576012036111c4575f81815260096020526040812091426001600160401b0316915b835481101561119f57610ff681856151ed565b5060018101945f9485938492835b895481101561111757611017818b615130565b60018060a01b0391549060031b1c16885f52600c60205260405f20875490815f5260205260405f2060018060a01b0383165f5260205260ff60405f2054166110a6575b5090600191895f52601860205260405f2090838060a01b03165f5260205260405f20611086815461521f565b9055885f52601960205260405f2061109e815461521f565b905501611004565b976110b09061521f565b97895f52600b60205260405f20815f5260205260405f2060018060a01b0383165f5260205260405f2054908a5f52600d60205260405f20905f5260205260405f20815f5260205260405f205487811161110a575b5061105a565b909a50955060018c611104565b5096925092939096508115610ce757600211610cd857837fb1becb4dbd2440a1dbd1bac657cc9dbbe112f85f7a99012f71aff4bab4acc5a86020856003600197018760ff19825416179055846004820155835f526024825260405f2081545f52825260405f208a6001600160401b03198254161790555493604051908152a301929192610fe3565b601360ff19600454161760045560136003545f516020615ac65f395f51905f525f80a3005b638f5431c960e01b5f5260045ffd5b346104965760803660031901126104965760243560028110156104965761120a606435916004355f52602760205260405f20615093565b6044355f5260205260405f2080548210156104965760209161122b91615130565b905460405160039290921b1c6001600160a01b03168152f35b3461049657611252366150e5565b905f52600660205260405f2080548210156104965760209161122b91615130565b34610496576020366003190112610496576004355f526019602052602060405f2054604051908152f35b3461049657608036600319011261049657600435602435906001600160401b03821680920361049657604435906001600160401b0382169283830361049657606435936001600160401b038516808603610496576112f96156fb565b831591821561138b575b8215611382575b508115611379575b811561136d575b5061135e5767ffffffffffffffff60401b91601b55601c54936001600160401b0360801b9060801b16936001600160401b0360c01b16179160401b161717601c555f80f35b631218659f60e01b5f5260045ffd5b61010091501185611319565b80159150611312565b1591508661130a565b83159250611303565b34610496576020366003190112610496576113ad6156fb565b60ff60045416601a811015610482576003036113ea5760048035600555805460ff1916811781556003545f516020615ac65f395f51905f525f80a3005b631529e88760e31b5f5260045ffd5b34610496576020366003190112610496576004355f52601f60205260206001600160401b0360405f205416604051908152f35b346104965761144361143d36615044565b91615579565b005b34610496575f3660031901126104965760206001600160401b03601c5460801c16604051908152f35b34610496575f366003190112610496576114866156fb565b60ff60045416601a8110156104825760010361152f57600154604051633c1a477960e11b815230600482015290602090829060249082906001600160a01b03165afa908115610ab0575f91611510575b501561150157600260ff19600454161760045560026003545f516020615ac65f395f51905f525f80a3005b634f3572c560e01b5f5260045ffd5b611529915060203d602011610aa957610a9b81836151b4565b816114d6565b632748c52f60e21b5f5260045ffd5b346104965761154c366150e5565b905f52602560205260405f20905f5260205260206001600160401b0360405f205416604051908152f35b346104965760203660031901126104965760043560035481036105b45761144390615429565b34610496576115aa366150e5565b6115b26156fb565b60ff60045416601a81101561048257600481141590816116ab575b5061169c57600354600181018091116116885782036105b4576002546001600160a01b031691823b156104965760405190635a6f53a760e01b825260048201525f81602481865afa8015610ab057611678575b50813b15610496575f91602483926040519485938492633f23a7b760e21b845260048401525af18015610ab057611668575b61165d60035461521f565b6003556114436158df565b5f611672916151b4565b80611652565b5f611682916151b4565b82611620565b634e487b7160e01b5f52601160045260245ffd5b63205400e160e21b5f5260045ffd5b601991501415836115cd565b34610496576116c536615044565b825f9392935260266020526116dd8360405f20615093565b815f5260205260405f2090600282019283549260ff8460c01c1680611975575b156119665760ff8460d01c16611957576001600160401b038460401c164211156119485760ff60d01b19909316600160d01b17909355600182015491546001600160a01b03165f908152602860205260409020805461179694600494909291611766919061522d565b9055805f52602760205261177d8560405f20615093565b825f5260205261179d60405f206040519586809261529c565b03856151b4565b600154604051631bad9e0560e11b81529360209185919082906001600160a01b03165afa928315610ab0575f93611914575b505f5b84518110156118df576001545f91906020906001600160a01b03908116906064906117fd858b61523a565b51166040519586938492637dd1602d60e11b845260048401528a60248401527814cd17d1949154d217d4d55091d493d55417d512535153d555603a1b60448401525af18015610ab0575f906118ad575b6001925084845f516020615ae65f395f51905f5260606001600160a01b03611875878d61523a565b511694604051907814cd17d1949154d217d4d55091d493d55417d512535153d555603a1b82528b60208301526040820152a4016117d2565b506020823d82116118d7575b816118c6602093836151b4565b81010312610496576001915161184d565b3d91506118b9565b507ff42fd7a6ed54d90eb785299babf26fd989424f01778ddc07a710b65b7effc0ca602086611911604051809261518c565ba3005b9092506020813d602011611940575b81611930602093836151b4565b81010312610496575191856117cf565b3d9150611923565b63aa161f2160e01b5f5260045ffd5b631a3b0bef60e01b5f5260045ffd5b630a18626760e21b5f5260045ffd5b5060ff8460c81c166116fd565b34610496575f366003190112610496575f546040516001600160a01b039091168152602090f35b346104965760203660031901126104965760043560035481036105b457611443906152dc565b34610496576020366003190112610496576119e86156fb565b600354600435036105b45760ff60045416601a81101561048257600703611a2e57600860ff19600454161760045560086003545f516020615ac65f395f51905f525f80a3005b63ca5a3b5560e01b5f5260045ffd5b3461049657611a4b366150e5565b905f52602460205260405f20905f5260205260206001600160401b0360405f205416604051908152f35b3461049657602036600319011261049657600435611a916156fb565b60ff60045416601a8110156104825760048114159081611b0a575b5061169c57600354600181018091116116885781036105b4576002546001600160a01b031690813b15610496575f90602460405180948193635a6f53a760e01b835260048301525afa8015610ab0576116685761165d60035461521f565b60199150141582611aac565b34610496575f366003190112610496576020602254604051908152f35b3461049657611b41366150fb565b9160035481036105b45760ff60045416601a81101561048257601103611ccf57805f52600960205260405f2054821015611cc0575f818152600a60209081526040808320858452825280832033845290915290205460ff1615610eee576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610ab0575f91611ca1575b5015610ec0578215611c92575f818152600f60209081526040808320858452825280832033845290915290205460ff16611c83575f818152600e60209081526040808320858452825280832033808552908352818420879055848452600f83528184208685528352818420818552835292819020805460ff191660011790555194855290937fc33cb04355b8f36a3e3860fb823d630cc37a5833cf62828f742e70bdd3751eac9190a4005b63432cb74f60e11b5f5260045ffd5b63d5d0c92560e01b5f5260045ffd5b611cba915060203d602011610aa957610a9b81836151b4565b84611bd8565b633b554d2360e11b5f5260045ffd5b637100347160e01b5f5260045ffd5b34610496575f366003190112610496576002546040516001600160a01b039091168152602090f35b34610496575f36600319011261049657602060405161012c8152f35b34610496575f36600319011261049657611d3a6156fb565b5f80546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461049657602036600319011261049657611d926156fb565b600354600435036105b45760ff60045416601a81101561048257601003611dd857601160ff19600454161760045560116003545f516020615ac65f395f51905f525f80a3005b63076b0ff760e01b5f5260045ffd5b346104965760203660031901126104965760043560035481036105b45760ff60045416601a81101561048257600603612233576001546040516310ab556b60e21b81523360048201526001600160a01b0390911690602081602481855afa908115610ab0575f91612214575b5015610ec0575f82815260076020908152604080832033845290915290205460ff1661220557815f52600660205261012c60405f205410156121f657604051634ab989d560e01b81527f00000000000000000000000000000000000000000000000000000000000000006004820152602081602481855afa908115610ab0575f916121c4575b50801515908161215d575b5061214e5760405163f2d679bf60e01b8152602081600481855afa908115610ab0575f9161211c575b5080611fc4575b5050805f526006602052611f2b3360405f2061524e565b5f8181526007602090815260408083203384529091529020805460ff19166001908117909155546001600160a01b0316803b15610496575f80916024604051809481936347e91db160e01b83523360048401525af18015610ab057611fb4575b5033907f88f3e7b00fb6a3104c7d2c8d3ff669b012e5724bd38e50559cb41d177a3a8ba15f80a3005b5f611fbe916151b4565b81611f8b565b6040516303d3b32360e51b8152336004820152602081602481865afa908115610ab0575f916120ea575b50604051631bad9e0560e11b815290602082600481875afa918215610ab0575f926120b6575b5081156120a25760249260209261202b9204615179565b926040519283809263b998328360e01b82523360048301525afa908115610ab0575f91612070575b501015612061578180611f14565b634f21e5ed60e11b5f5260045ffd5b90506020813d60201161209a575b8161208b602093836151b4565b81010312610496575183612053565b3d915061207e565b634e487b7160e01b5f52601260045260245ffd5b9091506020813d6020116120e2575b816120d2602093836151b4565b8101031261049657519085612014565b3d91506120c5565b90506020813d602011612114575b81612105602093836151b4565b81010312610496575184611fee565b3d91506120f8565b90506020813d602011612146575b81612137602093836151b4565b81010312610496575183611f0d565b3d915061212a565b631931d5eb60e01b5f5260045ffd5b6040516303d3b32360e51b8152336004820152909150602081602481865afa908115610ab0575f91612192575b501083611ee4565b90506020813d6020116121bc575b816121ad602093836151b4565b8101031261049657518461218a565b3d91506121a0565b90506020813d6020116121ee575b816121df602093836151b4565b81010312610496575183611ed9565b3d91506121d2565b63083a2c5d60e01b5f5260045ffd5b6306d4010b60e21b5f5260045ffd5b61222d915060203d602011610aa957610a9b81836151b4565b83611e53565b6331fe69e760e21b5f5260045ffd5b346104965760203660031901126104965760043561225e6156fb565b60035481036105b45760ff60045416601a81101561048257600a036122e85761229e6001600160401b03601c5460801c166001600160401b034316615159565b905f52601f6020526001600160401b0360405f2091166001600160401b0319825416179055600b60ff196004541617600455600b6003545f516020615ac65f395f51905f525f80a3005b630d1f756d60e31b5f5260045ffd5b346104965761230536615026565b60039391935482036105b45760ff60045416601a811015610482576015036125505782610efd575f828152601160209081526040808320868452825280832033845290915290205460ff1615610eee576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610ab0575f91612531575b5015610ec0575f828152601760209081526040808320868452825280832033845290915290205460ff1615612522575f828152601460209081526040808320868452825280832033845290915290205460ff16612513578315612504576040805160208101868152918101929092523360608301526080820183905290600160a082015260c0808201859052815261242860e0826151b4565b5190205f8281526016602090815260408083208684528252808320338452909152902054036124f5575f818152601460209081526040808320858452825280832033808552908352818420805460ff1916600117905584845260138352818420868552835281842081855283529281902086905551858152849184917ff381116b332b24a3860f90e240a69b31a4eb09c27b59cc54e049aad99897e97c9190a45f52601560205260405f20905f5260205260405f20905f5260205260405f206124f1815461521f565b9055005b634407f1db60e01b5f5260045ffd5b63010e92c160e11b5f5260045ffd5b636e479ac960e01b5f5260045ffd5b6343d466b760e11b5f5260045ffd5b61254a915060203d602011610aa957610a9b81836151b4565b8561238f565b63e396ca2760e01b5f5260045ffd5b34610496576020366003190112610496576125786156fb565b600354600435036105b45760ff60045416601a811015610482576011036125be57601260ff19600454161760045560126003545f516020615ac65f395f51905f525f80a3005b63e89736d560e01b5f5260045ffd5b34610496575f36600319011261049657601a546040516001600160a01b039091168152602090f35b3461049657612603366150e5565b9060035481116105b457805f52600960205260405f205482101561271757600191612638915f52600960205260405f206151ed565b50805490600281019060ff600382015416612669600483015492612662604051809881930161529c565b03866151b4565b604051948580966020865491828152019081965f5260205f20905f5b8181106126fe57505050916126a16126b79260209403896151b4565b60405196875260a08388015260a08701906150a9565b958587036040870152519586815201925f955b8087106126e65750508394501515606084015260808301520390f35b909360208060019287518152019501960195906126ca565b825484528a945060209093019260019283019201612685565b631935ca8d60e01b5f5260045ffd5b3461049657612734366150e5565b9081610efd5760035481116105b45760019161275a915f52601060205260405f20615206565b5080549060ff60028201541690612780600382015491611796604051809781930161529c565b61279c60405194859485526080602086015260808501906150a9565b911515604084015260608301520390f35b34610496576127bb36614ff6565b926127c46156fb565b805f5260266020526127d98360405f20615093565b825f5260205260405f2060028101805460ff8160c01c1680612b7f575b156119665760ff8160d01c166119575760ff60d01b1916600160d01b1790558415612b4857600181015490546001600160a01b03165f908152602860205260409020805490916128459161522d565b90556002831015610482576004612888948415805f14612b2757835f52600960205261288f60016128798760405f206151ed565b50015b6040519889809261529c565b03886151b4565b8015612b0757835f526009602052826128ab8660405f206151ed565b500154915b600154604051631bad9e0560e11b81529460209186919082906001600160a01b03165afa938415610ab0575f94612ad3575b505f5b8851811015612a93578215612a5257855f52600b60205260405f20875f5260205260405f2060018060a01b0361291b838c61523a565b511660018060a01b03165f526020528360405f20545b03612a4a576001545f91906020906001600160a01b0390811690606490612958858f61523a565b51166040519586938492637dd1602d60e11b845260048401528b602484015275299a2fa4a72b20a624a22fa0a3a3a922a3a0aa24a7a760511b60448401525af18015610ab05788888c945f93612a03575b505f516020615ae65f395f51905f5260606001966129cd87898060a01b039261523a565b5116946040519075299a2fa4a72b20a624a22fa0a3a3a922a3a0aa24a7a760511b82528c60208301526040820152a45b016128e5565b94505050506020823d8211612a42575b81612a20602093836151b4565b8101031261049657905189919088885f516020615ae65f395f51905f526129a9565b3d9150612a13565b6001906129fd565b855f52601360205260405f20875f5260205260405f2060018060a01b03612a79838c61523a565b511660018060a01b03165f526020528360405f2054612931565b509395925050507f9086299cfbdc88752f050229a9e7b3563c343b687b7fa5f7ace411c049301d1a9294505b612ace60405192839283615199565b0390a3005b9093506020813d602011612aff575b81612aef602093836151b4565b81010312610496575192886128e2565b3d9150612ae2565b835f5260106020526003612b1e8660405f20615206565b500154916128b0565b835f52601060205261288f6001612b418760405f20615206565b500161287c565b92612b7a60017f9086299cfbdc88752f050229a9e7b3563c343b687b7fa5f7ace411c049301d1a949693950154615721565b612abf565b5060ff8160c81c166127f6565b3461049657602036600319011261049657600435612ba86156fb565b80158015612bfd575b612bee5760407f6aa2abf97a85127b2d7e0a4a90f5bf57c815685cdf8a07985043f5298fd6190c91602354908060235582519182526020820152a1005b631e0e5cfb60e11b5f5260045ffd5b506127108111612bb1565b3461049657602036600319011261049657612c216156fb565b600354600435036105b45760ff60045416601a81101561048257601403612c6757601560ff19600454161760045560156003545f516020615ac65f395f51905f525f80a3005b63106ff71f60e01b5f5260045ffd5b34610496575f3660031901126104965760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b3461049657602036600319011261049657600435612ccc6156fb565b60035481036105b45760ff60045416601a81101561048257601803612d6e5760018060a01b036002541690805f52601960205260405f2054823b156104965760445f9283604051958694859363e13b7b3160e01b8552600485015260248401525af18015610ab057612d5e575b601960ff19600454161760045560196003545f516020615ac65f395f51905f525f80a3005b5f612d68916151b4565b80612d39565b6303858c7f60e01b5f5260045ffd5b3461049657602036600319011261049657612d966156fb565b600354600435036105b45760ff60045416601a81101561048257600d03612ddc57600e60ff196004541617600455600e6003545f516020615ac65f395f51905f525f80a3005b636487147760e11b5f5260045ffd5b34610496576020366003190112610496576004355f526006602052612e3a612e1f612e2660405f206040519283809261529c565b03826151b4565b6040519182916020835260208301906150a9565b0390f35b34610496575f366003190112610496576020600354604051908152f35b34610496575f3660031901126104965760206001600160401b03601c5460401c16604051908152f35b34610496576020366003190112610496576004355f52601d60205260206001600160401b0360405f205416604051908152f35b3461049657602036600319011261049657600435612ed36156fb565b60035480821090811561305f575b501561302757805f52600860205260ff60405f205416612fe957805f52600860205260405f20600160ff19825416179055805f52600660205260405f20905f5b8254811015612f9f576001546001600160a01b031690612f418185615130565b905460039190911b1c6001600160a01b0316823b15610496575f92602484926040519586938492631bf8593960e31b845260048401525af1918215610ab057600192612f8f575b5001612f21565b5f612f99916151b4565b84612f88565b506002546001600160a01b031690813b15610496575f9160248392604051948593849263224c19bb60e01b845260048401525af18015610ab057612fdf57005b5f611443916151b4565b60405162461bcd60e51b81526020600482015260166024820152751cdb1bdd1cc8185b1c9958591e481c995b19585cd95960521b6044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f11d2481a185cc81b9bdd08195b99195960821b6044820152606490fd5b905081148061306f575b82612ee1565b5060ff60045416601a81101561048257601914613069565b34610496575f36600319011261049657602060405160028152f35b34610496576020366003190112610496576004356130be6156fb565b60035481036105b45760ff60045416601a81101561048257600b0361319557805f526020805260405f2054801561318657602090604460018060a01b0360025416935f60405195869485936363fd7a1160e11b8552600485015260248401525af1908115610ab0575f91613167575b501561315857600c60ff196004541617600455600c6003545f516020615ac65f395f51905f525f80a3005b63611d8f8760e01b5f5260045ffd5b613180915060203d602011610aa957610a9b81836151b4565b8161312d565b63dbbb808560e01b5f5260045ffd5b63c827e84760e01b5f5260045ffd5b34610496576020366003190112610496576131bd6156fb565b600354600435036105b45760ff60045416601a8110156104825760080361320357600960ff19600454161760045560096003545f516020615ac65f395f51905f525f80a3005b63768c7a4d60e11b5f5260045ffd5b346104965760203660031901126104965761322b6156fb565b600435602255005b346104965760203660031901126104965761324c61507d565b6132546156fb565b6001600160a01b03168015610464576bffffffffffffffffffffffff60a01b601a541617601a555f80f35b346104965760403660031901126104965760043560243590811515809203610496576132a96156fb565b60035481036105b45760ff60045416601a81101561048257600c0361330b576002546001600160a01b031691823b156104965760445f928360405195869485936312308b7360e11b8552600485015260248401525af18015610ab057612fdf57005b630c14724760e31b5f5260045ffd5b34610496575f366003190112610496576020602354604051908152f35b346104965761335a61334836615044565b92915f52602660205260405f20615093565b905f5260205261012060405f2060018060a01b038154169060ff600182015491600360028201549101549260405194855260208501526001600160401b03811660408501526001600160401b038160401c1660608501526001600160401b038160801c166080850152818160c01c16151560a0850152818160c81c16151560c085015260d01c16151560e0830152610100820152f35b34610496576020366003190112610496576004355f526008602052602060ff60405f2054166040519015158152f35b34610496576020366003190112610496576134386156fb565b600354600435036105b45760ff60045416601a8110156104825760090361347e57600a60ff196004541617600455600a6003545f516020615ac65f395f51905f525f80a3005b63379ffc8d60e11b5f5260045ffd5b34610496576040366003190112610496576134a6615067565b6004355f52601860205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b34610496576020366003190112610496576134ea6156fb565b600354600435036105b45760ff60045416601a81101561048257600c0361319557600d60ff196004541617600455600d6003545f516020615ac65f395f51905f525f80a3005b346104965761353e36615044565b90916135486159ca565b8015801561395e575b6105b4576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610ab0575f9161393f575b5015610ec057600283101561048257826138e857805f52600960205260405f2054821015611cc057805f52600960205260ff60036135d08460405f206151ed565b50015416815f52602460205260405f20835f526020526001600160401b0360405f205416905b156138d9576001600160401b03613613601c549282841690615159565b1642116138ca57815f52602660205261362f8460405f20615093565b835f5260205260018060a01b0360405f2054166138bb576136686001600160401b03601b549260801c166001600160401b034316615159565b906040519161012083018381106001600160401b038211176138a757604052338352602083019182526040830190426001600160401b0316825260608401905f825260808501906001600160401b0316815260a085015f815260c08601915f835260e08701935f85526101008801965f8852895f5260266020528b60405f20906136f191615093565b5f8c815260209190915260409081902099518a546001600160a01b0319166001600160a01b0391909116178a55905160018a0155955160028901805492516fffffffffffffffffffffffffffffffff199093166001600160401b03909216919091179190961b67ffffffffffffffff60401b1617855551845467ffffffffffffffff60801b191660809190911b67ffffffffffffffff60801b16178455518354915161ffff60c01b1990921690151560c01b60ff60c01b161790151560c81b60ff60c81b1617825551815460ff60d01b191690151560d01b60ff60d01b1617905551600390910155601a54601b546040516323b872dd60e01b5f52336004523060245260449190915293906001600160a01b0316805a60645f806020948194f15f51600114811615613888575b856040525f60605215613876575061383984601b549261518c565b60208401527f4c0dd383a6eb2a2f4b8f2a967e4365ca69b2d0012887044c0c509fa0ce676f2360403394a45f5f516020615b065f395f51905f525d005b635274afe760e01b5f5260045260245ffd5b600181151661389e57813b15153d15161661381e565b853d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b633624f00960e21b5f5260045ffd5b63a4872ddd60e01b5f5260045ffd5b6355bc0ebf60e11b5f5260045ffd5b805f52601060205260405f2054821015611cc057805f52601060205260ff60026139158460405f20615206565b50015416815f52602560205260405f20835f526020526001600160401b0360405f205416906135f6565b613958915060203d602011610aa957610a9b81836151b4565b8461358f565b506003548111613551565b34610496576020366003190112610496576004355f526009602052602060405f2054604051908152f35b34610496576020366003190112610496576004356139af6156fb565b60035481036105b45760ff60045416601a81101561048257600f0361406f57805f52601e60205260405f205490811561406057805f52600660205260405f20905f915f81549160018060a01b0360015416915b838110613fdb5750613a13856158c8565b94613a2160405196876151b4565b808652613a30601f19916158c8565b013660208701375f905f5b848110613f4a578787876003825110613f3b57602492613a8860405160208101908382526720a3a3afa0a2222960c11b604082015260288152613a7f6048826151b4565b5190208461591a565b6002546040516327416acd60e21b815260048101849052945f9186919082906001600160a01b03165afa938415610ab0575f94613ea9575b506003845110613e9a5760405160208101918252660828e8ebe9288b60cb1b604082015260278152613af36047826151b4565b51902083515f19810190811161168857805b613e0e5750505f915f915f935b6003810180821161168857835181111580613dc5575b15613ca457825f52600960205260405f208054600160401b8110156138a757613b56916001820181556151ed565b5091613b618761521f565b96835560018301905f5b60038110613c355750505060039182613b85878a5161528f565b10613c22575b600281015f5b848110613bd257505090613bcc929154847fb9202018de9f7eba7cdf3f805001edb29b6629a2fd89f5bd9c667a9d99450f655f80a39461522d565b92613b12565b613be5613bdf828a61522d565b8b61523a565b5190825491600160401b8310156138a757613c07836001809501865585615130565b819291549060031b91821b915f19901b191617905501613b91565b9150613c2f85885161528f565b91613b8b565b600190613c586001600160a01b03613c50613bdf848761522d565b51168561524e565b865f52600a60205260405f2086545f5260205260405f20828060a01b03613c82613bdf848761522d565b5116838060a01b03165f5260205260405f208260ff1982541617905501613b6b565b5082906003613cb482845161528f565b1015613ce0575b601060ff19600454161760045560106003545f516020615ac65f395f51905f525f80a3005b825f52601060205260405f208054600160401b8110156138a757613d0991600182018155615206565b50915f83555f9160018401925b60038110613d50575050505054907fcba6d1415af496acc567dab38af44a786626bc3e3c6d0057a8b34329bb31e6ef5f80a3808080613cbb565b600190613d796001600160a01b03613d71613d6b848761522d565b8761523a565b51168661524e565b865f52601160205260405f2086545f5260205260405f20828060a01b03613da3613d6b848761522d565b5116838060a01b03165f5260205260405f208260ff1982541617905501613d16565b5060038501808611611688578751811190811591613de4575b50613b28565b90506002860180871161168857885110159081613e03575b5088613dde565b905087511088613dfc565b8451604051613e4581613e37602082019486888791606093918352602083015260408201520190565b03601f1981018352826151b4565b51902090600181019182821161168857613e9492613e6291615910565b613e6c818861523a565b5190613e83613e7b848a61523a565b51918961523a565b52613e8e828861523a565b52615904565b80613b05565b634607f5d560e01b5f5260045ffd5b9093503d805f833e613ebb81836151b4565b810190602081830312610496578051906001600160401b03821161049657019080601f83011215610496578151613ef1816158c8565b92613eff60405194856151b4565b81845260208085019260051b82010192831161049657602001905b828210613f2b575050509284613ac0565b8151815260209182019101613f1a565b631571768360e31b5f5260045ffd5b613f548183615130565b90546040516310ab556b60e21b815260039290921b1c6001600160a01b031660048201819052909190602083602481895afa8015610ab0576001935f91613fbd575b50613fa3575b5001613a3b565b613fb6613faf8661521f565b958a61523a565b5289613f9c565b613fd5915060203d8111610aa957610a9b81836151b4565b8b613f96565b613fe58183615130565b90546040516310ab556b60e21b815260039290921b1c6001600160a01b03166004820152602081602481875afa908115610ab0575f91614042575b5061402e575b600101613a02565b9461403a60019161521f565b959050614026565b61405a915060203d8111610aa957610a9b81836151b4565b88614020565b6366d7bd4760e01b5f5260045ffd5b636be945dd60e01b5f5260045ffd5b34610496576020366003190112610496576004355f52601e602052602060405f2054604051908152f35b34610496575f366003190112610496576020601b54604051908152f35b34610496575f366003190112610496576140dd6156fb565b60ff60045416601a8110156104825760020361418a57600154600254604051633c1a477960e11b81526001600160a01b0391821660048201529160209183916024918391165afa908115610ab0575f9161416b575b501561415c57600360ff196004541617600455600380545f516020615ac65f395f51905f525f80a3005b63f831404160e01b5f5260045ffd5b614184915060203d602011610aa957610a9b81836151b4565b81614132565b62bf1abb60e31b5f5260045ffd5b34610496575f3660031901126104965760206001600160401b03601c5416604051908152f35b34610496576141cc36615026565b60039391935482036105b45760ff60045416601a8110156104825760120361440257815f52600960205260405f2054831015611cc0575f828152600a60209081526040808320868452825280832033845290915290205460ff1615610eee576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610ab0575f916143e3575b5015610ec0575f828152600f60209081526040808320868452825280832033845290915290205460ff16156143d4575f828152600c60209081526040808320868452825280832033845290915290205460ff166125135783156125045760408051602081018681529181019290925233606083015260808201839052905f60a082015260c080820185905281526142fd60e0826151b4565b5190205f828152600e602090815260408083208684528252808320338452909152902054036143c5575f818152600c60209081526040808320858452825280832033808552908352818420805460ff19166001179055848452600b8352818420868552835281842081855283529281902086905551858152849184917e4ff64f8cc2f88940c535cfe6face7248dc5ebf1faba271d2f8052017e747c79190a45f52600d60205260405f20905f5260205260405f20905f5260205260405f206124f1815461521f565b635130b96560e11b5f5260045ffd5b632ccbe95160e01b5f5260045ffd5b6143fc915060203d602011610aa957610a9b81836151b4565b85614265565b636f5ecfff60e11b5f5260045ffd5b346104965760203660031901126104965760043561442d6156fb565b60035481036105b45760ff60045416601a81101561048257601703614a9b57600154604051631bad9e0560e11b815290602090829060049082906001600160a01b03165afa908115610ab0575f91614a69575b509061271061449160235484615179565b0491815f52600960205260405f20905f925b8254841015614775576144b684846151ed565b5091825492600460018201910154945f5b8254811015614762576144da8184615130565b90545f878152600c602090815260408083208b8452825280832060039590951b9390931c6001600160a01b0316808352939052205490919060ff1661463857891561461e57600154604051630b6f9cd360e11b815260048101849052602481018c90527320a3a3afaa18afa727afa9aaa126a4a9a9a4a7a760611b6044820152606481018890529290602090849060849082905f906001600160a01b03165af18015610ab0578789918d955f916145d4575b506001955f516020615ae65f395f51905f52915b604080517320a3a3afaa18afa727afa9aaa126a4a9a9a4a7a760611b81526020810193909352820152606090a45b016144c7565b95505050506020833d8211614616575b816145f1602093836151b4565b810103126104965787875f516020615ae65f395f51905f528d6001965191509561458c565b3d91506145e4565b60019187875f516020615ae65f395f51905f528d5f6145a0565b855f52600b60205260405f20875f5260205260405f2060018060a01b0383165f526020528760405f205403614671575b600191506145ce565b600154604051637dd1602d60e11b81526004810184905260248101879052734147475f54315f4241445f434f4e53454e53555360601b60448201529290602090849060649082905f906001600160a01b03165af1928315610ab0578789915f9561471e575b5060408051734147475f54315f4241445f434f4e53454e53555360601b8152602081018a9052908101959095526001945f516020615ae65f395f51905f5290606090a4614668565b945050506020833d821161475a575b8161473a602093836151b4565b810103126104965791519187875f516020615ae65f395f51905f526146d6565b3d915061472d565b50959193505060019193500192916144a3565b8491815f52601060205260405f20905f925b8254841015614a445761479a8484615206565b50805460038201549490937320a3a3afaa192fa727afa9aaa126a4a9a9a4a7a760611b929091734147475f54325f4241445f434f4e53454e53555360601b91600101905f905b8254821015614a2f5786906147f58385615130565b60018060a01b0391549060031b1c1692825f52601460205260405f208a5f5260205260405f2060018060a01b0385165f526020528c60ff60405f205416155f146149255750878d8015614909579060846020925f60018060a01b03600154166040519889958694630b6f9cd360e11b86528c60048701526024860152604485015260648401525af1938415610ab0578a8a8f95968b975f916148c6575b5060019596975f516020615ae65f395f51905f52915b60408051928352602083018b9052820152606090a45b0191506147e0565b95965050505093506020823d8211614901575b816148e6602093836151b4565b8101031261049657905187938d9392918b908b906001614892565b3d91506148d9565b9490600193945f516020615ae65f395f51905f528d925f6148a8565b9390919250885f52601360205260405f208a5f5260205260405f2060018060a01b0382165f526020528a60405f205403614963575b506001906148be565b9250905f91602060018060a01b0360015416606460405180968193637dd1602d60e11b83528960048401528c60248401528b60448401525af1938415610ab0578a8a8f955f976149e4575b505f516020615ae65f395f51905f5260019596978b8b60405192839260608401928452602084015260408301520390a49061495a565b949550505093506020823d8211614a27575b81614a03602093836151b4565b81010312610496579051928c9291908a8a5f516020615ae65f395f51905f526149ae565b3d91506149f6565b60019099019897509295506147879350505050565b601860ff19600454161760045560186003545f516020615ac65f395f51905f525f80a3005b90506020813d602011614a93575b81614a84602093836151b4565b81010312610496575182614480565b3d9150614a77565b6360cd0e9960e01b5f5260045ffd5b3461049657602036600319011261049657600435614ac66156fb565b60035481036105b45760ff60045416601a81101561048257600e03614bc1576002546040516356af71c960e11b81526004810183905290602090829060249082905f906001600160a01b03165af1908115610ab0575f91614ba2575b5015614b9357614b496001600160401b03601c5460801c166001600160401b034316615159565b905f52601d6020526001600160401b0360405f2091166001600160401b0319825416179055600f60ff196004541617600455600f6003545f516020615ac65f395f51905f525f80a3005b6307fb097360e41b5f5260045ffd5b614bbb915060203d602011610aa957610a9b81836151b4565b82614b22565b63183dd1d160e21b5f5260045ffd5b3461049657614bde36614ff6565b92614be76156fb565b805f526026602052614bfc8360405f20615093565b825f5260205260405f2060018060a01b0381541615614fcd576002810190815460ff8160c01c16614fbe5761ffff60c01b191686151560c81b60ff60c81b1617600160c01b1782558515614f795760030190815415614f6a57614ca190614c7a6001600160401b03601c5460401c166001600160401b034216615159565b67ffffffffffffffff60401b82549160401b169067ffffffffffffffff60401b1916179055565b5460028410156104825761266291614d086117969286155f14614f4957825f526009602052614ce76001614cd88860405f206151ed565b50015b6040519586809261529c565b825f526006602052614d0260405f206040519687809261529c565b8461591a565b60405193614d176080866151b4565b60038552602085019160603684375f975f5b8651811080614f3f575b15614e0e57614d55866001600160a01b03614d4e848b61523a565b5116615a80565b1580614d9c575b90614d6a91614d6f5761521f565b614d29565b6001600160a01b03614d81828a61523a565b5116614d96614d8f8d61521f565b9c8b61523a565b5261521f565b506001546001600160a01b039081169190602090614dba838b61523a565b51166024604051809581936310ab556b60e21b835260048301525afa8015610ab057614d6a925f91614df0575b50909150614d5c565b614e08915060203d8111610aa957610a9b81836151b4565b8c614de7565b50945094919660039196935010613f3b57835f526027602052614e348260405f20615093565b855f5260205260405f20958151906001600160401b0382116138a757600160401b82116138a7578754828955808310614f19575b50965f5260205f20965f5b828110614efc5750505084955093837f1aa282aabded606592d2cccbd78439ed03820b25543d6161b213b4b8c9651993614eea7f5f2ed0b8a76b3e4bf8076e2e31d83602fef08c30bdb2b069d1db259c176e63ef9697604051918291614ed9838961518c565b6040602084015260408301906150a9565b0390a3612ace60405192839283615199565b81516001600160a01b0316818a0155602090910190600101614e73565b885f528260205f2091820191015b818110614f345750614e68565b5f8155600101614f27565b5060038a10614d33565b825f526010602052614ce76001614f638860405f20615206565b5001614cdb565b632d5720c760e11b5f5260045ffd5b815460ff60d01b1916600160d01b1790915560010154919390927f5f2ed0b8a76b3e4bf8076e2e31d83602fef08c30bdb2b069d1db259c176e63ef92612b7a90615721565b63450cfd7f60e11b5f5260045ffd5b63167f349d60e11b5f5260045ffd5b34610496575f366003190112610496576020906005548152f35b60809060031901126104965760043590602435600281101561049657906044359060643580151581036104965790565b60809060031901126104965760043590602435906044359060643590565b606090600319011261049657600435906024356002811015610496579060443590565b602435906001600160a01b038216820361049657565b600435906001600160a01b038216820361049657565b906002811015610482575f5260205260405f2090565b90602080835192838152019201905f5b8181106150c65750505090565b82516001600160a01b03168452602093840193909201916001016150b9565b6040906003190112610496576004359060243590565b606090600319011261049657600435906024359060443590565b34610496575f36600319011261049657602060405160038152f35b8054821015615145575f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b906001600160401b03809116911601906001600160401b03821161168857565b8181029291811591840414171561168857565b9060028210156104825752565b9092916020906151ad83604081019661518c565b1515910152565b90601f801991011681019081106001600160401b038211176138a757604052565b90816020910312610496575180151581036104965790565b8054821015615145575f52600560205f20910201905f90565b8054821015615145575f5260205f209060021b01905f90565b5f1981146116885760010190565b9190820180921161168857565b80518210156151455760209160051b010190565b8054600160401b8110156138a75761526b91600182018155615130565b81546001600160a01b0393841660039290921b91821b9390911b1916919091179055565b9190820391821161168857565b90602082549182815201915f5260205f20905f5b8181106152bd5750505090565b82546001600160a01b03168452602090930192600192830192016152b0565b805f52601f6020526001600160401b0360405f205416801561541a57815f526020805260405f205461540b57804311156153fc5740801561538157604051602081019182528260408201526210555160ea1b6060820152604381526153426063826151b4565b5190205f828152602080805260409182902083905590519182527fe53c25b43758bf910ba120923c7295303dcbc2369273c7985227b00293f3b56991a2565b506153a36001600160401b03601c5460801c166001600160401b034316615159565b5f828152601f6020908152604091829020805467ffffffffffffffff19166001600160401b0394909416938417905590519182527f7e22e73285f299179a84bb3388a9dd94b3eac27a6de162455a42ee6ce6706f9491a2565b63de3f681d60e01b5f5260045ffd5b638d10dfef60e01b5f5260045ffd5b63906135ab60e01b5f5260045ffd5b805f52601d6020526001600160401b0360405f205416801561556a57815f52601e60205260405f205461555b578043111561554c574080156154d157604051602081019182528260408201526241474760e81b6060820152604381526154906063826151b4565b5190205f828152601e602090815260409182902083905590519182527f9dcaca5280e612f442cef14eea003bbece818a3fde40db4dc4a3887682aa07b691a2565b506154f36001600160401b03601c5460801c166001600160401b034316615159565b5f828152601d6020908152604091829020805467ffffffffffffffff19166001600160401b0394909416938417905590519182527fbed5c9fb50b85a70b1ea0ab5bf64bbf34ba76521149788c3873abaf955d545c291a2565b632f9cb5e160e21b5f5260045ffd5b634653993f60e01b5f5260045ffd5b631c237c4160e31b5f5260045ffd5b90815f52602660205261558f8160405f20615093565b835f5260205260405f209060018060a01b0382541615614fcd57600282019081549260ff8460c01c16614fbe576003019283546156ec576001600160401b039060801c16804311156156dd574091821561565e57506002811015610482577ff1ac2abd9bfa2c470babc8a68c69061d1661d798dedc05ba6f20a5fdb6bfe51592604092835160208101918252868582015260ff60f81b8460f81b166060820152876061820152606181526156446081826151b4565b5190208091556156568351809361518c565b6020820152a3565b7f2bee25e87a4aab24a6e926580a5c7026c624938721cc23acc05733fe7a4deebd9350604092506001600160401b03906156cc6156a483601c5460801c16844316615159565b825467ffffffffffffffff60801b191660809190911b67ffffffffffffffff60801b16178255565b5460801c166156568351809361518c565b63221198d360e11b5f5260045ffd5b63d6e8b91560e01b5f5260045ffd5b5f546001600160a01b0316330361570e57565b63118cdaa760e01b5f523360045260245ffd5b5f9080156158c4578060011c91615738838361528f565b601a546001600160a01b031693909190843b15610496575f94602486926040519788938492630852cd8d60e31b845260048401525af1938415610ab0576004946158b1575b50600154604051639958065160e01b81529460209186919082906001600160a01b03165afa9384156158a457819461585c575b506001600160a01b038416156157e85750601a546157e3936157db92916001600160a01b03166159ff565b60215461522d565b602155565b601a54929350916001600160a01b031690813b15615858578291602483926040519485938492630852cd8d60e31b845260048401525af1801561584d57615835575b50506157e3906157db565b6158408280926151b4565b61584a578061582a565b80fd5b6040513d84823e3d90fd5b8280fd5b9093506020813d60201161589c575b81615878602093836151b4565b8101031261589857516001600160a01b038116810361589857925f6157b0565b8380fd5b3d915061586b565b50604051903d90823e3d90fd5b6158bd91505f906151b4565b5f5f61577d565b5050565b6001600160401b0381116138a75760051b60200190565b600560ff19600454161760045560056003545f516020615ac65f395f51905f525f80a3565b8015611688575f190190565b81156120a2570690565b908151600281106159c5575f19810190811161168857805b61593b57505050565b825160405161596481613e37602082019486888791606093918352602083015260408201520190565b519020906001810191828211611688576159bf9261598191615910565b6001600160a01b03615993828761523a565b5116906159b46001600160a01b036159ab858961523a565b5116918761523a565b52613e8e828661523a565b80615932565b505050565b5f516020615b065f395f51905f525c6159f05760015f516020615b065f395f51905f525d565b633ee5aeb560e01b5f5260045ffd5b916040519163a9059cbb60e01b5f5260018060a01b031660045260245260205f60448180865af19060015f5114821615615a5f575b60405215615a3f5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b906001811516615a7757823b15153d15161690615a34565b503d5f823e3d90fd5b5f5b8251811015615abe576001600160a01b03615a9d828561523a565b51166001600160a01b03831614615ab657600101615a82565b505050600190565b5050505f9056fea8f52d7b4405100a39ccc552ccc9426d860b1e822c71e97675d1623b49db6babd31b1ab070aba73480b7f863f37fefd98c16862672290e5cadbf1212bf1630cd9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a26469706673582212202b6f4bdc7073b7a7cebe01216c59a6b0b173be6a2da033ef5d82a54b30e850cf64736f6c634300081c0033" + "bytecode": "0x60a03461016957601f615d0338819003918201601f19168301916001600160401b0383118484101761016d5780849260409485528339810103126101695780516001600160a01b038116919082900361016957602001513315610156575f8054336001600160a01b0319821681178355604051949290916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a35f60035568056bc75e2d63100000601b55601c80546001600160c01b0319167007000000000002a3000000000000015180179055610bb8602355801561014757600180546001600160a01b0319169190911790556080526004805460ff191690555f807fa8f52d7b4405100a39ccc552ccc9426d860b1e822c71e97675d1623b49db6bab8180a3615b8190816101828239608051818181611ec40152612cb30152f35b63f70949a160e01b5f5260045ffd5b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f3560e01c90816304501eec146150025750806309adabf514614bf65780630a2b338214614ad05780630ac04340146144375780630e84af23146141e4578063117f5f92146141be5780631511c3a6146140eb57806317baaf20146140ce578063199efdf4146140a45780631a336bfc146139b95780631c2c56bc1461398f5780631d9e7295146135565780631dfa1ac2146134f75780631eb30bfe146134b35780631f1761d0146134455780631f83192c146134165780632059a8991461335d5780632450922014613340578063246116e6146132a557806324e168af146132595780632a9cafbe146132385780632b143f03146131ca5780632d8d2d63146130c857806331823a22146130ad57806332280eed14612edd57806337d274c914612eaa5780633a0ea70414612e815780633b0ff6bb14612e645780633b6dffe514612e115780633ce58bba14612da357806342f7750214612cd657806343cdaad214612c9c57806346bf820a14612c2e5780634774b52414612bb25780634e649ca7146127d3578063530a9fb11461274c578063576a91b31461261b57806358aed7bc146125f3578063595e3411146125855780635b0283ef1461231d5780635bfd7d3a146122685780635fec207c14611e0d5780636336894114611d9f578063715018a614611d48578063747a21fe14611d2c57806378737a3914611d045780637c4596c714611b59578063868276ab14611b3c57806388c2978514611a9b578063894f4dd114611a635780638b1de7be146119f55780638cfbd515146119cf5780638da5cb5b146119a8578063914b92cd146116dd5780639de2f2a0146115c2578063a120c41a1461159c578063a5031f6414611581578063a7e10e2814611549578063aafebd5014611479578063ac0c7d5114611450578063ac9fc10014611437578063acaf042a146104a5578063adabd80114611404578063b4b9db4114610f94578063b593d2d51461139f578063b61998b6146112a8578063b6f104a81461127e578063b75997701461124f578063b7696bd6146111de578063b7a0e41c14610f99578063bee3fe1714610f94578063c0aeb10d14610f26578063c6e8fc6f14610d6c578063c9ad03c814610d34578063cad783b214610ad5578063cb404a75146109da578063cc279009146109bd578063d6f353a114610995578063d86795ac14610927578063e2852e3f146108fe578063e547118e14610890578063e57c943e14610847578063e6f5e11914610694578063e9c8bb521461066b578063ead1df17146105ce578063f0f95f6414610554578063f2fde38b146104cf578063f34db55e146104a55763f4eed253146103ef575f80fd5b346104a15760203660031901126104a1576104086150a3565b610410615721565b60ff60045416601a81101561048d5761047e576001600160a01b0316801561046f576bffffffffffffffffffffffff60a01b6002541617600255600160ff19600454161760045560016003545f516020615aec5f395f51905f525f80a3005b63f70949a160e01b5f5260045ffd5b631f85a85960e31b5f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b346104a15760203660031901126104a1576004355f526012602052602060405f2054604051908152f35b346104a15760203660031901126104a1576104e86150a3565b6104f0615721565b6001600160a01b03168015610541575f80546001600160a01b03198116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b631e4fbdf760e01b5f525f60045260245ffd5b346104a1576105623661510b565b9061056b615721565b60035481036105bf5760ff60045416601a81101561048d57601681141590816105b3575b506105a4575f52601260205260405f20555f80f35b63c7a0e6e160e01b5f5260045ffd5b6004915014158361058f565b6347ca2dcf60e01b5f5260045ffd5b346104a1575f3660031901126104a1576105e66159f0565b335f52602860205260405f2054801561065d57335f81815260286020526040812055601a5461061f9183916001600160a01b0316615a25565b6040519081527fa09dd3797281514bc434a54ae8b69a6d02ae2fdbfab7c86dc8975163fd0045d060203392a25f5f516020615b2c5f395f51905f525d005b621db6b160e21b5f5260045ffd5b346104a1575f3660031901126104a15760ff60045416604051601a82101561048d576020918152f35b346104a15760803660031901126104a15760243560043560028210156104a15760643590604435906001600160a01b03831683036104a15760a09361079b575f818152600f6020908152604080832085845282528083205f196001891b019690961680845295825280832054848452600e83528184208685528352818420878552835281842054858552600c84528285208786528452828520888652845282852054868652600b8552838620888752855283862098865297845282852054958552600d84528285209685529583528184208585529092529091205460ff9182169490939116905b604051941515855260208501521515604084015260608301526080820152f35b5f81815260176020908152604080832085845282528083205f196001891b01969096168084529582528083205484845260168352818420868552835281842087855283528184205485855260148452828520878652845282852088865284528285205486865260138552838620888752855283862098865297845282852054958552601584528285209685529583528184208585529092529091205460ff91821694909391169061077b565b346104a15760403660031901126104a15761086061508d565b6004355f52600760205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b346104a15760203660031901126104a1576108a9615721565b600354600435036105bf5760ff60045416601a81101561048d576005036108ef57600660ff19600454161760045560066003545f516020615aec5f395f51905f525f80a3005b6321012b6760e01b5f5260045ffd5b346104a15760203660031901126104a1576004355f5260208052602060405f2054604051908152f35b346104a15760203660031901126104a157610940615721565b600354600435036105bf5760ff60045416601a81101561048d5760130361098657601460ff19600454161760045560146003545f516020615aec5f395f51905f525f80a3005b631416b1a360e01b5f5260045ffd5b346104a1575f3660031901126104a1576001546040516001600160a01b039091168152602090f35b346104a1575f3660031901126104a1576020602154604051908152f35b346104a15760203660031901126104a1576004356109f6615721565b60035481036105bf5760ff60045416601a81101561048d57601603610ac65760025460405163cb404a7560e01b81526004810192909252602090829060249082905f906001600160a01b03165af1908115610abb575f91610a8c575b5015610a7d57601760ff19600454161760045560176003545f516020615aec5f395f51905f525f80a3005b634b058d6560e11b5f5260045ffd5b610aae915060203d602011610ab4575b610aa681836151da565b8101906151fb565b81610a52565b503d610a9c565b6040513d5f823e3d90fd5b63b52ae37160e01b5f5260045ffd5b346104a15760203660031901126104a157600435610af1615721565b60035481036105bf5760ff60045416601a81101561048d57601503610d25575f81815260106020526040812091426001600160401b0316915b8354811015610d0057610b3d818561522c565b505f5f905f935f9760018501985b8954811015610c5d57610b5e818b615156565b60018060a01b0391549060031b1c16885f52601460205260405f20875490815f5260205260405f2060018060a01b0383165f5260205260ff60405f205416610bed575b5090600191895f52601860205260405f2090838060a01b03165f5260205260405f20610bcd8154615245565b9055885f52601960205260405f20610be58154615245565b905501610b4b565b97610bf790615245565b97895f52601360205260405f20815f5260205260405f2060018060a01b0383165f5260205260405f2054908a5f52601560205260405f20905f5260205260405f20815f5260205260405f2054878111610c51575b50610ba1565b9650945060018c610c4b565b50969750929150928315610cf257600211610ce3577f9bca595da6d2d1ae0f0ef7abe316afdd467890adf8eb5a2234a8af80f8d151b760206001948360028895018760ff19825416179055816003820155845f526025835260405f2090545f52825260405f20896001600160401b0319825416179055604051908152a201929192610b2a565b6380c2216560e01b5f5260045ffd5b62b5305b60e51b5f5260045ffd5b601660ff19600454161760045560166003545f516020615aec5f395f51905f525f80a3005b63c4d5525560e01b5f5260045ffd5b346104a15760203660031901126104a1576001600160a01b03610d556150a3565b165f526028602052602060405f2054604051908152f35b346104a157610d7a36615121565b9160035481036105bf5760ff60045416601a81101561048d57601403610f175781610f08575f818152601160209081526040808320858452825280832033845290915290205460ff1615610ef9576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610abb575f91610eda575b5015610ecb578215610ebc575f818152601760209081526040808320858452825280832033845290915290205460ff16610ead575f818152601660209081526040808320858452825280832033808552908352818420879055848452601783528184208685528352818420818552835292819020805460ff191660011790555194855290937f430cf27dfb66aa7260b606dbba6d3d2600983f065b1e30ffcab0e6457f3776c39190a4005b6343a311af60e01b5f5260045ffd5b630e660de560e11b5f5260045ffd5b636936304560e01b5f5260045ffd5b610ef3915060203d602011610ab457610aa681836151da565b84610e02565b6328e28e8360e01b5f5260045ffd5b6327fda01d60e11b5f5260045ffd5b636582261960e11b5f5260045ffd5b346104a15760203660031901126104a157610f3f615721565b600354600435036105bf5760ff60045416601a81101561048d57600603610f8557600760ff19600454161760045560076003545f516020615aec5f395f51905f525f80a3005b6301876fed60e21b5f5260045ffd5b61513b565b346104a15760203660031901126104a157600435610fb5615721565b60035481036105bf5760ff60045416601a81101561048d576012036111cf575f81815260096020526040812091426001600160401b0316915b83548110156111aa576110018185615213565b5060018101945f9485938492835b895481101561112257611022818b615156565b60018060a01b0391549060031b1c16885f52600c60205260405f20875490815f5260205260405f2060018060a01b0383165f5260205260ff60405f2054166110b1575b5090600191895f52601860205260405f2090838060a01b03165f5260205260405f206110918154615245565b9055885f52601960205260405f206110a98154615245565b90550161100f565b976110bb90615245565b97895f52600b60205260405f20815f5260205260405f2060018060a01b0383165f5260205260405f2054908a5f52600d60205260405f20905f5260205260405f20815f5260205260405f2054878111611115575b50611065565b909a50955060018c61110f565b5096925092939096508115610cf257600211610ce357837fb1becb4dbd2440a1dbd1bac657cc9dbbe112f85f7a99012f71aff4bab4acc5a86020856003600197018760ff19825416179055846004820155835f526024825260405f2081545f52825260405f208a6001600160401b03198254161790555493604051908152a301929192610fee565b601360ff19600454161760045560136003545f516020615aec5f395f51905f525f80a3005b638f5431c960e01b5f5260045ffd5b346104a15760803660031901126104a15760243560028110156104a157611215606435916004355f52602760205260405f206150b9565b6044355f5260205260405f2080548210156104a15760209161123691615156565b905460405160039290921b1c6001600160a01b03168152f35b346104a15761125d3661510b565b905f52600660205260405f2080548210156104a15760209161123691615156565b346104a15760203660031901126104a1576004355f526019602052602060405f2054604051908152f35b346104a15760803660031901126104a157600435602435906001600160401b0382168092036104a157604435906001600160401b038216928383036104a157606435936001600160401b0385168086036104a157611304615721565b8315918215611396575b821561138d575b508115611384575b8115611378575b506113695767ffffffffffffffff60401b91601b55601c54936001600160401b0360801b9060801b16936001600160401b0360c01b16179160401b161717601c555f80f35b631218659f60e01b5f5260045ffd5b61010091501185611324565b8015915061131d565b15915086611315565b8315925061130e565b346104a15760203660031901126104a1576113b8615721565b60ff60045416601a81101561048d576003036113f55760048035600555805460ff1916811781556003545f516020615aec5f395f51905f525f80a3005b631529e88760e31b5f5260045ffd5b346104a15760203660031901126104a1576004355f52601f60205260206001600160401b0360405f205416604051908152f35b346104a15761144e6114483661506a565b9161559f565b005b346104a1575f3660031901126104a15760206001600160401b03601c5460801c16604051908152f35b346104a1575f3660031901126104a157611491615721565b60ff60045416601a81101561048d5760010361153a57600154604051633c1a477960e11b815230600482015290602090829060249082906001600160a01b03165afa908115610abb575f9161151b575b501561150c57600260ff19600454161760045560026003545f516020615aec5f395f51905f525f80a3005b634f3572c560e01b5f5260045ffd5b611534915060203d602011610ab457610aa681836151da565b816114e1565b632748c52f60e21b5f5260045ffd5b346104a1576115573661510b565b905f52602560205260405f20905f5260205260206001600160401b0360405f205416604051908152f35b346104a1575f3660031901126104a157602060405160018152f35b346104a15760203660031901126104a15760043560035481036105bf5761144e9061544f565b346104a1576115d03661510b565b6115d8615721565b60ff60045416601a81101561048d57600481141590816116d1575b506116c257600354600181018091116116ae5782036105bf576002546001600160a01b031691823b156104a15760405190635a6f53a760e01b825260048201525f81602481865afa8015610abb5761169e575b50813b156104a1575f91602483926040519485938492633f23a7b760e21b845260048401525af18015610abb5761168e575b611683600354615245565b60035561144e615905565b5f611698916151da565b80611678565b5f6116a8916151da565b82611646565b634e487b7160e01b5f52601160045260245ffd5b63205400e160e21b5f5260045ffd5b601991501415836115f3565b346104a1576116eb3661506a565b825f9392935260266020526117038360405f206150b9565b815f5260205260405f2090600282019283549260ff8460c01c168061199b575b1561198c5760ff8460d01c1661197d576001600160401b038460401c1642111561196e5760ff60d01b19909316600160d01b17909355600182015491546001600160a01b03165f90815260286020526040902080546117bc9460049490929161178c9190615253565b9055805f5260276020526117a38560405f206150b9565b825f526020526117c360405f20604051958680926152c2565b03856151da565b600154604051631bad9e0560e11b81529360209185919082906001600160a01b03165afa928315610abb575f9361193a575b505f5b8451811015611905576001545f91906020906001600160a01b0390811690606490611823858b615260565b51166040519586938492637dd1602d60e11b845260048401528a60248401527814cd17d1949154d217d4d55091d493d55417d512535153d555603a1b60448401525af18015610abb575f906118d3575b6001925084845f516020615b0c5f395f51905f5260606001600160a01b0361189b878d615260565b511694604051907814cd17d1949154d217d4d55091d493d55417d512535153d555603a1b82528b60208301526040820152a4016117f8565b506020823d82116118fd575b816118ec602093836151da565b810103126104a15760019151611873565b3d91506118df565b507ff42fd7a6ed54d90eb785299babf26fd989424f01778ddc07a710b65b7effc0ca60208661193760405180926151b2565ba3005b9092506020813d602011611966575b81611956602093836151da565b810103126104a1575191856117f5565b3d9150611949565b63aa161f2160e01b5f5260045ffd5b631a3b0bef60e01b5f5260045ffd5b630a18626760e21b5f5260045ffd5b5060ff8460c81c16611723565b346104a1575f3660031901126104a1575f546040516001600160a01b039091168152602090f35b346104a15760203660031901126104a15760043560035481036105bf5761144e90615302565b346104a15760203660031901126104a157611a0e615721565b600354600435036105bf5760ff60045416601a81101561048d57600703611a5457600860ff19600454161760045560086003545f516020615aec5f395f51905f525f80a3005b63ca5a3b5560e01b5f5260045ffd5b346104a157611a713661510b565b905f52602460205260405f20905f5260205260206001600160401b0360405f205416604051908152f35b346104a15760203660031901126104a157600435611ab7615721565b60ff60045416601a81101561048d5760048114159081611b30575b506116c257600354600181018091116116ae5781036105bf576002546001600160a01b031690813b156104a1575f90602460405180948193635a6f53a760e01b835260048301525afa8015610abb5761168e57611683600354615245565b60199150141582611ad2565b346104a1575f3660031901126104a1576020602254604051908152f35b346104a157611b6736615121565b9160035481036105bf5760ff60045416601a81101561048d57601103611cf557805f52600960205260405f2054821015611ce6575f818152600a60209081526040808320858452825280832033845290915290205460ff1615610ef9576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610abb575f91611cc7575b5015610ecb578215611cb8575f818152600f60209081526040808320858452825280832033845290915290205460ff16611ca9575f818152600e60209081526040808320858452825280832033808552908352818420879055848452600f83528184208685528352818420818552835292819020805460ff191660011790555194855290937fc33cb04355b8f36a3e3860fb823d630cc37a5833cf62828f742e70bdd3751eac9190a4005b63432cb74f60e11b5f5260045ffd5b63d5d0c92560e01b5f5260045ffd5b611ce0915060203d602011610ab457610aa681836151da565b84611bfe565b633b554d2360e11b5f5260045ffd5b637100347160e01b5f5260045ffd5b346104a1575f3660031901126104a1576002546040516001600160a01b039091168152602090f35b346104a1575f3660031901126104a157602060405161012c8152f35b346104a1575f3660031901126104a157611d60615721565b5f80546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346104a15760203660031901126104a157611db8615721565b600354600435036105bf5760ff60045416601a81101561048d57601003611dfe57601160ff19600454161760045560116003545f516020615aec5f395f51905f525f80a3005b63076b0ff760e01b5f5260045ffd5b346104a15760203660031901126104a15760043560035481036105bf5760ff60045416601a81101561048d57600603612259576001546040516310ab556b60e21b81523360048201526001600160a01b0390911690602081602481855afa908115610abb575f9161223a575b5015610ecb575f82815260076020908152604080832033845290915290205460ff1661222b57815f52600660205261012c60405f2054101561221c57604051634ab989d560e01b81527f00000000000000000000000000000000000000000000000000000000000000006004820152602081602481855afa908115610abb575f916121ea575b508015159081612183575b506121745760405163f2d679bf60e01b8152602081600481855afa908115610abb575f91612142575b5080611fea575b5050805f526006602052611f513360405f20615274565b5f8181526007602090815260408083203384529091529020805460ff19166001908117909155546001600160a01b0316803b156104a1575f80916024604051809481936347e91db160e01b83523360048401525af18015610abb57611fda575b5033907f88f3e7b00fb6a3104c7d2c8d3ff669b012e5724bd38e50559cb41d177a3a8ba15f80a3005b5f611fe4916151da565b81611fb1565b6040516303d3b32360e51b8152336004820152602081602481865afa908115610abb575f91612110575b50604051631bad9e0560e11b815290602082600481875afa918215610abb575f926120dc575b5081156120c857602492602092612051920461519f565b926040519283809263b998328360e01b82523360048301525afa908115610abb575f91612096575b501015612087578180611f3a565b634f21e5ed60e11b5f5260045ffd5b90506020813d6020116120c0575b816120b1602093836151da565b810103126104a1575183612079565b3d91506120a4565b634e487b7160e01b5f52601260045260245ffd5b9091506020813d602011612108575b816120f8602093836151da565b810103126104a15751908561203a565b3d91506120eb565b90506020813d60201161213a575b8161212b602093836151da565b810103126104a1575184612014565b3d915061211e565b90506020813d60201161216c575b8161215d602093836151da565b810103126104a1575183611f33565b3d9150612150565b631931d5eb60e01b5f5260045ffd5b6040516303d3b32360e51b8152336004820152909150602081602481865afa908115610abb575f916121b8575b501083611f0a565b90506020813d6020116121e2575b816121d3602093836151da565b810103126104a15751846121b0565b3d91506121c6565b90506020813d602011612214575b81612205602093836151da565b810103126104a1575183611eff565b3d91506121f8565b63083a2c5d60e01b5f5260045ffd5b6306d4010b60e21b5f5260045ffd5b612253915060203d602011610ab457610aa681836151da565b83611e79565b6331fe69e760e21b5f5260045ffd5b346104a15760203660031901126104a157600435612284615721565b60035481036105bf5760ff60045416601a81101561048d57600a0361230e576122c46001600160401b03601c5460801c166001600160401b03431661517f565b905f52601f6020526001600160401b0360405f2091166001600160401b0319825416179055600b60ff196004541617600455600b6003545f516020615aec5f395f51905f525f80a3005b630d1f756d60e31b5f5260045ffd5b346104a15761232b3661504c565b60039391935482036105bf5760ff60045416601a81101561048d576015036125765782610f08575f828152601160209081526040808320868452825280832033845290915290205460ff1615610ef9576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610abb575f91612557575b5015610ecb575f828152601760209081526040808320868452825280832033845290915290205460ff1615612548575f828152601460209081526040808320868452825280832033845290915290205460ff1661253957831561252a576040805160208101868152918101929092523360608301526080820183905290600160a082015260c0808201859052815261244e60e0826151da565b5190205f82815260166020908152604080832086845282528083203384529091529020540361251b575f818152601460209081526040808320858452825280832033808552908352818420805460ff1916600117905584845260138352818420868552835281842081855283529281902086905551858152849184917ff381116b332b24a3860f90e240a69b31a4eb09c27b59cc54e049aad99897e97c9190a45f52601560205260405f20905f5260205260405f20905f5260205260405f206125178154615245565b9055005b634407f1db60e01b5f5260045ffd5b63010e92c160e11b5f5260045ffd5b636e479ac960e01b5f5260045ffd5b6343d466b760e11b5f5260045ffd5b612570915060203d602011610ab457610aa681836151da565b856123b5565b63e396ca2760e01b5f5260045ffd5b346104a15760203660031901126104a15761259e615721565b600354600435036105bf5760ff60045416601a81101561048d576011036125e457601260ff19600454161760045560126003545f516020615aec5f395f51905f525f80a3005b63e89736d560e01b5f5260045ffd5b346104a1575f3660031901126104a157601a546040516001600160a01b039091168152602090f35b346104a1576126293661510b565b9060035481116105bf57805f52600960205260405f205482101561273d5760019161265e915f52600960205260405f20615213565b50805490600281019060ff60038201541661268f60048301549261268860405180988193016152c2565b03866151da565b604051948580966020865491828152019081965f5260205f20905f5b81811061272457505050916126c76126dd9260209403896151da565b60405196875260a08388015260a08701906150cf565b958587036040870152519586815201925f955b80871061270c5750508394501515606084015260808301520390f35b909360208060019287518152019501960195906126f0565b825484528a9450602090930192600192830192016126ab565b631935ca8d60e01b5f5260045ffd5b346104a15761275a3661510b565b9081610f085760035481116105bf57600191612780915f52601060205260405f2061522c565b5080549060ff600282015416906127a66003820154916117bc60405180978193016152c2565b6127c260405194859485526080602086015260808501906150cf565b911515604084015260608301520390f35b346104a1576127e13661501c565b926127ea615721565b805f5260266020526127ff8360405f206150b9565b825f5260205260405f2060028101805460ff8160c01c1680612ba5575b1561198c5760ff8160d01c1661197d5760ff60d01b1916600160d01b1790558415612b6e57600181015490546001600160a01b03165f9081526028602052604090208054909161286b91615253565b9055600283101561048d5760046128ae948415805f14612b4d57835f5260096020526128b5600161289f8760405f20615213565b50015b604051988980926152c2565b03886151da565b8015612b2d57835f526009602052826128d18660405f20615213565b500154915b600154604051631bad9e0560e11b81529460209186919082906001600160a01b03165afa938415610abb575f94612af9575b505f5b8851811015612ab9578215612a7857855f52600b60205260405f20875f5260205260405f2060018060a01b03612941838c615260565b511660018060a01b03165f526020528360405f20545b03612a70576001545f91906020906001600160a01b039081169060649061297e858f615260565b51166040519586938492637dd1602d60e11b845260048401528b602484015275299a2fa4a72b20a624a22fa0a3a3a922a3a0aa24a7a760511b60448401525af18015610abb5788888c945f93612a29575b505f516020615b0c5f395f51905f5260606001966129f387898060a01b0392615260565b5116946040519075299a2fa4a72b20a624a22fa0a3a3a922a3a0aa24a7a760511b82528c60208301526040820152a45b0161290b565b94505050506020823d8211612a68575b81612a46602093836151da565b810103126104a157905189919088885f516020615b0c5f395f51905f526129cf565b3d9150612a39565b600190612a23565b855f52601360205260405f20875f5260205260405f2060018060a01b03612a9f838c615260565b511660018060a01b03165f526020528360405f2054612957565b509395925050507f9086299cfbdc88752f050229a9e7b3563c343b687b7fa5f7ace411c049301d1a9294505b612af4604051928392836151bf565b0390a3005b9093506020813d602011612b25575b81612b15602093836151da565b810103126104a157519288612908565b3d9150612b08565b835f5260106020526003612b448660405f2061522c565b500154916128d6565b835f5260106020526128b56001612b678760405f2061522c565b50016128a2565b92612ba060017f9086299cfbdc88752f050229a9e7b3563c343b687b7fa5f7ace411c049301d1a949693950154615747565b612ae5565b5060ff8160c81c1661281c565b346104a15760203660031901126104a157600435612bce615721565b80158015612c23575b612c145760407f6aa2abf97a85127b2d7e0a4a90f5bf57c815685cdf8a07985043f5298fd6190c91602354908060235582519182526020820152a1005b631e0e5cfb60e11b5f5260045ffd5b506127108111612bd7565b346104a15760203660031901126104a157612c47615721565b600354600435036105bf5760ff60045416601a81101561048d57601403612c8d57601560ff19600454161760045560156003545f516020615aec5f395f51905f525f80a3005b63106ff71f60e01b5f5260045ffd5b346104a1575f3660031901126104a15760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346104a15760203660031901126104a157600435612cf2615721565b60035481036105bf5760ff60045416601a81101561048d57601803612d945760018060a01b036002541690805f52601960205260405f2054823b156104a15760445f9283604051958694859363e13b7b3160e01b8552600485015260248401525af18015610abb57612d84575b601960ff19600454161760045560196003545f516020615aec5f395f51905f525f80a3005b5f612d8e916151da565b80612d5f565b6303858c7f60e01b5f5260045ffd5b346104a15760203660031901126104a157612dbc615721565b600354600435036105bf5760ff60045416601a81101561048d57600d03612e0257600e60ff196004541617600455600e6003545f516020615aec5f395f51905f525f80a3005b636487147760e11b5f5260045ffd5b346104a15760203660031901126104a1576004355f526006602052612e60612e45612e4c60405f20604051928380926152c2565b03826151da565b6040519182916020835260208301906150cf565b0390f35b346104a1575f3660031901126104a1576020600354604051908152f35b346104a1575f3660031901126104a15760206001600160401b03601c5460401c16604051908152f35b346104a15760203660031901126104a1576004355f52601d60205260206001600160401b0360405f205416604051908152f35b346104a15760203660031901126104a157600435612ef9615721565b600354808210908115613085575b501561304d57805f52600860205260ff60405f20541661300f57805f52600860205260405f20600160ff19825416179055805f52600660205260405f20905f5b8254811015612fc5576001546001600160a01b031690612f678185615156565b905460039190911b1c6001600160a01b0316823b156104a1575f92602484926040519586938492631bf8593960e31b845260048401525af1918215610abb57600192612fb5575b5001612f47565b5f612fbf916151da565b84612fae565b506002546001600160a01b031690813b156104a1575f9160248392604051948593849263224c19bb60e01b845260048401525af18015610abb5761300557005b5f61144e916151da565b60405162461bcd60e51b81526020600482015260166024820152751cdb1bdd1cc8185b1c9958591e481c995b19585cd95960521b6044820152606490fd5b60405162461bcd60e51b815260206004820152601060248201526f11d2481a185cc81b9bdd08195b99195960821b6044820152606490fd5b9050811480613095575b82612f07565b5060ff60045416601a81101561048d5760191461308f565b346104a1575f3660031901126104a157602060405160028152f35b346104a15760203660031901126104a1576004356130e4615721565b60035481036105bf5760ff60045416601a81101561048d57600b036131bb57805f526020805260405f205480156131ac57602090604460018060a01b0360025416935f60405195869485936363fd7a1160e11b8552600485015260248401525af1908115610abb575f9161318d575b501561317e57600c60ff196004541617600455600c6003545f516020615aec5f395f51905f525f80a3005b63611d8f8760e01b5f5260045ffd5b6131a6915060203d602011610ab457610aa681836151da565b81613153565b63dbbb808560e01b5f5260045ffd5b63c827e84760e01b5f5260045ffd5b346104a15760203660031901126104a1576131e3615721565b600354600435036105bf5760ff60045416601a81101561048d5760080361322957600960ff19600454161760045560096003545f516020615aec5f395f51905f525f80a3005b63768c7a4d60e11b5f5260045ffd5b346104a15760203660031901126104a157613251615721565b600435602255005b346104a15760203660031901126104a1576132726150a3565b61327a615721565b6001600160a01b0316801561046f576bffffffffffffffffffffffff60a01b601a541617601a555f80f35b346104a15760403660031901126104a157600435602435908115158092036104a1576132cf615721565b60035481036105bf5760ff60045416601a81101561048d57600c03613331576002546001600160a01b031691823b156104a15760445f928360405195869485936312308b7360e11b8552600485015260248401525af18015610abb5761300557005b630c14724760e31b5f5260045ffd5b346104a1575f3660031901126104a1576020602354604051908152f35b346104a15761338061336e3661506a565b92915f52602660205260405f206150b9565b905f5260205261012060405f2060018060a01b038154169060ff600182015491600360028201549101549260405194855260208501526001600160401b03811660408501526001600160401b038160401c1660608501526001600160401b038160801c166080850152818160c01c16151560a0850152818160c81c16151560c085015260d01c16151560e0830152610100820152f35b346104a15760203660031901126104a1576004355f526008602052602060ff60405f2054166040519015158152f35b346104a15760203660031901126104a15761345e615721565b600354600435036105bf5760ff60045416601a81101561048d576009036134a457600a60ff196004541617600455600a6003545f516020615aec5f395f51905f525f80a3005b63379ffc8d60e11b5f5260045ffd5b346104a15760403660031901126104a1576134cc61508d565b6004355f52601860205260405f209060018060a01b03165f52602052602060405f2054604051908152f35b346104a15760203660031901126104a157613510615721565b600354600435036105bf5760ff60045416601a81101561048d57600c036131bb57600d60ff196004541617600455600d6003545f516020615aec5f395f51905f525f80a3005b346104a1576135643661506a565b909161356e6159f0565b80158015613984575b6105bf576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610abb575f91613965575b5015610ecb57600283101561048d578261390e57805f52600960205260405f2054821015611ce657805f52600960205260ff60036135f68460405f20615213565b50015416815f52602460205260405f20835f526020526001600160401b0360405f205416905b156138ff576001600160401b03613639601c54928284169061517f565b1642116138f057815f5260266020526136558460405f206150b9565b835f5260205260018060a01b0360405f2054166138e15761368e6001600160401b03601b549260801c166001600160401b03431661517f565b906040519161012083018381106001600160401b038211176138cd57604052338352602083019182526040830190426001600160401b0316825260608401905f825260808501906001600160401b0316815260a085015f815260c08601915f835260e08701935f85526101008801965f8852895f5260266020528b60405f2090613717916150b9565b5f8c815260209190915260409081902099518a546001600160a01b0319166001600160a01b0391909116178a55905160018a0155955160028901805492516fffffffffffffffffffffffffffffffff199093166001600160401b03909216919091179190961b67ffffffffffffffff60401b1617855551845467ffffffffffffffff60801b191660809190911b67ffffffffffffffff60801b16178455518354915161ffff60c01b1990921690151560c01b60ff60c01b161790151560c81b60ff60c81b1617825551815460ff60d01b191690151560d01b60ff60d01b1617905551600390910155601a54601b546040516323b872dd60e01b5f52336004523060245260449190915293906001600160a01b0316805a60645f806020948194f15f516001148116156138ae575b856040525f6060521561389c575061385f84601b54926151b2565b60208401527f4c0dd383a6eb2a2f4b8f2a967e4365ca69b2d0012887044c0c509fa0ce676f2360403394a45f5f516020615b2c5f395f51905f525d005b635274afe760e01b5f5260045260245ffd5b60018115166138c457813b15153d151616613844565b853d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b633624f00960e21b5f5260045ffd5b63a4872ddd60e01b5f5260045ffd5b6355bc0ebf60e11b5f5260045ffd5b805f52601060205260405f2054821015611ce657805f52601060205260ff600261393b8460405f2061522c565b50015416815f52602560205260405f20835f526020526001600160401b0360405f2054169061361c565b61397e915060203d602011610ab457610aa681836151da565b846135b5565b506003548111613577565b346104a15760203660031901126104a1576004355f526009602052602060405f2054604051908152f35b346104a15760203660031901126104a1576004356139d5615721565b60035481036105bf5760ff60045416601a81101561048d57600f0361409557805f52601e60205260405f205490811561408657805f52600660205260405f20905f915f81549160018060a01b0360015416915b8381106140015750613a39856158ee565b94613a4760405196876151da565b808652613a56601f19916158ee565b013660208701375f905f5b848110613f70578787876003825110613f6157602492613aae60405160208101908382526720a3a3afa0a2222960c11b604082015260288152613aa56048826151da565b51902084615940565b6002546040516327416acd60e21b815260048101849052945f9186919082906001600160a01b03165afa938415610abb575f94613ecf575b506003845110613ec05760405160208101918252660828e8ebe9288b60cb1b604082015260278152613b196047826151da565b51902083515f1981019081116116ae57805b613e345750505f915f915f935b600381018082116116ae57835181111580613deb575b15613cca57825f52600960205260405f208054600160401b8110156138cd57613b7c91600182018155615213565b5091613b8787615245565b96835560018301905f5b60038110613c5b5750505060039182613bab878a516152b5565b10613c48575b600281015f5b848110613bf857505090613bf2929154847fb9202018de9f7eba7cdf3f805001edb29b6629a2fd89f5bd9c667a9d99450f655f80a394615253565b92613b38565b613c0b613c05828a615253565b8b615260565b5190825491600160401b8310156138cd57613c2d836001809501865585615156565b819291549060031b91821b915f19901b191617905501613bb7565b9150613c558588516152b5565b91613bb1565b600190613c7e6001600160a01b03613c76613c058487615253565b511685615274565b865f52600a60205260405f2086545f5260205260405f20828060a01b03613ca8613c058487615253565b5116838060a01b03165f5260205260405f208260ff1982541617905501613b91565b5082906003613cda8284516152b5565b1015613d06575b601060ff19600454161760045560106003545f516020615aec5f395f51905f525f80a3005b825f52601060205260405f208054600160401b8110156138cd57613d2f9160018201815561522c565b50915f83555f9160018401925b60038110613d76575050505054907fcba6d1415af496acc567dab38af44a786626bc3e3c6d0057a8b34329bb31e6ef5f80a3808080613ce1565b600190613d9f6001600160a01b03613d97613d918487615253565b87615260565b511686615274565b865f52601160205260405f2086545f5260205260405f20828060a01b03613dc9613d918487615253565b5116838060a01b03165f5260205260405f208260ff1982541617905501613d3c565b50600385018086116116ae578751811190811591613e0a575b50613b4e565b9050600286018087116116ae57885110159081613e29575b5088613e04565b905087511088613e22565b8451604051613e6b81613e5d602082019486888791606093918352602083015260408201520190565b03601f1981018352826151da565b5190209060018101918282116116ae57613eba92613e8891615936565b613e928188615260565b5190613ea9613ea1848a615260565b519189615260565b52613eb48288615260565b5261592a565b80613b2b565b634607f5d560e01b5f5260045ffd5b9093503d805f833e613ee181836151da565b8101906020818303126104a1578051906001600160401b0382116104a157019080601f830112156104a1578151613f17816158ee565b92613f2560405194856151da565b81845260208085019260051b8201019283116104a157602001905b828210613f51575050509284613ae6565b8151815260209182019101613f40565b631571768360e31b5f5260045ffd5b613f7a8183615156565b90546040516310ab556b60e21b815260039290921b1c6001600160a01b031660048201819052909190602083602481895afa8015610abb576001935f91613fe3575b50613fc9575b5001613a61565b613fdc613fd586615245565b958a615260565b5289613fc2565b613ffb915060203d8111610ab457610aa681836151da565b8b613fbc565b61400b8183615156565b90546040516310ab556b60e21b815260039290921b1c6001600160a01b03166004820152602081602481875afa908115610abb575f91614068575b50614054575b600101613a28565b94614060600191615245565b95905061404c565b614080915060203d8111610ab457610aa681836151da565b88614046565b6366d7bd4760e01b5f5260045ffd5b636be945dd60e01b5f5260045ffd5b346104a15760203660031901126104a1576004355f52601e602052602060405f2054604051908152f35b346104a1575f3660031901126104a1576020601b54604051908152f35b346104a1575f3660031901126104a157614103615721565b60ff60045416601a81101561048d576002036141b057600154600254604051633c1a477960e11b81526001600160a01b0391821660048201529160209183916024918391165afa908115610abb575f91614191575b501561418257600360ff196004541617600455600380545f516020615aec5f395f51905f525f80a3005b63f831404160e01b5f5260045ffd5b6141aa915060203d602011610ab457610aa681836151da565b81614158565b62bf1abb60e31b5f5260045ffd5b346104a1575f3660031901126104a15760206001600160401b03601c5416604051908152f35b346104a1576141f23661504c565b60039391935482036105bf5760ff60045416601a81101561048d5760120361442857815f52600960205260405f2054831015611ce6575f828152600a60209081526040808320868452825280832033845290915290205460ff1615610ef9576001546040516310ab556b60e21b815233600482015290602090829060249082906001600160a01b03165afa908115610abb575f91614409575b5015610ecb575f828152600f60209081526040808320868452825280832033845290915290205460ff16156143fa575f828152600c60209081526040808320868452825280832033845290915290205460ff1661253957831561252a5760408051602081018681529181019290925233606083015260808201839052905f60a082015260c0808201859052815261432360e0826151da565b5190205f828152600e602090815260408083208684528252808320338452909152902054036143eb575f818152600c60209081526040808320858452825280832033808552908352818420805460ff19166001179055848452600b8352818420868552835281842081855283529281902086905551858152849184917e4ff64f8cc2f88940c535cfe6face7248dc5ebf1faba271d2f8052017e747c79190a45f52600d60205260405f20905f5260205260405f20905f5260205260405f206125178154615245565b635130b96560e11b5f5260045ffd5b632ccbe95160e01b5f5260045ffd5b614422915060203d602011610ab457610aa681836151da565b8561428b565b636f5ecfff60e11b5f5260045ffd5b346104a15760203660031901126104a157600435614453615721565b60035481036105bf5760ff60045416601a81101561048d57601703614ac157600154604051631bad9e0560e11b815290602090829060049082906001600160a01b03165afa908115610abb575f91614a8f575b50906127106144b76023548461519f565b0491815f52600960205260405f20905f925b825484101561479b576144dc8484615213565b5091825492600460018201910154945f5b8254811015614788576145008184615156565b90545f878152600c602090815260408083208b8452825280832060039590951b9390931c6001600160a01b0316808352939052205490919060ff1661465e57891561464457600154604051630b6f9cd360e11b815260048101849052602481018c90527320a3a3afaa18afa727afa9aaa126a4a9a9a4a7a760611b6044820152606481018890529290602090849060849082905f906001600160a01b03165af18015610abb578789918d955f916145fa575b506001955f516020615b0c5f395f51905f52915b604080517320a3a3afaa18afa727afa9aaa126a4a9a9a4a7a760611b81526020810193909352820152606090a45b016144ed565b95505050506020833d821161463c575b81614617602093836151da565b810103126104a15787875f516020615b0c5f395f51905f528d600196519150956145b2565b3d915061460a565b60019187875f516020615b0c5f395f51905f528d5f6145c6565b855f52600b60205260405f20875f5260205260405f2060018060a01b0383165f526020528760405f205403614697575b600191506145f4565b600154604051637dd1602d60e11b81526004810184905260248101879052734147475f54315f4241445f434f4e53454e53555360601b60448201529290602090849060649082905f906001600160a01b03165af1928315610abb578789915f95614744575b5060408051734147475f54315f4241445f434f4e53454e53555360601b8152602081018a9052908101959095526001945f516020615b0c5f395f51905f5290606090a461468e565b945050506020833d8211614780575b81614760602093836151da565b810103126104a15791519187875f516020615b0c5f395f51905f526146fc565b3d9150614753565b50959193505060019193500192916144c9565b8491815f52601060205260405f20905f925b8254841015614a6a576147c0848461522c565b50805460038201549490937320a3a3afaa192fa727afa9aaa126a4a9a9a4a7a760611b929091734147475f54325f4241445f434f4e53454e53555360601b91600101905f905b8254821015614a5557869061481b8385615156565b60018060a01b0391549060031b1c1692825f52601460205260405f208a5f5260205260405f2060018060a01b0385165f526020528c60ff60405f205416155f1461494b5750878d801561492f579060846020925f60018060a01b03600154166040519889958694630b6f9cd360e11b86528c60048701526024860152604485015260648401525af1938415610abb578a8a8f95968b975f916148ec575b5060019596975f516020615b0c5f395f51905f52915b60408051928352602083018b9052820152606090a45b019150614806565b95965050505093506020823d8211614927575b8161490c602093836151da565b810103126104a157905187938d9392918b908b9060016148b8565b3d91506148ff565b9490600193945f516020615b0c5f395f51905f528d925f6148ce565b9390919250885f52601360205260405f208a5f5260205260405f2060018060a01b0382165f526020528a60405f205403614989575b506001906148e4565b9250905f91602060018060a01b0360015416606460405180968193637dd1602d60e11b83528960048401528c60248401528b60448401525af1938415610abb578a8a8f955f97614a0a575b505f516020615b0c5f395f51905f5260019596978b8b60405192839260608401928452602084015260408301520390a490614980565b949550505093506020823d8211614a4d575b81614a29602093836151da565b810103126104a1579051928c9291908a8a5f516020615b0c5f395f51905f526149d4565b3d9150614a1c565b60019099019897509295506147ad9350505050565b601860ff19600454161760045560186003545f516020615aec5f395f51905f525f80a3005b90506020813d602011614ab9575b81614aaa602093836151da565b810103126104a15751826144a6565b3d9150614a9d565b6360cd0e9960e01b5f5260045ffd5b346104a15760203660031901126104a157600435614aec615721565b60035481036105bf5760ff60045416601a81101561048d57600e03614be7576002546040516356af71c960e11b81526004810183905290602090829060249082905f906001600160a01b03165af1908115610abb575f91614bc8575b5015614bb957614b6f6001600160401b03601c5460801c166001600160401b03431661517f565b905f52601d6020526001600160401b0360405f2091166001600160401b0319825416179055600f60ff196004541617600455600f6003545f516020615aec5f395f51905f525f80a3005b6307fb097360e41b5f5260045ffd5b614be1915060203d602011610ab457610aa681836151da565b82614b48565b63183dd1d160e21b5f5260045ffd5b346104a157614c043661501c565b92614c0d615721565b805f526026602052614c228360405f206150b9565b825f5260205260405f2060018060a01b0381541615614ff3576002810190815460ff8160c01c16614fe45761ffff60c01b191686151560c81b60ff60c81b1617600160c01b1782558515614f9f5760030190815415614f9057614cc790614ca06001600160401b03601c5460401c166001600160401b03421661517f565b67ffffffffffffffff60401b82549160401b169067ffffffffffffffff60401b1916179055565b54600284101561048d5761268891614d2e6117bc9286155f14614f6f57825f526009602052614d0d6001614cfe8860405f20615213565b50015b604051958680926152c2565b825f526006602052614d2860405f20604051968780926152c2565b84615940565b60405193614d3d6080866151da565b60038552602085019160603684375f975f5b8651811080614f65575b15614e3457614d7b866001600160a01b03614d74848b615260565b5116615aa6565b1580614dc2575b90614d9091614d9557615245565b614d4f565b6001600160a01b03614da7828a615260565b5116614dbc614db58d615245565b9c8b615260565b52615245565b506001546001600160a01b039081169190602090614de0838b615260565b51166024604051809581936310ab556b60e21b835260048301525afa8015610abb57614d90925f91614e16575b50909150614d82565b614e2e915060203d8111610ab457610aa681836151da565b8c614e0d565b50945094919660039196935010613f6157835f526027602052614e5a8260405f206150b9565b855f5260205260405f20958151906001600160401b0382116138cd57600160401b82116138cd578754828955808310614f3f575b50965f5260205f20965f5b828110614f225750505084955093837f1aa282aabded606592d2cccbd78439ed03820b25543d6161b213b4b8c9651993614f107f5f2ed0b8a76b3e4bf8076e2e31d83602fef08c30bdb2b069d1db259c176e63ef9697604051918291614eff83896151b2565b6040602084015260408301906150cf565b0390a3612af4604051928392836151bf565b81516001600160a01b0316818a0155602090910190600101614e99565b885f528260205f2091820191015b818110614f5a5750614e8e565b5f8155600101614f4d565b5060038a10614d59565b825f526010602052614d0d6001614f898860405f2061522c565b5001614d01565b632d5720c760e11b5f5260045ffd5b815460ff60d01b1916600160d01b1790915560010154919390927f5f2ed0b8a76b3e4bf8076e2e31d83602fef08c30bdb2b069d1db259c176e63ef92612ba090615747565b63450cfd7f60e11b5f5260045ffd5b63167f349d60e11b5f5260045ffd5b346104a1575f3660031901126104a1576020906005548152f35b60809060031901126104a1576004359060243560028110156104a157906044359060643580151581036104a15790565b60809060031901126104a15760043590602435906044359060643590565b60609060031901126104a1576004359060243560028110156104a1579060443590565b602435906001600160a01b03821682036104a157565b600435906001600160a01b03821682036104a157565b90600281101561048d575f5260205260405f2090565b90602080835192838152019201905f5b8181106150ec5750505090565b82516001600160a01b03168452602093840193909201916001016150df565b60409060031901126104a1576004359060243590565b60609060031901126104a157600435906024359060443590565b346104a1575f3660031901126104a157602060405160038152f35b805482101561516b575f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b906001600160401b03809116911601906001600160401b0382116116ae57565b818102929181159184041417156116ae57565b90600282101561048d5752565b9092916020906151d38360408101966151b2565b1515910152565b90601f801991011681019081106001600160401b038211176138cd57604052565b908160209103126104a1575180151581036104a15790565b805482101561516b575f52600560205f20910201905f90565b805482101561516b575f5260205f209060021b01905f90565b5f1981146116ae5760010190565b919082018092116116ae57565b805182101561516b5760209160051b010190565b8054600160401b8110156138cd5761529191600182018155615156565b81546001600160a01b0393841660039290921b91821b9390911b1916919091179055565b919082039182116116ae57565b90602082549182815201915f5260205f20905f5b8181106152e35750505090565b82546001600160a01b03168452602090930192600192830192016152d6565b805f52601f6020526001600160401b0360405f205416801561544057815f526020805260405f20546154315780431115615422574080156153a757604051602081019182528260408201526210555160ea1b6060820152604381526153686063826151da565b5190205f828152602080805260409182902083905590519182527fe53c25b43758bf910ba120923c7295303dcbc2369273c7985227b00293f3b56991a2565b506153c96001600160401b03601c5460801c166001600160401b03431661517f565b5f828152601f6020908152604091829020805467ffffffffffffffff19166001600160401b0394909416938417905590519182527f7e22e73285f299179a84bb3388a9dd94b3eac27a6de162455a42ee6ce6706f9491a2565b63de3f681d60e01b5f5260045ffd5b638d10dfef60e01b5f5260045ffd5b63906135ab60e01b5f5260045ffd5b805f52601d6020526001600160401b0360405f205416801561559057815f52601e60205260405f20546155815780431115615572574080156154f757604051602081019182528260408201526241474760e81b6060820152604381526154b66063826151da565b5190205f828152601e602090815260409182902083905590519182527f9dcaca5280e612f442cef14eea003bbece818a3fde40db4dc4a3887682aa07b691a2565b506155196001600160401b03601c5460801c166001600160401b03431661517f565b5f828152601d6020908152604091829020805467ffffffffffffffff19166001600160401b0394909416938417905590519182527fbed5c9fb50b85a70b1ea0ab5bf64bbf34ba76521149788c3873abaf955d545c291a2565b632f9cb5e160e21b5f5260045ffd5b634653993f60e01b5f5260045ffd5b631c237c4160e31b5f5260045ffd5b90815f5260266020526155b58160405f206150b9565b835f5260205260405f209060018060a01b0382541615614ff357600282019081549260ff8460c01c16614fe457600301928354615712576001600160401b039060801c168043111561570357409182156156845750600281101561048d577ff1ac2abd9bfa2c470babc8a68c69061d1661d798dedc05ba6f20a5fdb6bfe51592604092835160208101918252868582015260ff60f81b8460f81b1660608201528760618201526061815261566a6081826151da565b51902080915561567c835180936151b2565b6020820152a3565b7f2bee25e87a4aab24a6e926580a5c7026c624938721cc23acc05733fe7a4deebd9350604092506001600160401b03906156f26156ca83601c5460801c1684431661517f565b825467ffffffffffffffff60801b191660809190911b67ffffffffffffffff60801b16178255565b5460801c1661567c835180936151b2565b63221198d360e11b5f5260045ffd5b63d6e8b91560e01b5f5260045ffd5b5f546001600160a01b0316330361573457565b63118cdaa760e01b5f523360045260245ffd5b5f9080156158ea578060011c9161575e83836152b5565b601a546001600160a01b031693909190843b156104a1575f94602486926040519788938492630852cd8d60e31b845260048401525af1938415610abb576004946158d7575b50600154604051639958065160e01b81529460209186919082906001600160a01b03165afa9384156158ca578194615882575b506001600160a01b0384161561580e5750601a546158099361580192916001600160a01b0316615a25565b602154615253565b602155565b601a54929350916001600160a01b031690813b1561587e578291602483926040519485938492630852cd8d60e31b845260048401525af180156158735761585b575b505061580990615801565b6158668280926151da565b6158705780615850565b80fd5b6040513d84823e3d90fd5b8280fd5b9093506020813d6020116158c2575b8161589e602093836151da565b810103126158be57516001600160a01b03811681036158be57925f6157d6565b8380fd5b3d9150615891565b50604051903d90823e3d90fd5b6158e391505f906151da565b5f5f6157a3565b5050565b6001600160401b0381116138cd5760051b60200190565b600560ff19600454161760045560056003545f516020615aec5f395f51905f525f80a3565b80156116ae575f190190565b81156120c8570690565b908151600281106159eb575f1981019081116116ae57805b61596157505050565b825160405161598a81613e5d602082019486888791606093918352602083015260408201520190565b5190209060018101918282116116ae576159e5926159a791615936565b6001600160a01b036159b98287615260565b5116906159da6001600160a01b036159d18589615260565b51169187615260565b52613eb48286615260565b80615958565b505050565b5f516020615b2c5f395f51905f525c615a165760015f516020615b2c5f395f51905f525d565b633ee5aeb560e01b5f5260045ffd5b916040519163a9059cbb60e01b5f5260018060a01b031660045260245260205f60448180865af19060015f5114821615615a85575b60405215615a655750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b906001811516615a9d57823b15153d15161690615a5a565b503d5f823e3d90fd5b5f5b8251811015615ae4576001600160a01b03615ac38285615260565b51166001600160a01b03831614615adc57600101615aa8565b505050600190565b5050505f9056fea8f52d7b4405100a39ccc552ccc9426d860b1e822c71e97675d1623b49db6babd31b1ab070aba73480b7f863f37fefd98c16862672290e5cadbf1212bf1630cd9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a2646970667358221220fabaacd9430158ccd17c9a0ac33efa6e9e318289f8412906ff8bfc11b57356af64736f6c634300081c0033" } \ No newline at end of file diff --git a/cache_model_0/manifest.json b/cache_model_0/manifest.json index 7d615231..08ee5263 100644 --- a/cache_model_0/manifest.json +++ b/cache_model_0/manifest.json @@ -168,7 +168,7 @@ "artifact": { "type": "foundry-artifact", "path": "contracts/abis/DINTaskCoordinator.json", - "ipfs": "bafybeigmde3cggcas75ux3yezjmjsefho6obqlpqyl7qpyv6nhd6az5kum", + "ipfs": "bafybeif7fay4vgal4vb6ojsspc4e6muyyujuveyutszlf7z4mfb5uxkteu", "stakeholders": [ "modelowner", "auditors", diff --git a/dincli/abis/DINTaskCoordinator.json b/dincli/abis/DINTaskCoordinator.json index e95468c7..719e8bfd 100644 --- a/dincli/abis/DINTaskCoordinator.json +++ b/dincli/abis/DINTaskCoordinator.json @@ -1441,6 +1441,19 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "taskProtocolVersion", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "pure" + }, { "type": "function", "name": "tier1BatchCount", diff --git a/dincli/cli/modelownerd/deploy.py b/dincli/cli/modelownerd/deploy.py index c819702e..c9d0229b 100644 --- a/dincli/cli/modelownerd/deploy.py +++ b/dincli/cli/modelownerd/deploy.py @@ -7,9 +7,11 @@ from dincli.cli.utils import (build_and_send_tx, load_din_info, resolve_task_coordinator_address, set_env_key) -_MODEL_ID_ABI = [ +_COORDINATOR_DEPLOYMENT_ABI = [ {"type": "function", "name": "modelId", "inputs": [], "outputs": [{"type": "uint256"}], "stateMutability": "view"}, + {"type": "function", "name": "taskProtocolVersion", "inputs": [], + "outputs": [{"type": "uint256"}], "stateMutability": "pure"}, ] @@ -114,9 +116,9 @@ def task_auditor( coordinator=task_coordinator_address, model_id=model_id, registry=din_info.get(effective_network, {}).get("registry"), ) + paired = w3.eth.contract(address=task_coordinator_address, abi=_COORDINATOR_DEPLOYMENT_ABI) if len(constructor_args) >= 3: # IDs select stake floors; both IDs and the one-shot pairing are immutable. - paired = w3.eth.contract(address=task_coordinator_address, abi=_MODEL_ID_ABI) try: paired_model_id = paired.functions.modelId().call() except (ContractLogicError, BadFunctionCallOutput) as exc: @@ -127,6 +129,35 @@ def task_auditor( if paired_model_id != model_id: raise typer.BadParameter("--model-id must match the paired coordinator modelId()") + if not w3.eth.get_code(task_coordinator_address): + raise typer.BadParameter("The paired coordinator has no deployed code; verify --taskCoordinator") + try: + coordinator_version = paired.functions.taskProtocolVersion().call() + except (ContractLogicError, BadFunctionCallOutput): + # Released legacy coordinators have no version getter. Transport/RPC + # failures still propagate, rather than silently taking the legacy path. + coordinator_version = 0 + auditor_version = 1 if len(constructor_args) == 4 else 0 + if coordinator_version not in (0, 1) or coordinator_version != auditor_version: + raise typer.BadParameter( + "Incompatible task contract generations: coordinator protocol version " + f"{coordinator_version}, auditor protocol version {auditor_version}. " + "Deploy a matching task pair; auditor pairing is one-shot." + ) + + if len(constructor_args) == 2: + # The pre-model-ID auditor also lacks the reward-pool interface used by + # later coordinators. Keep the two released legacy families separate. + try: + paired.functions.modelId().call() + except (ContractLogicError, BadFunctionCallOutput): + pass + else: + raise typer.BadParameter( + "Incompatible task contract generations: a coordinator with modelId() " + "requires an auditor with a model ID. Deploy a matching task pair." + ) + console.print(f"[bold green]Deploying DINTaskAuditor on network:[/bold green] {effective_network}") console.print(f"[cyan]Using DINValidatorStake address:[/cyan] {din_validator_stake_address}") console.print(f"[cyan]Using DINTaskCoordinator address:[/cyan] {task_coordinator_address}") diff --git a/foundry/src/DINTaskCoordinator.sol b/foundry/src/DINTaskCoordinator.sol index 04af36aa..a9451457 100644 --- a/foundry/src/DINTaskCoordinator.sol +++ b/foundry/src/DINTaskCoordinator.sol @@ -19,6 +19,13 @@ interface IBurnableDinToken { contract DINTaskCoordinator is Ownable, ReentrancyGuardTransient { using SafeERC20 for IERC20; + /// @notice Task-pair interface generation: 1 requires the auditor GI-start guard. + /// @dev Deployment tooling uses this before the irreversible auditor pairing. + /// Legacy coordinators expose no version getter. + function taskProtocolVersion() external pure returns (uint256) { + return 1; + } + IDinValidatorStake public dinvalidatorStakeContract; IDINTaskAuditor public dinTaskAuditorContract; diff --git a/foundry/test/RewardEngine.t.sol b/foundry/test/RewardEngine.t.sol index 2f50e187..8e2bbf13 100644 --- a/foundry/test/RewardEngine.t.sol +++ b/foundry/test/RewardEngine.t.sol @@ -439,6 +439,7 @@ contract RewardEngineTest is Test { function test_disabledModel_blocksBothOverloadsAtomically_andReenable() public { _killSwitchFixture(); + assertEq(tc.taskProtocolVersion(), 1); (bool exists, uint256 approvedId) = registry.getModelIdByTaskAuditor(address(ta)); assertTrue(exists); assertEq(approvedId, 0); diff --git a/tests/test_model_disable_deployment.py b/tests/test_model_disable_deployment.py index 0fe0ef69..06f06c33 100644 --- a/tests/test_model_disable_deployment.py +++ b/tests/test_model_disable_deployment.py @@ -56,6 +56,10 @@ def auditor_command(monkeypatch): paired.functions.modelId.return_value.call.return_value = 7 w3 = Mock() w3.eth.contract.return_value = paired + w3.eth.get_code.return_value = b"\x60" + paired.functions.taskProtocolVersion.return_value.call.side_effect = ( + lambda: 1 if len(contract.abi[0]["inputs"]) == 4 else 0 + ) obj = Mock() obj.get_en_w3_account_console.return_value = ("local", w3, Mock(), Mock()) ctx = SimpleNamespace(obj=obj) @@ -95,6 +99,7 @@ def test_auditor_command_checks_pair_and_deploys(auditor_command, with_registry) command.paired.functions.modelId.assert_called_once_with() command.paired.functions.modelId.return_value.call.assert_called_once_with() assert command.w3.eth.contract.call_args.kwargs["address"] == "coordinator" + command.paired.functions.taskProtocolVersion.return_value.call.assert_called_once_with() assert command.send.call_count == 2 assert command.send.call_args_list[0].args[1] is command.contract.constructor.return_value setter = command.ctx.obj.get_deployed_din_task_coordinator_contract.return_value @@ -167,18 +172,17 @@ def test_auditor_command_does_not_wrap_contract_creation_error(auditor_command): assert_no_deployment(command) -def test_legacy_auditor_deploys_without_model_id_read(auditor_command): +def test_legacy_auditor_deploys_when_model_id_getter_is_absent(auditor_command): command = auditor_command command.contract.abi = artifact("address", "address").abi - command.paired.functions.modelId.return_value.call.side_effect = AssertionError( - "legacy deployment must not read modelId" - ) + command.paired.functions.modelId.return_value.call.side_effect = ContractLogicError("no getter") deploy.task_auditor(command.ctx, "artifact.json", None, "coordinator") command.contract.constructor.assert_called_once_with("stake", "coordinator") - command.w3.eth.contract.assert_not_called() - command.paired.functions.modelId.assert_not_called() + command.w3.eth.contract.assert_called_once() + command.paired.functions.taskProtocolVersion.return_value.call.assert_called_once_with() + command.paired.functions.modelId.return_value.call.assert_called_once_with() assert command.send.call_count == 2 command.write_env.assert_called_once_with( "LOCAL_coordinator_DINTaskAuditor_Contract_Address", "auditor", @@ -249,3 +253,95 @@ def test_invalid_id_sends_no_transaction(auditor_command, role, model_id): command.w3.eth.contract.assert_not_called() assert_no_deployment(command) + + +@pytest.mark.parametrize("types,version", [ + (("address", "address"), 1), + (("address", "address", "uint256"), 1), + (("address", "address", "uint256", "address"), 0), + (("address", "address", "uint256", "address"), 2), + (("address", "address", "uint256"), 2), +]) +def test_incompatible_generations_send_no_transaction(auditor_command, types, version): + command = auditor_command + command.contract.abi = artifact(*types).abi + command.paired.functions.taskProtocolVersion.return_value.call.side_effect = None + command.paired.functions.taskProtocolVersion.return_value.call.return_value = version + + with pytest.raises(typer.BadParameter, match="Incompatible task contract generations"): + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=7, + task_coordinator="coordinator") + + assert_no_deployment(command) + + +@pytest.mark.parametrize("error_type", [ContractLogicError, BadFunctionCallOutput]) +@pytest.mark.parametrize("guarded", [False, True]) +def test_missing_version_getter_only_allows_legacy_pairs(auditor_command, error_type, guarded): + command = auditor_command + types = ("address", "address", "uint256") + (("address",) if guarded else ()) + command.contract.abi = artifact(*types).abi + command.paired.functions.taskProtocolVersion.return_value.call.side_effect = error_type("no getter") + + if guarded: + with pytest.raises(typer.BadParameter, match="Incompatible task contract generations"): + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=7, + task_coordinator="coordinator") + assert_no_deployment(command) + else: + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=7, + task_coordinator="coordinator") + command.contract.constructor.assert_called_once_with("stake", "coordinator", 7) + assert command.send.call_count == 2 + + +@pytest.mark.parametrize("error_type", [ConnectionError, ValueError]) +def test_version_read_rpc_errors_do_not_fall_back_to_legacy(auditor_command, error_type): + command = auditor_command + command.contract.abi = artifact("address", "address", "uint256").abi + error = error_type("RPC failure") + command.paired.functions.taskProtocolVersion.return_value.call.side_effect = error + + with pytest.raises(error_type) as caught: + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=7, + task_coordinator="coordinator") + + assert caught.value is error + assert_no_deployment(command) + + +def test_legacy_auditor_rejects_coordinator_without_code(auditor_command): + command = auditor_command + command.contract.abi = artifact("address", "address").abi + command.w3.eth.get_code.return_value = b"" + + with pytest.raises(typer.BadParameter, match="no deployed code"): + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=None, + task_coordinator="coordinator") + + command.paired.functions.taskProtocolVersion.assert_not_called() + assert_no_deployment(command) + + +def test_two_argument_auditor_rejects_previous_model_id_coordinator(auditor_command): + command = auditor_command + command.contract.abi = artifact("address", "address").abi + command.paired.functions.modelId.return_value.call.return_value = 7 + + with pytest.raises(typer.BadParameter, match="requires an auditor with a model ID"): + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=None, + task_coordinator="coordinator") + + assert_no_deployment(command) + + +def test_legacy_capability_rpc_error_does_not_deploy(auditor_command): + command = auditor_command + command.contract.abi = artifact("address", "address").abi + command.paired.functions.modelId.return_value.call.side_effect = ConnectionError("RPC failure") + + with pytest.raises(ConnectionError): + deploy.task_auditor(command.ctx, artifact_path="artifact.json", model_id=None, + task_coordinator="coordinator") + + assert_no_deployment(command) diff --git a/tests/test_task_auditor_model_id_guard.py b/tests/test_task_auditor_model_id_guard.py index 370f4b86..87ec9403 100644 --- a/tests/test_task_auditor_model_id_guard.py +++ b/tests/test_task_auditor_model_id_guard.py @@ -37,9 +37,10 @@ def _contract(address, abi): return SimpleNamespace( functions=SimpleNamespace( modelId=lambda: SimpleNamespace(call=lambda: coord_model_id), + taskProtocolVersion=lambda: SimpleNamespace(call=lambda: 0), ) ) - return SimpleNamespace(eth=SimpleNamespace(contract=_contract)) + return SimpleNamespace(eth=SimpleNamespace(contract=_contract, get_code=lambda address: b"\x60")) class DummyCtxObj: From 7d20067ad9a5e3af54f8ebefb5ab9bca8d57cafe Mon Sep 17 00:00:00 2001 From: santiagocetran Date: Fri, 6 Nov 2026 12:27:13 -0300 Subject: [PATCH 6/7] ci: diagnose task artifact bytecode mismatches --- .github/scripts/check_task_artifacts.py | 43 ++++++++++++++ tests/test_task_artifact_check.py | 75 +++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/.github/scripts/check_task_artifacts.py b/.github/scripts/check_task_artifacts.py index 06bf4b75..f5ccae66 100644 --- a/.github/scripts/check_task_artifacts.py +++ b/.github/scripts/check_task_artifacts.py @@ -3,6 +3,7 @@ from __future__ import annotations import argparse +import hashlib import json from pathlib import Path @@ -14,6 +15,47 @@ def canonical_abi(abi: list) -> list[str]: return sorted(json.dumps(entry, sort_keys=True, separators=(",", ":")) for entry in abi) +def bytecode_diagnostics(bundled: object, expected: str, artifact: dict) -> str: + """Expose reproducibility differences without relaxing exact bytecode parity.""" + if not isinstance(bundled, str): + return "bundled creation bytecode is not a string" + actual = bytes.fromhex(expected.removeprefix("0x")) + try: + saved = bytes.fromhex(bundled.removeprefix("0x")) + except ValueError: + return "bundled creation bytecode is not valid hex" + if saved == actual: + difference = "decoded bytes identical; textual representation differs" + else: + first = next((i for i, (a, b) in enumerate(zip(saved, actual)) if a != b), min(len(saved), len(actual))) + difference = f"first differing byte: {first}" + metadata = artifact.get("metadata", {}) + if isinstance(metadata, str): + try: + metadata = json.loads(metadata) + except ValueError: + metadata = None + if not isinstance(metadata, dict): + details = "build metadata: unavailable" + else: + sources = metadata.get("sources", {}) + source_hashes = { + path: source.get("keccak256") + for path, source in sources.items() if isinstance(source, dict) + } if isinstance(sources, dict) else {} + details = ( + f"build compiler: {json.dumps(metadata.get('compiler', {}), sort_keys=True)}; " + f"build settings: {json.dumps(metadata.get('settings', {}), sort_keys=True)}; " + f"build source hashes: {json.dumps(source_hashes, sort_keys=True)}" + ) + return ( + f"{difference}; bundled length: {len(saved)}; build length: {len(actual)}; " + f"bundled sha256: {hashlib.sha256(saved).hexdigest()}; " + f"build sha256: {hashlib.sha256(actual).hexdigest()}; " + f"{details}" + ) + + def check_artifacts(root: Path) -> list[str]: errors = [] for name in CONTRACTS: @@ -36,6 +78,7 @@ def check_artifacts(root: Path) -> list[str]: errors.append(f"{directory}/{name}.json: ABI differs from build") if directory == "cache_model_0/abis" and bundled.get("bytecode") != expected_bytecode: errors.append(f"{directory}/{name}.json: creation bytecode differs from build") + errors.append(f"{name}: {bytecode_diagnostics(bundled.get('bytecode'), expected_bytecode, artifact)}") except (OSError, ValueError, KeyError, TypeError) as exc: errors.append(f"{directory}/{name}.json: invalid artifact ({exc})") return errors diff --git a/tests/test_task_artifact_check.py b/tests/test_task_artifact_check.py index 122cffcb..cf88c2b4 100644 --- a/tests/test_task_artifact_check.py +++ b/tests/test_task_artifact_check.py @@ -1,5 +1,6 @@ """The post-build gate must reject ABI drift and stale deployable bytecode.""" import importlib.util +import hashlib import json from pathlib import Path @@ -56,3 +57,77 @@ def test_empty_build_bytecode_fails(artifacts): data["bytecode"]["object"] = "0x" path.write_text(json.dumps(data)) assert any("empty build bytecode" in error for error in checker.check_artifacts(artifacts)) + + +def test_bytecode_mismatch_reports_build_settings_without_passing(artifacts): + path = artifacts / "foundry/out/DINTaskAuditor.sol/DINTaskAuditor.json" + data = json.loads(path.read_text()) + data["bytecode"]["object"] = "0x6002" + data["metadata"] = {"compiler": {"version": "0.8.28"}, "settings": {"viaIR": True}} + path.write_text(json.dumps(data)) + errors = checker.check_artifacts(artifacts) + assert any("creation bytecode differs from build" in error for error in errors) + diagnostic = next(error for error in errors if "first differing byte:" in error) + assert "first differing byte: 1" in diagnostic + assert "bundled sha256:" in diagnostic and "build sha256:" in diagnostic + assert '"version": "0.8.28"' in diagnostic + assert '"viaIR": true' in diagnostic + + +@pytest.mark.parametrize("metadata", [None, [], "null", "[]", "invalid json"]) +def test_unusable_metadata_preserves_mismatch_reporting(artifacts, metadata, monkeypatch, capsys): + path = artifacts / "foundry/out/DINTaskAuditor.sol/DINTaskAuditor.json" + data = json.loads(path.read_text()) + data.update(metadata=metadata, bytecode={"object": "0x6002"}) + path.write_text(json.dumps(data)) + monkeypatch.setattr("sys.argv", ["check_task_artifacts.py", "--root", str(artifacts)]) + assert checker.main() == 1 + output = capsys.readouterr().out + assert "creation bytecode differs from build" in output + assert "first differing byte: 1" in output + assert "build metadata: unavailable" in output + + +def test_formatting_only_difference_still_fails_gate(artifacts): + path = artifacts / "cache_model_0/abis/DINTaskAuditor.json" + data = json.loads(path.read_text()) + data["bytecode"] = "6001" + path.write_text(json.dumps(data)) + errors = checker.check_artifacts(artifacts) + assert any("creation bytecode differs from build" in error for error in errors) + assert any("decoded bytes identical; textual representation differs" in error for error in errors) + assert not any("first differing byte:" in error for error in errors) + + +@pytest.mark.parametrize("saved,built,offset", [ + ("0x6001", "0x6101", 0), + ("0x60", "0x6001", 1), + ("0x6001", "0x60", 1), +]) +def test_diagnostic_byte_offsets_lengths_and_hashes(saved, built, offset): + result = checker.bytecode_diagnostics(saved, built, {}) + assert f"first differing byte: {offset}" in result + for label, code in (("bundled", saved), ("build", built)): + raw = bytes.fromhex(code[2:]) + assert f"{label} length: {len(raw)};" in result + assert f"{label} sha256: {hashlib.sha256(raw).hexdigest()};" in result + + +@pytest.mark.parametrize("as_string", [False, True]) +def test_diagnostics_report_sorted_source_hashes_and_metadata(as_string): + metadata = { + "compiler": {"version": "0.8.28"}, "settings": {"viaIR": True}, + "sources": {"z.sol": {"keccak256": "0x22"}, "a.sol": {"keccak256": "0x11"}}, + } + result = checker.bytecode_diagnostics("0x6001", "0x6002", { + "metadata": json.dumps(metadata) if as_string else metadata, + }) + assert 'build source hashes: {"a.sol": "0x11", "z.sol": "0x22"}' in result + assert '"version": "0.8.28"' in result + assert '"viaIR": true' in result + + +def test_hex_case_difference_has_no_differing_byte(): + result = checker.bytecode_diagnostics("0x60AA", "0x60aa", {}) + assert "decoded bytes identical; textual representation differs" in result + assert "first differing byte:" not in result From c8a722453024846b6e3886bf8bfaae8b3c333cf9 Mon Sep 17 00:00:00 2001 From: santiagocetran Date: Fri, 6 Nov 2026 12:50:29 -0300 Subject: [PATCH 7/7] build: pin duplicate dependency remappings for reproducible bytecode --- foundry/remappings.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/foundry/remappings.txt b/foundry/remappings.txt index 11a7c6c2..4cce724f 100644 --- a/foundry/remappings.txt +++ b/foundry/remappings.txt @@ -2,3 +2,5 @@ forge-std/=lib/forge-std/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @openzeppelin/foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ +halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/