diff --git a/Developer/issues/indexer.md b/Developer/issues/indexer.md index 8d179fa..bc1d997 100644 --- a/Developer/issues/indexer.md +++ b/Developer/issues/indexer.md @@ -387,3 +387,75 @@ The better production design is: - use The Graph or a similar indexer for pagination, filtering, and historical queries If a query pattern mainly serves dashboards, reviewers, governance interfaces, or analytics, it is usually a strong candidate for the indexer rather than additional on-chain enumeration logic. + +--- + +## P4-IDX Doc-Correction Pass (feat/din-indexer) + +This section records decisions made and open questions resolved by the +P4-IDX1/IDX2/IDX3 work (feat/din-indexer). Added as a correction pass per the +convention established in PR #13. + +### Open questions resolved + +**"Should DIN use The Graph directly, or maintain a DIN-operated indexer service?"** +Resolved: The Graph Protocol subgraph is the chosen approach. A self-hosted +`graph-node` runs against the local Hardhat chain (chainId 1337) via +`subgraph/docker-compose.yml`. See `subgraph/docs/event-coverage-audit.md` §1 +for the written tradeoff. + +**"Which DIN product queries truly require on-chain enumerable structures, if any?"** +Resolved: None for the current platform contracts. The `list-pending-requests` +command in `dincli/cli/dindao.py` was the primary on-chain enumeration loop +for model and manifest requests — both loops have been replaced with GraphQL +queries in P4-IDX3. Remaining RPC-loop candidates (`lms.py` ~line 62, +`aggregation.py` ~lines 76, 95, 136) touch task-level contract state +(DINTaskCoordinator), which is out of scope for this subgraph; see +`subgraph/docs/handoff.md` for follow-up notes. + +**"Are there any platform-level queues whose ordering is execution-critical?"** +Resolved: No. The model and manifest request ordering in `DINModelRegistry` is +UI-critical only (review queue for the DAO admin). Execution semantics depend +on the request ID (array index), not on queue position. The indexer serves this +read pattern correctly via `processed: false` filters. + +**"Which current events are insufficient for reconstructing state cleanly?"** +Resolved: Two events were audited as insufficient; both are flagged as PENDING +additions in `subgraph/docs/event-coverage-audit.md` §6: +- `ModelRegistrationRequested` — missing `isOpenSource` and `feePaid` in payload +- `ManifestUpdateRequested` — missing `requester` in payload + +Until these additions land, the mapping handlers bridge the gap with storage +calls (`DINModelRegistry.bind(...).modelRequests(requestId)`). A third addition +(`ETHTreasuryWithdrawn` on `DinCoordinator.withdraw()`) is also proposed in §6.3 +but not yet merged. + +**"Should governance, registry, and staking data be indexed in one subgraph or separate modules?"** +Resolved: One subgraph for all four platform contracts in P4. Dynamic data +sources for task-level contracts (DINTaskCoordinator, DINTaskAuditor) are +deferred to P5+ as a materially harder Graph pattern with its own deploy/hosting +story. See `subgraph/subgraph.yaml` (network: `din-local`, 4 data sources, +31 handlers). + +### Stale references in this document + +None found. The architectural direction, guiding principle, and contribution +guidelines remain accurate against the current contract and CLI codebase. +The "Governance Queries" example section is still forward-looking (DIN-DAO +contracts are tracked in a separate issue, Start DIN-DAO #23); it is not stale, +just not yet implemented. + +### Artefacts produced by P4-IDX + +| Artefact | Path | +|----------|------| +| Indexing approach tradeoff + event audit | `subgraph/docs/event-coverage-audit.md` | +| GraphQL entity schema | `subgraph/schema.graphql` | +| Subgraph manifest | `subgraph/subgraph.yaml` | +| AssemblyScript handlers | `subgraph/src/` | +| Local Graph node stack | `subgraph/docker-compose.yml` | +| Daemon event schema | `subgraph/docs/daemon-event-schema.md` | +| Example queries | `subgraph/docs/example-queries.md` | +| GraphQL integration + fallback | `dincli/cli/dindao.py` | +| Integration unit tests | `tests/test_list_pending_requests.py` | +| Integration handoff notes | `subgraph/docs/handoff.md` | diff --git a/Documentation/technical/audits/task-contract-event-audit.md b/Documentation/technical/audits/task-contract-event-audit.md new file mode 100644 index 0000000..e9b8810 --- /dev/null +++ b/Documentation/technical/audits/task-contract-event-audit.md @@ -0,0 +1,232 @@ +# Task-contract event audit — DINTaskCoordinator & DINTaskAuditor + +Conducted as part of the subgraph extension to task-level contracts +(`feat/din-indexer`). Follows the same format as the platform-contract audit +in `subgraph/docs/event-coverage-audit.md`. + +--- + +## §1 Scope + +Two contracts deployed once per model by the model owner: + +- `DINTaskCoordinator` — orchestrates the full GI lifecycle (aggregator/auditor + registration, LMS, batch creation, T1/T2 aggregation, slashing, GI end) +- `DINTaskAuditor` — handles auditor registration, local model submission, + eligibility voting, scoring, and auditor slashing + +**Not proxied.** Both contracts are deployed fresh for each model; there is no +upgrade proxy. This has one consequence for the indexer: already-deployed +instances on `sepolia_op_devnet` will not emit the new events proposed in §4. +Dynamic data sources can only index newly deployed task contracts going forward. +State this explicitly in any dind or operator runbook. + +--- + +## §2 Existing events + +### DINTaskCoordinator + +| Event | Emitted by | Indexed fields | Notes | +|-------|-----------|---------------|-------| +| `DINValidatorRegistered(uint GI, address validator)` | `registerDINaggregator()` | `GI`, `validator` | Aggregator registration only; auditors use the auditor contract | +| `Tier1BatchAuto(uint GI, uint batchId)` | `autoCreateTier1AndTier2()` | `GI`, `batchId` | Emitted once per T1 batch created | +| `Tier2BatchAuto(uint GI, uint batchId)` | `autoCreateTier1AndTier2()` | `GI`, `batchId` | Emitted once for the single T2 batch | +| `AggregatorSlashed(uint GI, uint batchId, address aggregator, bytes32 reason, uint256 requested, uint256 actual)` | `slashAggregators()` | `GI`, `batchId`, `aggregator` | Covers both T1 and T2 slash loops | + +### DINTaskAuditor + +| Event | Emitted by | Indexed fields | Notes | +|-------|-----------|---------------|-------| +| `DINAuditorRegistered(uint GI, address auditor)` | `registerDINAuditor()` | `GI`, `auditor` | | +| `AuditorsBatchAuto(uint GI, uint batchId)` | `createAuditorsBatches()` | `GI`, `batchId` | Per-batch; one emit per batch created | +| `AuditorsBatchesCreated(uint GI, uint batchCount)` | `createAuditorsBatches()` | `GI` | Summary event after all batches created | +| `AuditScoreSubmitted(uint256 gi, uint batchId, address auditor, uint modelIndex, uint256 score)` | `setAuditScorenEligibility()` | `gi`, `batchId`, `auditor` | | +| `EligibilityVoted(uint256 gi, uint batchId, uint modelIndex, address auditor, bool vote)` | `setAuditScorenEligibility()` | `gi`, `batchId`, `modelIndex` | | +| `EligibilityFinalized(uint256 gi, uint batchId, uint modelIndex, bool eligible, uint totalVotes)` | `_tryFinalizeEligibility()` | `gi`, `batchId`, `modelIndex` | Fires when quorum is reached after each vote | +| `PassScoreUpdated(uint256 oldScore, uint256 newScore)` | `updatePassScore()` | none | Called by coordinator at `startGI` when score arg supplied | +| `AuditorSlashed(uint256 gi, uint batchId, address auditor, bytes32 reason, uint256 requested, uint256 actual)` | `slashAuditors()` | `gi`, `batchId`, `auditor` | | + +--- + +## §3 Gap analysis + +### §3.1 Silent GI state machine + +`GIstates` (defined in `DINShared.sol`) has 23 states. `DINTaskCoordinator` +writes to `GIstate` at 23 sites — the constructor, the four pre-GI setup steps, +and every phase-transition function — and **none of them emit an event**. The +full list of silent write sites: + +| Site | Transition | +|------|-----------| +| `constructor` | → `AwaitingDINTaskAuditorToBeSet` | +| `setDINTaskAuditorContract()` | → `AwaitingDINTaskCoordinatorAsSlasher` | +| `setDINTaskCoordinatorAsSlasher()` | → `AwaitingDINTaskAuditorAsSlasher` | +| `setDINTaskAuditorAsSlasher()` | → `AwaitingGenesisModel` | +| `setGenesisModelIpfsHash()` | → `GenesisModelCreated` | +| `_startGI()` | → `GIstarted` | +| `startDINaggregatorsRegistration()` | → `DINaggregatorsRegistrationStarted` | +| `closeDINaggregatorsRegistration()` | → `DINaggregatorsRegistrationClosed` | +| `startDINauditorsRegistration()` | → `DINauditorsRegistrationStarted` | +| `closeDINauditorsRegistration()` | → `DINauditorsRegistrationClosed` | +| `startLMsubmissions()` | → `LMSstarted` | +| `closeLMsubmissions()` | → `LMSclosed` | +| `createAuditorsBatches()` | → `AuditorsBatchesCreated` | +| `startLMsubmissionsEvaluation()` | → `LMSevaluationStarted` | +| `closeLMsubmissionsEvaluation()` | → `LMSevaluationClosed` | +| `autoCreateTier1AndTier2()` | → `T1nT2Bcreated` | +| `startT1Aggregation()` | → `T1AggregationStarted` | +| `finalizeT1Aggregation()` | → `T1AggregationDone` | +| `startT2Aggregation()` | → `T2AggregationStarted` | +| `finalizeT2Aggregation()` | → `T2AggregationDone` | +| `slashAuditors()` | → `AuditorsSlashed` | +| `slashAggregators()` | → `AggregatorsSlashed` | +| `endGI()` | → `GIended` | + +The indexer cannot track which phase a model is currently in without a storage +call per block — defeating the purpose of event-driven indexing. + +### §3.2 Missing submission events + +| Function | Contract | Impact | +|----------|---------|--------| +| `submitLocalModel()` | `DINTaskAuditor` | `lms.py ~line 62` iterates `getClientModels()` over RPC. Cannot be replaced with a GraphQL query without a `LocalModelSubmitted` event. | +| `submitT1Aggregation()` | `DINTaskCoordinator` | `aggregation.py ~lines 76, 95` iterates T1 batch submissions over RPC. No event means no indexer coverage. | +| `submitT2Aggregation()` | `DINTaskCoordinator` | `aggregation.py ~line 136` iterates T2 batch submissions over RPC. Same gap. | + +### §3.3 Missing finalization events + +`finalizeT1Aggregation()` iterates all T1 batches, tallies votes per CID, and +writes the majority CID to `b.finalCID` in storage — no event emitted. +`finalizeT2Aggregation()` does the same for the single T2 batch; its `b.finalCID` +is the **new global model CID** for the GI — the single most important value +produced by a completed iteration. + +Without finalization events, the indexer would have to re-implement the +vote-majority tallying logic in AssemblyScript from the submission events alone. +That is complex, divergence-prone, and fragile against any future change to the +tally logic. The contract has already computed the winner; it should emit it. + +--- + +## §4 Proposed additions + +All six additions are minimal — one declaration and one (or N, for +`GIStateChanged`) emit site per event. No storage changes, no new state. To be +landed as one small reviewed PR before handler implementation begins. + +### §4.1 GI state machine — catch-all event + +```solidity +event GIStateChanged(uint indexed GI, uint8 indexed newState); +``` + +**Implementation:** replace every `GIstate = GIstates.X;` assignment in +`DINTaskCoordinator` with a call to an internal setter: + +```solidity +function _setGIstate(GIstates newState) internal { + GIstate = newState; + emit GIStateChanged(GI, uint8(newState)); +} +``` + +One declaration, one emit site, and it is impossible to miss a future +transition when new states are added. `newState` is indexed so consumers +(including `dind` P4-7.1) can topic-filter for a specific phase — +e.g. `newState == 16` (`T1AggregationStarted`) to queue aggregation jobs. + +The schema maps `uint8` → a GraphQL enum mirroring `GIstates` from +`DINShared.sol`, so queries read as clearly as named events would. + +### §4.2 Local model submission + +```solidity +event LocalModelSubmitted( + uint indexed GI, + uint indexed modelIndex, + address indexed client, + bytes32 modelCID +); +``` + +Emit inside `DINTaskAuditor.submitLocalModel()` after the `lmSubmissions[_GI].push(...)`. +`modelIndex` is `lmSubmissions[_GI].length` before the push (already computed +as a local variable in the current code). + +### §4.3 T1 and T2 aggregation submission + +```solidity +event T1AggregationSubmitted( + uint indexed GI, + uint indexed batchId, + address indexed aggregator, + bytes32 cid +); + +event T2AggregationSubmitted( + uint indexed GI, + uint indexed batchId, + address indexed aggregator, + bytes32 cid +); +``` + +Both in `DINTaskCoordinator`. `T2AggregationSubmitted.batchId` is always `0` +today (`TC_OnlyOneTier2Batch` enforces it) — kept for symmetry with T1 and +future extensibility. + +### §4.4 T1 and T2 finalization + +```solidity +event T1BatchFinalized( + uint indexed GI, + uint indexed batchId, + bytes32 winningCID +); + +event T2Finalized( + uint indexed GI, + bytes32 globalModelCID +); +``` + +`T1BatchFinalized` emits once per batch inside the `finalizeT1Aggregation()` +loop, immediately after `b.finalCID = winningCID`. `T2Finalized` emits once +inside `finalizeT2Aggregation()` — `globalModelCID` is the T2 winning CID, +i.e. the new global model for this GI. + +--- + +## §5 Dead code — RewardDeposited + +`DINTaskAuditor` declares: + +```solidity +event RewardDeposited(address indexed modelOwner, uint256 amount); +``` + +No `depositRewards` function exists anywhere in the contract. The event is +never emitted. **Proposal:** remove in the same PR as the event additions above, +unless P3-5.1–5.3 (fee routing / reward distribution) introduces a real deposit +flow — that work should decide whether the event gets an emitter or is deleted. +Do not add an emitter here without the corresponding P3 design. + +--- + +## §6 Dynamic data source design note + +Task contracts are factory-deployed — one `DINTaskCoordinator` + `DINTaskAuditor` +pair per model. The subgraph will use **The Graph data source templates**: + +1. `DINModelRegistry` emits `ModelApproved` when a model is registered. +2. The `handleModelApproved` mapping handler reads `taskCoordinator` and + `taskAuditor` addresses from the `DINModelRegistry` storage call (same bridge + approach used in the platform-contract handlers). +3. The handler calls `DINTaskCoordinatorTemplate.create(taskCoordinatorAddress)` + and `DINTaskAuditorTemplate.create(taskAuditorAddress)` to instantiate live + indexing for that model's contracts. + +Entity design and AssemblyScript handler implementation follow once this audit +and the proposed contract additions are reviewed and merged. diff --git a/dincli/cli/dindao.py b/dincli/cli/dindao.py index 02a9a89..119d5ca 100644 --- a/dincli/cli/dindao.py +++ b/dincli/cli/dindao.py @@ -1,12 +1,36 @@ import os import time +import requests as _requests import typer from dincli.cli.contract_utils import get_contract_instance from dincli.cli.utils import (build_and_send_tx, get_env_key, load_din_info, resolve_task_coordinator_address, save_din_info) +_SUBGRAPH_URL = os.environ.get( + "DIN_SUBGRAPH_URL", + "http://localhost:8000/subgraphs/name/din-protocol/graphql", +) + + +def _query_subgraph(query: str, *, timeout: int = 5) -> dict | None: + """POST a GraphQL query to the local subgraph. Returns the data dict or None on any error.""" + try: + resp = _requests.post( + _SUBGRAPH_URL, + json={"query": query}, + timeout=timeout, + ) + resp.raise_for_status() + body = resp.json() + if "errors" in body: + return None + return body.get("data") + except Exception: + return None + + app = typer.Typer(help="Commands for DIN DAO") registry_app = typer.Typer(help="Registry sub-app (for 'dincli dindao registry to interact with DINRegistry ...')") @@ -438,31 +462,59 @@ def list_pending_requests(ctx: typer.Context, req_type: str = typer.Option(None, if normalized_type in (None, "model"): console.print("[bold cyan]Pending Model Registration Requests:[/bold cyan]") - totalModelRequests = DINModelRegistry_Contract.functions.totalModelRequests().call() found_model = False - for idx in range(totalModelRequests): - req = DINModelRegistry_Contract.functions.modelRequests(idx).call() - if not req[6]: + gql_data = _query_subgraph( + "{ modelRegistrationRequests(where: { processed: false }, first: 1000)" + " { requestId requester isOpenSource feePaid } }" + ) + if gql_data is not None: + for r in gql_data.get("modelRegistrationRequests", []): console.print( - f" [green]Request ID {idx}[/green] - Requester: {req[0]}, " - f"Open Source: {req[1]}, Fee: {w3.from_wei(req[5], 'ether')} ETH" + f" [green]Request ID {r['requestId']}[/green] - " + f"Requester: {r['requester']}, " + f"Open Source: {r['isOpenSource']}, " + f"Fee: {w3.from_wei(int(r['feePaid']), 'ether')} ETH" ) found_model = True + else: + totalModelRequests = DINModelRegistry_Contract.functions.totalModelRequests().call() + for idx in range(totalModelRequests): + req = DINModelRegistry_Contract.functions.modelRequests(idx).call() + if not req[6]: + console.print( + f" [green]Request ID {idx}[/green] - Requester: {req[0]}, " + f"Open Source: {req[1]}, Fee: {w3.from_wei(req[5], 'ether')} ETH" + ) + found_model = True if not found_model: console.print(" [gray]No pending model registration requests[/gray]") if normalized_type in (None, "manifest"): console.print("[bold cyan]Pending Manifest Update Requests:[/bold cyan]") - totalManifestRequests = DINModelRegistry_Contract.functions.totalManifestRequests().call() found_manifest = False - for idx in range(totalManifestRequests): - req = DINModelRegistry_Contract.functions.manifestRequests(idx).call() - if not req[4]: + gql_data = _query_subgraph( + "{ manifestUpdateRequests(where: { processed: false }, first: 1000)" + " { requestId model { modelId } requester feePaid } }" + ) + if gql_data is not None: + for r in gql_data.get("manifestUpdateRequests", []): console.print( - f" [green]Request ID {idx}[/green] - Model ID: {req[0]}, " - f"Requester: {req[2]}, Fee: {w3.from_wei(req[3], 'ether')} ETH" + f" [green]Request ID {r['requestId']}[/green] - " + f"Model ID: {r['model']['modelId']}, " + f"Requester: {r['requester']}, " + f"Fee: {w3.from_wei(int(r['feePaid']), 'ether')} ETH" ) found_manifest = True + else: + totalManifestRequests = DINModelRegistry_Contract.functions.totalManifestRequests().call() + for idx in range(totalManifestRequests): + req = DINModelRegistry_Contract.functions.manifestRequests(idx).call() + if not req[4]: + console.print( + f" [green]Request ID {idx}[/green] - Model ID: {req[0]}, " + f"Requester: {req[2]}, Fee: {w3.from_wei(req[3], 'ether')} ETH" + ) + found_manifest = True if not found_manifest: console.print(" [gray]No pending manifest update requests[/gray]") diff --git a/subgraph/.env.example b/subgraph/.env.example new file mode 100644 index 0000000..27d551b --- /dev/null +++ b/subgraph/.env.example @@ -0,0 +1,11 @@ +# Copy to .env and adjust if your local setup differs from the defaults. +# docker-compose.yml reads these values at container start time. + +# RPC endpoint for the local anvil/hardhat chain (chainId 1337). +# On Linux, replace host.docker.internal with the Docker bridge IP: 172.17.0.1 +ETHEREUM_RPC=http://host.docker.internal:8545 + +# Postgres credentials — change before any non-local deployment. +POSTGRES_USER=graph-node +POSTGRES_PASSWORD=din-graph-local +POSTGRES_DB=graph-node diff --git a/subgraph/abis/DINModelRegistry.json b/subgraph/abis/DINModelRegistry.json new file mode 100644 index 0000000..096023c --- /dev/null +++ b/subgraph/abis/DINModelRegistry.json @@ -0,0 +1,934 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_dinValidatorStake", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyProcessed", + "type": "error" + }, + { + "inputs": [], + "name": "AuditorNoLongerSlasher", + "type": "error" + }, + { + "inputs": [], + "name": "AuditorOwnershipChanged", + "type": "error" + }, + { + "inputs": [], + "name": "CoordinatorNoLongerSlasher", + "type": "error" + }, + { + "inputs": [], + "name": "CoordinatorOwnershipChanged", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidModelId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRequestId", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "ModelIsDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "NotDINDAOAdmin", + "type": "error" + }, + { + "inputs": [], + "name": "NotModelOwner", + "type": "error" + }, + { + "inputs": [], + "name": "NotOwnerOfTaskAuditor", + "type": "error" + }, + { + "inputs": [], + "name": "NotOwnerOfTaskCoordinator", + "type": "error" + }, + { + "inputs": [], + "name": "TaskAuditorAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "TaskCoordinatorAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "TaskCoordinatorEqualsTaskAuditor", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldAdmin", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "DAOAdminUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "openSourceFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proprietaryFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "openSourceUpdateFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proprietaryUpdateFee", + "type": "uint256" + } + ], + "name": "FeesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FeesWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "ManifestUpdateRejected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "ManifestUpdateRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "newCID", + "type": "bytes32" + } + ], + "name": "ManifestUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "ModelApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "ModelDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "ModelEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "requester", + "type": "address" + } + ], + "name": "ModelRegistrationRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "ModelRejected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "OpenSourceFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "OpenSourceUpdateFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "ProprietaryFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "ProprietaryUpdateFeeUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "approveManifestUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "approveModel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "daoAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dinValidatorStake", + "outputs": [ + { + "internalType": "contract IDinValidatorStake", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "disableModel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "enableModel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "name": "getModel", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bool", + "name": "isOpenSource", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "manifestCID", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "taskCoordinator", + "type": "address" + }, + { + "internalType": "address", + "name": "taskAuditor", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "taskAuditor", + "type": "address" + } + ], + "name": "getModelIdByTaskAuditor", + "outputs": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "taskCoordinator", + "type": "address" + } + ], + "name": "getModelIdByTaskCoordinator", + "outputs": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "manifestRequests", + "outputs": [ + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "newManifestCID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feePaid", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "processed", + "type": "bool" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "modelDisabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "modelRequests", + "outputs": [ + { + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "internalType": "bool", + "name": "isOpenSource", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "manifestCID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taskCoordinator", + "type": "address" + }, + { + "internalType": "address", + "name": "taskAuditor", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feePaid", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "processed", + "type": "bool" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "openSourceFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "openSourceUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proprietaryFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proprietaryUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "rejectManifestUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "rejectModel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "modelId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "newManifestCID", + "type": "bytes32" + } + ], + "name": "requestManifestUpdate", + "outputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "manifestCID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taskCoordinator", + "type": "address" + }, + { + "internalType": "address", + "name": "taskAuditor", + "type": "address" + }, + { + "internalType": "bool", + "name": "isOpenSource", + "type": "bool" + } + ], + "name": "requestModelRegistration", + "outputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "setDAOAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_openSourceFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_proprietaryFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_openSourceUpdateFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_proprietaryUpdateFee", + "type": "uint256" + } + ], + "name": "setFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "setOpenSourceFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "setOpenSourceUpdateFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "setProprietaryFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "setProprietaryUpdateFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalManifestRequests", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalModelRequests", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalModels", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "to", + "type": "address" + } + ], + "name": "withdrawFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/subgraph/abis/DINTaskAuditor.json b/subgraph/abis/DINTaskAuditor.json new file mode 100644 index 0000000..3098c7f --- /dev/null +++ b/subgraph/abis/DINTaskAuditor.json @@ -0,0 +1,1221 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_dinvalidatorStakeContract_address", + "type": "address" + }, + { + "internalType": "address", + "name": "_dintaskcoordinator_contract_address", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "TA_AlreadySubmitted", + "type": "error" + }, + { + "inputs": [], + "name": "TA_AlreadyVoted", + "type": "error" + }, + { + "inputs": [], + "name": "TA_AuditorAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "TA_AuditorNotActive", + "type": "error" + }, + { + "inputs": [], + "name": "TA_AuditorRegistrationNotOpen", + "type": "error" + }, + { + "inputs": [], + "name": "TA_BatchDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "TA_BatchIDMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "TA_CannotCreateAuditorsBatches", + "type": "error" + }, + { + "inputs": [], + "name": "TA_CannotFinalizeEvaluation", + "type": "error" + }, + { + "inputs": [], + "name": "TA_CannotSetAuditScore", + "type": "error" + }, + { + "inputs": [], + "name": "TA_CannotSetTestDataAssignedFlag", + "type": "error" + }, + { + "inputs": [], + "name": "TA_FlagAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "TA_FlagMustBeTrue", + "type": "error" + }, + { + "inputs": [], + "name": "TA_InvalidModelIndex", + "type": "error" + }, + { + "inputs": [], + "name": "TA_InvalidPassScore", + "type": "error" + }, + { + "inputs": [], + "name": "TA_LMSubmissionsNotOpen", + "type": "error" + }, + { + "inputs": [], + "name": "TA_MaxLMSubmissionsReached", + "type": "error" + }, + { + "inputs": [], + "name": "TA_NotAssignedAuditor", + "type": "error" + }, + { + "inputs": [], + "name": "TA_NotEnoughAuditors", + "type": "error" + }, + { + "inputs": [], + "name": "TA_NotTaskCoordinator", + "type": "error" + }, + { + "inputs": [], + "name": "TA_ScoreOutOfRange", + "type": "error" + }, + { + "inputs": [], + "name": "TA_WrongGI", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "gi", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "auditor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "modelIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "AuditScoreSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "gi", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "auditor", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "reason", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "requested", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "AuditorSlashed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + } + ], + "name": "AuditorsBatchAuto", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "batchCount", + "type": "uint256" + } + ], + "name": "AuditorsBatchesCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "auditor", + "type": "address" + } + ], + "name": "DINAuditorRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "gi", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "modelIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "eligible", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalVotes", + "type": "uint256" + } + ], + "name": "EligibilityFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "gi", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "modelIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "auditor", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "vote", + "type": "bool" + } + ], + "name": "EligibilityVoted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldScore", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newScore", + "type": "uint256" + } + ], + "name": "PassScoreUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "modelOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardDeposited", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "AuditorsBatchCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "Is_testdataCIDs_Assigned", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "LMeligibleVote", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "approvedModelIndexes", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gi", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "testDataCID", + "type": "bytes32" + } + ], + "name": "assignAuditTestDataset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "auditBatches", + "outputs": [ + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "testDataCID", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "auditScores", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "clientHasSubmitted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "clientSubmissionIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "createAuditorsBatches", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "dinAuditors", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dintaskcoordinatorContract", + "outputs": [ + { + "internalType": "contract IDINTaskCoordinator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dinvalidatorStakeContract", + "outputs": [ + { + "internalType": "contract IDinValidatorStake", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "finalizeEvaluation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_batchId", + "type": "uint256" + } + ], + "name": "getAuditorsBatch", + "outputs": [ + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "auditors", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "modelIndexes", + "type": "uint256[]" + }, + { + "internalType": "bytes32", + "name": "testDataCID", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "getClientModels", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "client", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "modelCID", + "type": "bytes32" + }, + { + "internalType": "uint40", + "name": "submittedAt", + "type": "uint40" + }, + { + "internalType": "bool", + "name": "eligible", + "type": "bool" + }, + { + "internalType": "bool", + "name": "evaluated", + "type": "bool" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "finalAvgScore", + "type": "uint256" + } + ], + "internalType": "struct DINTaskAuditor.LMSubmission[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "getDINtaskAuditors", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "hasAuditedLM", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isBatchAuditor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "isBatchModelIndex", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isRegisteredAuditor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "lmSubmissions", + "outputs": [ + { + "internalType": "address", + "name": "client", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "modelCID", + "type": "bytes32" + }, + { + "internalType": "uint40", + "name": "submittedAt", + "type": "uint40" + }, + { + "internalType": "bool", + "name": "eligible", + "type": "bool" + }, + { + "internalType": "bool", + "name": "evaluated", + "type": "bool" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "finalAvgScore", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "params", + "outputs": [ + { + "internalType": "uint256", + "name": "auditorsPerBatch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "modelsPerBatch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minEligibilityQuorum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minScoreQuorum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "passScore", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "MIN_MODELS_PER_BATCH", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "registerDINAuditor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gi", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "modelIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "vote", + "type": "bool" + } + ], + "name": "setAuditScorenEligibility", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "flag", + "type": "bool" + } + ], + "name": "setTestDataAssignedFlag", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "slashAuditors", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_clientModel", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "submitLocalModel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalDepositedRewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newPassScore", + "type": "uint256" + } + ], + "name": "updatePassScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/subgraph/abis/DINTaskCoordinator.json b/subgraph/abis/DINTaskCoordinator.json new file mode 100644 index 0000000..33db992 --- /dev/null +++ b/subgraph/abis/DINTaskCoordinator.json @@ -0,0 +1,1339 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "dinvalidatorStakeContract_address", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AggregatorAlreadyRegistered", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AggregatorNotActive", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AggregatorsRegistrationCannotBeFinished", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AggregatorsRegistrationCannotBeStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AggregatorsRegistrationNotOpen", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AlreadySubmitted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AuditorCannotBeSetAsSlasher", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AuditorIsNotSlasher", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AuditorsRegistrationCannotBeFinished", + "type": "error" + }, + { + "inputs": [], + "name": "TC_AuditorsRegistrationCannotBeStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_BatchNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "TC_CannotSetTestDataAssignedFlag", + "type": "error" + }, + { + "inputs": [], + "name": "TC_CoordinatorCannotBeSetAsSlasher", + "type": "error" + }, + { + "inputs": [], + "name": "TC_CoordinatorIsNotSlasher", + "type": "error" + }, + { + "inputs": [], + "name": "TC_EvalPhaseNotClosed", + "type": "error" + }, + { + "inputs": [], + "name": "TC_FailedToCreateAuditorsBatches", + "type": "error" + }, + { + "inputs": [], + "name": "TC_FailedToFinalizeEvaluation", + "type": "error" + }, + { + "inputs": [], + "name": "TC_FailedToSlashAuditors", + "type": "error" + }, + { + "inputs": [], + "name": "TC_GICannotBeStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_GenesisModelHashCannotBeSet", + "type": "error" + }, + { + "inputs": [], + "name": "TC_InvalidBatch", + "type": "error" + }, + { + "inputs": [], + "name": "TC_LMEvalCannotBeFinished", + "type": "error" + }, + { + "inputs": [], + "name": "TC_LMEvalCannotBeStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_LMSubmissionsCannotBeStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_LMSubmissionsNotStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NoSubmissions", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotBatchAggregator", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotEnoughApprovedModels", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotEnoughValidators", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyForT1Aggregation", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyForT2Aggregation", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyToEndGI", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyToFinalizeT1", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyToFinalizeT2", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyToSetTier2Score", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyToSlashAggregators", + "type": "error" + }, + { + "inputs": [], + "name": "TC_NotReadyToSlashAuditors", + "type": "error" + }, + { + "inputs": [], + "name": "TC_OnlyOneTier2Batch", + "type": "error" + }, + { + "inputs": [], + "name": "TC_T1AggregationNotStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_T2AggregationNotStarted", + "type": "error" + }, + { + "inputs": [], + "name": "TC_TaskAuditorContractCannotBeSet", + "type": "error" + }, + { + "inputs": [], + "name": "TC_WrongGI", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "aggregator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "reason", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "requested", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "actual", + "type": "uint256" + } + ], + "name": "AggregatorSlashed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "DINValidatorRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + } + ], + "name": "Tier1BatchAuto", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "GI", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + } + ], + "name": "Tier2BatchAuto", + "type": "event" + }, + { + "inputs": [], + "name": "GI", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GIstate", + "outputs": [ + { + "internalType": "enum GIstates", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_T1_MODELS_PER_BATCH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "T1_AGGREGATORS_PER_BATCH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "T1_MODELS_PER_BATCH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "autoCreateTier1AndTier2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "closeDINaggregatorsRegistration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "closeDINauditorsRegistration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "closeLMsubmissions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "closeLMsubmissionsEvaluation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "createAuditorsBatches", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "dinAggregators", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dinTaskAuditorContract", + "outputs": [ + { + "internalType": "contract IDINTaskAuditor", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dinvalidatorStakeContract", + "outputs": [ + { + "internalType": "contract IDinValidatorStake", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "endGI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "finalizeT1Aggregation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "finalizeT2Aggregation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "genesisModelIpfsHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "getDINtaskAggregators", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + } + ], + "name": "getTier1Batch", + "outputs": [ + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "validators", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "modelIndexes", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "finalized", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "finalCID", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + } + ], + "name": "getTier2Batch", + "outputs": [ + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "validators", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "finalized", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "finalCID", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "getTier2Score", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isDINAggregator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "registerDINaggregator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "setDINTaskAuditorAsSlasher", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_dintaskauditor_contract_address", + "type": "address" + } + ], + "name": "setDINTaskAuditorContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "setDINTaskCoordinatorAsSlasher", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_genesisModelIpfsHash", + "type": "bytes32" + } + ], + "name": "setGenesisModelIpfsHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "flag", + "type": "bool" + } + ], + "name": "setTestDataAssignedFlag", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_score", + "type": "uint256" + } + ], + "name": "setTier2Score", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "slashAggregators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "slashAuditors", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startDINaggregatorsRegistration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startDINauditorsRegistration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startGI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "startGI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startLMsubmissions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startLMsubmissionsEvaluation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startT1Aggregation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "startT2Aggregation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_batchId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_aggregationCID", + "type": "bytes32" + } + ], + "name": "submitT1Aggregation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_batchId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_aggregationCID", + "type": "bytes32" + } + ], + "name": "submitT2Aggregation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "t1SubmissionCID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "t1Submitted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "t1Votes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "t2SubmissionCID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "t2Submitted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "t2Votes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_GI", + "type": "uint256" + } + ], + "name": "tier1BatchCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tier1Batches", + "outputs": [ + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "finalized", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "finalCID", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tier2Batches", + "outputs": [ + { + "internalType": "uint256", + "name": "batchId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "finalized", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "finalCID", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tier2Score", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/subgraph/abis/DinCoordinator.json b/subgraph/abis/DinCoordinator.json new file mode 100644 index 0000000..82f390d --- /dev/null +++ b/subgraph/abis/DinCoordinator.json @@ -0,0 +1,290 @@ +{ + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidAddress", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [], + "name": "TransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ValidatorStakeContractNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValue", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "DinPerEthUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintAmount", + "type": "uint256" + } + ], + "name": "EthDepositAndDINminted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "slasher", + "type": "address" + } + ], + "name": "SlasherContractAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "slasher", + "type": "address" + } + ], + "name": "SlasherContractRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validatorStakeContract", + "type": "address" + } + ], + "name": "ValidatorStakeContractUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "slasherContract", + "type": "address" + } + ], + "name": "addSlasherContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositAndMint", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "dinPerEth", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dinToken", + "outputs": [ + { + "internalType": "contract DinToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dinValidatorStakeContract", + "outputs": [ + { + "internalType": "contract IDinValidatorStake", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "slasherContract", + "type": "address" + } + ], + "name": "removeSlasherContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "updateDinPerEth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validatorStakeContract", + "type": "address" + } + ], + "name": "updateValidatorStakeContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/subgraph/abis/DinToken.json b/subgraph/abis/DinToken.json new file mode 100644 index 0000000..1b7e09c --- /dev/null +++ b/subgraph/abis/DinToken.json @@ -0,0 +1,383 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "owner_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAddress", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokensMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "OWNER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/subgraph/abis/DinValidatorStake.json b/subgraph/abis/DinValidatorStake.json new file mode 100644 index 0000000..cb28532 --- /dev/null +++ b/subgraph/abis/DinValidatorStake.json @@ -0,0 +1,639 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "dinToken", + "type": "address" + }, + { + "internalType": "address", + "name": "dinCoordinator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AmountLessThanMinStake", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAddress", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSlashAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidUnstakeAmount", + "type": "error" + }, + { + "inputs": [], + "name": "NoPendingWithdrawal", + "type": "error" + }, + { + "inputs": [], + "name": "NotDINCoordinator", + "type": "error" + }, + { + "inputs": [], + "name": "NotEnoughStake", + "type": "error" + }, + { + "inputs": [], + "name": "NotSlasherContract", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "PendingWithdrawalExists", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [], + "name": "SlasherContractAlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "SlasherContractNotAdded", + "type": "error" + }, + { + "inputs": [], + "name": "ValidatorIsBlacklisted", + "type": "error" + }, + { + "inputs": [], + "name": "ValidatorNotBlacklisted", + "type": "error" + }, + { + "inputs": [], + "name": "WithdrawalNotReady", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "slasher", + "type": "address" + } + ], + "name": "SlasherContractAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "slasher", + "type": "address" + } + ], + "name": "SlasherContractRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "ValidatorBlacklisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "reason", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "slasher", + "type": "address" + } + ], + "name": "ValidatorSlashed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ValidatorStaked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "ValidatorUnblacklisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "withdrawAvailableAt", + "type": "uint64" + } + ], + "name": "ValidatorUnstakeRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ValidatorWithdrawalClaimed", + "type": "event" + }, + { + "inputs": [], + "name": "DIN_COORDINATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DIN_TOKEN", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_STAKE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UNBONDING_PERIOD", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "slasherContract", + "type": "address" + } + ], + "name": "addSlasherContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "blacklistValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimUnstaked", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "getStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "slasherContract", + "type": "address" + } + ], + "name": "isSlasherContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "isValidatorActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "slasherContract", + "type": "address" + } + ], + "name": "removeSlasherContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "reason", + "type": "bytes32" + } + ], + "name": "slash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "slashableStakeOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "slasherContracts", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "stake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "unblacklistValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unstake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "validators", + "outputs": [ + { + "internalType": "uint256", + "name": "activeStake", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pendingWithdrawals", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "withdrawAvailableAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "jailedUntil", + "type": "uint64" + }, + { + "internalType": "enum DinValidatorStake.ValidatorStatus", + "name": "status", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/subgraph/docker-compose.yml b/subgraph/docker-compose.yml new file mode 100644 index 0000000..e0452e0 --- /dev/null +++ b/subgraph/docker-compose.yml @@ -0,0 +1,97 @@ +# DIN Protocol — local Graph node stack +# +# Runs a self-hosted graph-node, IPFS, and Postgres against the local anvil +# chain (chainId 1337) for subgraph development and testing. +# +# Quick start +# ─────────── +# 1. Start the local anvil chain first: +# ./foundry/anvil.sh (from repo root) +# +# 2. Deploy the platform contracts and copy their addresses into +# subgraph.yaml (replace the 0x0000...0001–0004 placeholders): +# cd hardhat && npx hardhat run scripts/deploy.ts --network localhost +# +# 3. Start the Graph node stack: +# cd subgraph && docker compose up -d +# +# 4. Wait ~10 s for graph-node to connect to Postgres and IPFS, then +# create and deploy the subgraph: +# npm run codegen +# npm run build +# npm run create:local +# npm run deploy:local +# +# 5. Query at http://localhost:8000/subgraphs/name/din-protocol/graphql +# +# Teardown +# ──────── +# docker compose down -v # removes containers AND volumes (fresh slate) +# docker compose down # stops containers, keeps indexed data + +services: + + graph-node: + image: graphprotocol/graph-node:v0.34.1 + ports: + - "8000:8000" # GraphQL HTTP endpoint + - "8001:8001" # GraphQL WebSocket endpoint + - "8020:8020" # Admin / subgraph deploy RPC + - "8030:8030" # Index-node status API + - "8040:8040" # Prometheus metrics + depends_on: + postgres: + condition: service_healthy + ipfs: + condition: service_started + environment: + postgres_host: postgres + postgres_user: graph-node + postgres_pass: din-graph-local + postgres_db: graph-node + ipfs: "ipfs:5001" + # din-local must match the network name in subgraph.yaml. + # host.docker.internal resolves to the host on Docker Desktop (macOS/Win). + # On Linux replace with the Docker bridge IP: 172.17.0.1 + ethereum: "din-local:http://host.docker.internal:8545" + GRAPH_LOG: info + # Allow the graph-node to connect to a chain that has not finalised + # many blocks (local anvil produces blocks on demand). + GRAPH_ETHEREUM_TARGET_TRIGGERS_PER_BLOCK_RANGE: "1" + restart: unless-stopped + + ipfs: + image: ipfs/kubo:v0.27.0 + ports: + - "5001:5001" # IPFS API (used by graph-node and graph deploy) + volumes: + - ipfs-data:/data/ipfs + healthcheck: + test: ["CMD", "ipfs", "id"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + + postgres: + image: postgres:16-alpine + ports: + - "5432:5432" + environment: + POSTGRES_USER: graph-node + POSTGRES_PASSWORD: din-graph-local + POSTGRES_DB: graph-node + # Required by graph-node to use TOAST compression for large bytea values. + POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U graph-node -d graph-node"] + interval: 5s + timeout: 5s + retries: 10 + restart: unless-stopped + +volumes: + ipfs-data: + postgres-data: diff --git a/subgraph/docs/daemon-event-schema.md b/subgraph/docs/daemon-event-schema.md new file mode 100644 index 0000000..5eace65 --- /dev/null +++ b/subgraph/docs/daemon-event-schema.md @@ -0,0 +1,244 @@ +# Daemon Event Schema — DIN Platform Contracts (P4-IDX1) + +**Consumer:** `dind` — the DIN daemon (issue #21, P4-7.1) +**Authored by:** Robbert (P4-IDX1 deliverable) +**Status:** Design — feeds Santiago's P4-7.1 on-chain event listening engine + +--- + +## Purpose + +This document defines which platform-contract events `dind` must subscribe to, +what each event means for the daemon's job queue, and where current event +coverage is insufficient. It is the handoff from the indexer design (P4-IDX1) +to the daemon implementation (P4-7.1). + +The indexer subgraph and the daemon share the same event surface. The subgraph +indexes events for historical queries and pagination; the daemon subscribes to +the same events in real time to trigger job execution without manual CLI +invocation. + +--- + +## 1. Subscription priority + +Events are grouped by how immediately the daemon must react. + +### 1.1 Immediate — daemon must act within the same block or next block + +| Event | Contract | Trigger | Daemon action | +|---|---|---|---| +| `ValidatorBlacklisted(address validator)` | DinValidatorStake | Validator address matches local operator | **Halt all active tasks immediately.** Stop any in-flight LMS, aggregation, or audit jobs. Alert operator via structured log at CRITICAL level. | +| `ModelDisabled(uint256 modelId)` | DINModelRegistry | modelId is one the local operator runs | Abort any in-flight work for that model. Remove model from active-participation list. Alert operator. | + +### 1.2 High-priority — daemon should react within a few blocks + +| Event | Contract | Trigger | Daemon action | +|---|---|---|---| +| `ValidatorSlashed(address validator, uint256 amount, bytes32 reason, address slasher)` | DinValidatorStake | Validator address matches local operator | Log slash with amount and reason. Recalculate active stake. If stake falls below `MIN_STAKE`, suspend new task registration until stake is topped up. | +| `ModelApproved(uint256 requestId, uint256 modelId)` | DINModelRegistry | requester matches local operator | Model registration confirmed. Cache the new modelId locally; the operator can now run `dincli model-owner gi start`. In future daemon automation, this triggers the GI-start workflow. | +| `ManifestUpdated(uint256 requestId, uint256 modelId, bytes32 newCID)` | DINModelRegistry | modelId is one the local operator participates in | Invalidate local manifest cache for that modelId. Re-fetch manifest from IPFS at the new CID before the next task action. | +| `ValidatorUnblacklisted(address validator)` | DinValidatorStake | Validator address matches local operator | Restore participation eligibility. Log at INFO level; do not auto-resume — operator must explicitly re-register for active tasks. | + +### 1.3 Informational — daemon should record and expose via `/health` or status API + +| Event | Contract | Notes | +|---|---|---| +| `ModelRegistrationRequested(uint256 requestId, address requester)` | DINModelRegistry | Relevant to DAO admin operators: new request pending review. Notify via log. | +| `ManifestUpdateRequested(uint256 requestId, uint256 modelId)` | DINModelRegistry | Same — DAO admin operators should be alerted. | +| `ModelRejected(uint256 requestId)` | DINModelRegistry | Relevant if local operator was the requester. | +| `ManifestUpdateRejected(uint256 requestId)` | DINModelRegistry | Same. | +| `ModelEnabled(uint256 modelId)` | DINModelRegistry | Model re-enabled after a kill-switch. Operator may choose to resume participation. | +| `ValidatorStaked(address validator, uint256 amount)` | DinValidatorStake | Useful for dashboard: record in local stake history. | +| `ValidatorUnstakeRequested(address validator, uint256 amount, uint64 withdrawAvailableAt)` | DinValidatorStake | Record in local unstake tracker. Daemon can surface `withdrawAvailableAt` countdown in `/health`. | +| `ValidatorWithdrawalClaimed(address validator, uint256 amount)` | DinValidatorStake | Record completion of withdrawal in local history. | +| `DinPerEthUpdated(uint256 newRate)` | DinCoordinator | Update local exchange-rate cache. Relevant for cost estimation in future reward projections. | +| `DAOAdminUpdated(address oldAdmin, address newAdmin)` | DINModelRegistry | Alert operator of governance-key rotation. Log at WARN level. | +| `EthDepositAndDINminted(address user, uint256 ethAmount, uint256 dinAmount)` | DinCoordinator | Relevant only if user matches local operator wallet. Record in local token history. | + +--- + +## 2. Event payload reference + +Full field inventory for events the daemon consumes. Includes current field +set and pending additions from the event-coverage audit. + +### DINModelRegistry + +``` +ModelRegistrationRequested( + uint256 indexed requestId, + address indexed requester + // PENDING: bool isOpenSource (audit §6.1) + // PENDING: uint256 fee (audit §6.1) +) + +ModelApproved( + uint256 indexed requestId, + uint256 indexed modelId +) + +ModelRejected( + uint256 indexed requestId +) + +ManifestUpdateRequested( + uint256 indexed requestId, + uint256 indexed modelId + // PENDING: address indexed requester (audit §6.2) +) + +ManifestUpdated( + uint256 indexed requestId, + uint256 indexed modelId, + bytes32 newCID +) + +ManifestUpdateRejected( + uint256 indexed requestId +) + +ModelDisabled(uint256 indexed modelId) +ModelEnabled(uint256 indexed modelId) + +DAOAdminUpdated( + address indexed oldAdmin, + address indexed newAdmin +) +``` + +### DinValidatorStake + +``` +ValidatorStaked( + address indexed validator, + uint256 amount +) + +ValidatorSlashed( + address indexed validator, + uint256 amount, + bytes32 indexed reason, + address indexed slasher +) + +ValidatorUnstakeRequested( + address indexed validator, + uint256 amount, + uint64 withdrawAvailableAt +) + +ValidatorWithdrawalClaimed( + address indexed validator, + uint256 amount +) + +ValidatorBlacklisted(address indexed validator) +ValidatorUnblacklisted(address indexed validator) +``` + +### DinCoordinator + +``` +EthDepositAndDINminted( + address indexed user, + uint256 ethAmount, + uint256 mintAmount +) + +DinPerEthUpdated(uint256 newRate) + +ValidatorStakeContractUpdated(address indexed validatorStakeContract) + +// PENDING: ETHTreasuryWithdrawn(address indexed to, uint256 amount) +// (audit §6.3 — not yet emitted by withdraw()) +``` + +--- + +## 3. Coverage gaps + +### 3.1 Task-level GI events — out of scope for platform subgraph + +P4-7.1 notes that the daemon must "at minimum wire: `T1AggregationStarted` → +queue aggregation job." None of the GI-lifecycle events exist on the four +platform contracts. They live on the task-level contracts `DINTaskCoordinator` +and `DINTaskAuditor`, which are out of scope for this subgraph (P5+). + +Events the daemon needs from task-level contracts once those are indexed: + +| Event | Contract | Daemon action | +|---|---|---| +| `DINaggregatorsRegistrationStarted` | DINTaskCoordinator | Queue aggregator registration job | +| `DINauditorsRegistrationStarted` | DINTaskCoordinator | Queue auditor registration job | +| `LMSstarted` | DINTaskCoordinator | Queue local model submission job | +| `AuditorsBatchesCreated` | DINTaskCoordinator | Trigger test data fetch for assigned auditor batch | +| `LMSevaluationStarted` | DINTaskCoordinator | Queue audit scoring job | +| `T1AggregationStarted` | DINTaskCoordinator | Queue T1 aggregation job (explicitly called out in P4-7.1) | +| `T2AggregationStarted` | DINTaskCoordinator | Queue T2 aggregation job | +| `GIended` | DINTaskCoordinator | Finalize round, archive local model artifacts | + +These events do not exist in the task contracts under these names today — the +`GIstates` enum drives state transitions but the state-change itself is not +emitted as an event with an indexed GI number. When task-level indexing is +designed (P5+), each `GIstate` transition should emit a dedicated event with +`uint256 indexed modelId` and `uint256 indexed gi` so the daemon can filter +by model and iteration without scanning all events. + +### 3.2 `ValidatorJailed` — dead code path + +`DinValidatorStake` has a `Jailed` status and a `jailedUntil` timestamp but no +function that sets `jailedUntil` and no `ValidatorJailed` event. When the +slashing/dispute spec (P3-4.x) lands a jailing mechanism, it must emit: + +``` +ValidatorJailed( + address indexed validator, + uint64 jailedUntil +) +``` + +Without this event the daemon cannot react to a jailing in real time — it +would only discover the status change on the next stake-related event for that +validator. + +### 3.3 `ETHTreasuryWithdrawn` — missing + +`DinCoordinator.withdraw()` emits no event. The daemon has no way to observe +ETH treasury outflows. Proposed addition documented in audit §6.3. + +--- + +## 4. Subscription mechanics for Santiago (P4-7.1) + +The daemon can subscribe to events by polling the JSON-RPC `eth_getLogs` +endpoint with the contract address and topic filters, or by using a WebSocket +connection (`eth_subscribe` with `logs` filter). + +Recommended approach for `dind` v1: +- **WebSocket `eth_subscribe` logs filter** per contract address — one + subscription per contract, four total. Lower latency than polling and + avoids block re-org edge cases that polling must handle explicitly. +- Maintain a local `lastProcessedBlock` per contract in the daemon's + persistent state store (SQLite or JSON per P4-1.1 choice) so subscriptions + can be replayed from the correct block on daemon restart. +- Filter by `address` + `topics[0]` (event selector). The Graph node itself + uses the same mechanism internally. + +The indexer subgraph handles historical state (indexer as the query layer); +the daemon handles real-time reaction (websocket subscriptions as the trigger +layer). The two paths are complementary — the daemon does not need to re-index +historical events, only respond to new ones. + +--- + +## 5. Integration order + +Per Umer (2026-07-09): dincli first, then SDK, then daemon — in that order. + +| Phase | Consumer | Integration point | +|---|---|---| +| P4-IDX2 (now) | `dincli` | Replace `dindao.py` RPC loop with GraphQL query to subgraph | +| P4-IDX3 | `dincli` test suite | Wire call site replacement into existing tests | +| P4-1.2 (SDK extraction) | `dincli/sdk/` | Event subscription helpers extracted from CLI internals | +| P4-7.1 | `dind` | Santiago implements listening engine using this schema as spec | diff --git a/subgraph/docs/event-coverage-audit.md b/subgraph/docs/event-coverage-audit.md new file mode 100644 index 0000000..ce42c4e --- /dev/null +++ b/subgraph/docs/event-coverage-audit.md @@ -0,0 +1,239 @@ +# Event Coverage Audit — DIN Platform Contracts + +**Scope:** The four platform contracts indexed in P4-IDX1: +`DINModelRegistry`, `DinValidatorStake`, `DinCoordinator`, `DinToken`. + +**Principle:** Every meaningful state transition must be reconstructable +from events alone. Where reconstruction requires a storage call, a minimal +event addition is preferable to a call handler — call handlers are not +supported on all Graph node backends and degrade indexing performance. + +**Outcome:** Two confirmed event additions (coordinated with Umer, 2026-07-09) +to be landed as one small PR after this audit is reviewed. No storage-layout +changes. All four contracts are behind proxies (PR #13), so re-deploying +locally with updated event signatures is low-friction. + +--- + +## 1. DINModelRegistry + +### 1.1 Events audited + +| Event | Indexed fields | Non-indexed fields | Assessment | +|---|---|---|---| +| `ModelRegistrationRequested` | `requestId`, `requester` | — | **Gap — see §3.1** | +| `ModelApproved` | `requestId`, `modelId` | — | Sufficient | +| `ModelRejected` | `requestId` | — | Sufficient | +| `ManifestUpdateRequested` | `requestId`, `modelId` | — | **Gap — see §3.2** | +| `ManifestUpdated` | `requestId`, `modelId` | `newCID` | Sufficient | +| `ManifestUpdateRejected` | `requestId` | — | Sufficient | +| `ModelDisabled` | `modelId` | — | Sufficient | +| `ModelEnabled` | `modelId` | — | Sufficient | +| `OpenSourceFeeUpdated` | — | `newFee` | Sufficient for timeline; no old-value field but this is acceptable | +| `ProprietaryFeeUpdated` | — | `newFee` | Same as above | +| `OpenSourceUpdateFeeUpdated` | — | `newFee` | Same as above | +| `ProprietaryUpdateFeeUpdated` | — | `newFee` | Same as above | +| `FeesUpdated` | — | `openSourceFee`, `proprietaryFee`, `openSourceUpdateFee`, `proprietaryUpdateFee` | Sufficient for atomic governance proposals | +| `FeesWithdrawn` | `to` | `amount` | Sufficient | +| `DAOAdminUpdated` | `oldAdmin`, `newAdmin` | — | Sufficient | + +### 1.2 Missing transitions + +**No event on `ModelRequest` task-contract addresses.** `ModelRegistrationRequested` +does not include `taskCoordinator` or `taskAuditor`. These can be recovered from the +`ModelApproved` path via the on-chain `getModel()` view, but that requires a call at +index time. The proposed addition in §3.1 does not cover these fields — they are an +additional gap. For v1 the indexer will query them via the `ModelApproved` handler +(the contracts are local-only so a call in the mapping is currently acceptable as a +one-time lookup on approval, not on every request). Flagged as a follow-up candidate +for a future event extension. + +--- + +## 2. DinValidatorStake + +### 2.1 Events audited + +| Event | Indexed fields | Non-indexed fields | Assessment | +|---|---|---|---| +| `ValidatorStaked` | `validator` | `amount` | Sufficient | +| `ValidatorSlashed` | `validator`, `reason`, `slasher` | `amount` | Sufficient — all key query fields indexed | +| `ValidatorUnstakeRequested` | `validator` | `amount`, `withdrawAvailableAt` | Sufficient | +| `ValidatorWithdrawalClaimed` | `validator` | `amount` | Sufficient | +| `ValidatorBlacklisted` | `validator` | — | Sufficient | +| `ValidatorUnblacklisted` | `validator` | — | Sufficient | +| `SlasherContractAdded` | `slasher` | — | **Signature conflict — see §4** | +| `SlasherContractRemoved` | `slasher` | — | **Signature conflict — see §4** | + +### 2.2 Missing transitions + +**No `ValidatorJailed` event.** `ValidatorInfo` carries a `jailedUntil` timestamp and +the `_syncValidatorStatus` helper transitions validators into `Jailed` status when +`jailedUntil > block.timestamp`, but no function in the current contract sets +`jailedUntil` or emits a jailing event. The Jailed status path is currently dead code. +No action required for the indexer now; flagged for the slashing/dispute spec (P3-4.x) +— when a jailing mechanism is added, it must emit a `ValidatorJailed` event with the +`jailedUntil` timestamp for the indexer to reconstruct status correctly. + +**Validator status derivable from existing events.** Current live status transitions +(None → Active → Exiting → Active, Blacklisted, Unblacklisted) are fully reconstructable +from `ValidatorStaked`, `ValidatorUnstakeRequested`, `ValidatorWithdrawalClaimed`, +`ValidatorBlacklisted`, and `ValidatorUnblacklisted`. No additional event needed for +the current status machine. + +--- + +## 3. DinCoordinator + +### 3.1 Events audited + +| Event | Indexed fields | Non-indexed fields | Assessment | +|---|---|---|---| +| `EthDepositAndDINminted` | `user` | `ethAmount`, `mintAmount` | Sufficient | +| `SlasherContractAdded` | `slasher` | — | **Signature conflict — see §4** | +| `SlasherContractRemoved` | `slasher` | — | **Signature conflict — see §4** | +| `ValidatorStakeContractUpdated` | `validatorStakeContract` | — | Sufficient | +| `DinPerEthUpdated` | — | `newRate` | Sufficient; old rate omitted but can be derived from the previous snapshot entity | + +### 3.2 Missing transitions + +**`withdraw()` emits no event.** The owner ETH withdrawal function transfers the +coordinator's entire ETH balance with no event. This makes treasury outflow invisible +to the indexer. + +**Proposed addition:** `ETHTreasuryWithdrawn(address indexed to, uint256 amount)` +emitted at the end of `withdraw()`. This is a pure observatory addition — one event, +one line. See §3.3. + +--- + +## 4. DinToken + +### 4.1 Events audited + +| Event | Indexed fields | Non-indexed fields | Assessment | +|---|---|---|---| +| `TokensMinted` | `to` | `amount` | Sufficient | +| `Transfer` (ERC20 inherited) | `from`, `to` | `value` | Sufficient — standard | +| `Approval` (ERC20 inherited) | `owner`, `spender` | `value` | Sufficient — standard | + +No gaps identified. + +--- + +## 5. Cross-contract issue: SlasherContractAdded / SlasherContractRemoved + +Both `DinCoordinator` and `DinValidatorStake` emit events with **identical signatures**: + +``` +event SlasherContractAdded(address indexed slasher) +event SlasherContractRemoved(address indexed slasher) +``` + +This is intentional at the Solidity level — `DinCoordinator.addSlasherContract` +forwards the call to `DinValidatorStake`, so both contracts emit the same event for +the same action. + +**Indexer implication:** If both events are processed without tracking the source +contract address, a single slasher add/remove operation creates two identical entity +records and slasher counts become inflated. + +**Resolution (schema-level, no contract change needed):** The subgraph data source +for each contract processes its own event; the `SlasherRegistration` entity carries a +`sourceContract` field (set to the emitting contract's address in the mapping handler). +`DinCoordinator`-sourced and `DinValidatorStake`-sourced events are therefore kept +as distinct records. The canonical slasher set is derived only from +`DinValidatorStake` events, since that contract owns the actual `slasherContracts` +mapping. `DinCoordinator` events are recorded for audit trail completeness only. + +--- + +## 6. Proposed event additions + +The following two additions were confirmed by Umer (2026-07-09, issue #24). All +other gaps noted in this document are either handled at the schema level (§5) or +deferred to future work. + +### 6.1 `DINModelRegistry.ModelRegistrationRequested` + +**Current:** +```solidity +event ModelRegistrationRequested( + uint256 indexed requestId, + address indexed requester +); +``` + +**Proposed:** +```solidity +event ModelRegistrationRequested( + uint256 indexed requestId, + address indexed requester, + bool isOpenSource, + uint256 fee +); +``` + +**Why:** Without `isOpenSource`, the indexer cannot distinguish open-source from +proprietary pending requests without a storage call. Without `fee`, the indexer +cannot surface fee amounts for pending-request dashboards. Both fields are already +in storage (`modelRequests[requestId].isOpenSource` / `.feePaid`) — emitting them +costs one additional log word each. + +### 6.2 `DINModelRegistry.ManifestUpdateRequested` + +**Current:** +```solidity +event ManifestUpdateRequested( + uint256 indexed requestId, + uint256 indexed modelId +); +``` + +**Proposed:** +```solidity +event ManifestUpdateRequested( + uint256 indexed requestId, + uint256 indexed modelId, + address indexed requester +); +``` + +**Why:** Without `requester`, the indexer cannot attribute a pending manifest update +to its submitter. The requester address is already available as `msg.sender` at emit +time — adding it costs nothing beyond the extra log topic slot. + +### 6.3 `DinCoordinator.ETHTreasuryWithdrawn` (new event) + +**Proposed addition to `withdraw()`:** +```solidity +event ETHTreasuryWithdrawn(address indexed to, uint256 amount); +``` + +**Why:** The coordinator's `withdraw()` function transfers the full ETH balance to +the owner with no trace. For treasury governance and audit purposes, every outflow +should be observable on-chain. This addition is independent of the §6.1/6.2 changes +and can be batched into the same PR. + +--- + +## 7. Daemon event schema (P4-7.1 feed) + +The following platform-contract events are the minimum the `dind` daemon (issue #21) +needs to subscribe to for reactive job scheduling. Coverage gaps are noted. + +| Event | Contract | dind use | Gap | +|---|---|---|---| +| `ModelRegistrationRequested` | DINModelRegistry | Notify DAO admin of new pending request | After §6.1 addition: adequate | +| `ModelApproved` | DINModelRegistry | Trigger model-owner onboarding flow | Adequate | +| `ModelRejected` | DINModelRegistry | Notify model owner | Adequate | +| `ManifestUpdateRequested` | DINModelRegistry | Notify DAO admin | After §6.2 addition: adequate | +| `ManifestUpdated` | DINModelRegistry | Trigger manifest refresh in local cache | Adequate | +| `ValidatorStaked` | DinValidatorStake | Update local validator-eligibility cache | Adequate | +| `ValidatorSlashed` | DinValidatorStake | Log slash for local validator operator | Adequate | +| `ValidatorBlacklisted` | DinValidatorStake | Halt local validator process immediately | Adequate | +| `DinPerEthUpdated` | DinCoordinator | Update local exchange-rate cache | Adequate — old rate derivable from prior snapshot | +| `DAOAdminUpdated` | DINModelRegistry | Alert operator of governance-key rotation | Adequate | + +**Events not yet needed by dind v1** (task-contract GI events): not in scope here. +These belong in the P4-IDX3 handoff notes once platform-contract indexing is stable. diff --git a/subgraph/docs/example-queries.md b/subgraph/docs/example-queries.md new file mode 100644 index 0000000..891a2c5 --- /dev/null +++ b/subgraph/docs/example-queries.md @@ -0,0 +1,265 @@ +# DIN Protocol subgraph — example queries + +All queries run against the local GraphQL playground at +`http://localhost:8000/subgraphs/name/din-protocol/graphql` once the stack +from `subgraph/docker-compose.yml` is up and the subgraph is deployed. + +--- + +## 1. Validator registry + +### All active validators with stake summary + +```graphql +{ + validators( + where: { status: "Active" } + orderBy: activeStake + orderDirection: desc + first: 50 + ) { + id + activeStake + totalStaked + totalSlashed + status + } +} +``` + +### Validators currently in the exit queue (unbonding) + +```graphql +{ + validators(where: { status: "Exiting" }) { + id + activeStake + pendingWithdrawal + withdrawAvailableAt + } +} +``` + +### All blacklisted validators + +```graphql +{ + validators(where: { blacklisted: true }) { + id + totalSlashed + status + } +} +``` + +--- + +## 2. Model registry + +### All pending model registration requests + +```graphql +{ + modelRegistrationRequests( + where: { processed: false } + orderBy: submittedAtTimestamp + orderDirection: asc + first: 100 + ) { + requestId + requester + isOpenSource + feePaid + submittedAtTimestamp + } +} +``` + +### All approved models (open-source only) + +```graphql +{ + models( + where: { isOpenSource: true, disabled: false } + orderBy: createdAtTimestamp + orderDirection: desc + first: 50 + ) { + modelId + owner + manifestCID + taskCoordinator + createdAtTimestamp + } +} +``` + +### Pending manifest update requests for a specific model owner + +```graphql +{ + manifestUpdateRequests( + where: { + processed: false + requester: "0xYOUR_ADDRESS_HERE" + } + orderBy: submittedAtTimestamp + orderDirection: asc + ) { + requestId + model { modelId } + feePaid + newManifestCID + submittedAtTimestamp + } +} +``` + +### Full request lifecycle for model 0 + +```graphql +{ + model(id: "0") { + modelId + owner + isOpenSource + disabled + registrationRequest { + requestId + feePaid + submittedAtTimestamp + processedAtTimestamp + } + manifestHistory(orderBy: submittedAtTimestamp, orderDirection: asc) { + requestId + approved + submittedAtTimestamp + processedAtTimestamp + } + } +} +``` + +--- + +## 3. Slash and reward history + +### Full slash history — most recent first + +```graphql +{ + slashEvents( + orderBy: blockTimestamp + orderDirection: desc + first: 50 + ) { + validator { id } + amount + reason + slasherContract + blockTimestamp + transactionHash + } +} +``` + +### Slash history for a specific validator + +```graphql +{ + validator(id: "0xVALIDATOR_ADDRESS_HERE") { + id + totalSlashed + slashEvents(orderBy: blockTimestamp, orderDirection: desc) { + amount + reason + slasherContract + blockTimestamp + } + } +} +``` + +### All slashes issued by a specific task contract + +```graphql +{ + slashEvents( + where: { slasherContract: "0xTASK_COORDINATOR_ADDRESS_HERE" } + orderBy: blockTimestamp + orderDirection: desc + ) { + validator { id } + amount + reason + blockTimestamp + } +} +``` + +### DIN mint history (ETH deposits) — all time + +```graphql +{ + dINMintEvents( + orderBy: blockTimestamp + orderDirection: desc + first: 100 + ) { + user + ethAmount + dinAmount + blockTimestamp + } +} +``` + +### Exchange rate timeline + +```graphql +{ + exchangeRateSnapshots( + orderBy: blockTimestamp + orderDirection: asc + ) { + newRate + blockTimestamp + transactionHash + } +} +``` + +--- + +## 4. Authorized slashers + +### Currently active slasher contracts (canonical set from DinValidatorStake) + +```graphql +{ + slasherRegistrations( + where: { + active: true + sourceContract: "0xDINVALIDATORSTAKE_ADDRESS_HERE" + } + ) { + slasherAddress + addedAtBlock + addedAtTimestamp + } +} +``` + +### Full slasher audit trail (both DinCoordinator and DinValidatorStake events) + +```graphql +{ + slasherRegistrations(orderBy: addedAtTimestamp, orderDirection: asc) { + slasherAddress + sourceContract + active + addedAtTimestamp + removedAtTimestamp + } +} +``` diff --git a/subgraph/docs/handoff.md b/subgraph/docs/handoff.md new file mode 100644 index 0000000..b5fba81 --- /dev/null +++ b/subgraph/docs/handoff.md @@ -0,0 +1,96 @@ +# DIN Indexer — GraphQL integration handoff + +## What was done + +The `dindao registry list-pending-requests` command (`dincli/cli/dindao.py`) previously +enumerated pending model and manifest requests by calling `totalModelRequests()` / +`totalManifestRequests()` on-chain and iterating through every index with an individual +RPC call per entry. Those two `for idx in range(...)` loops have been replaced with +GraphQL queries to the locally-running subgraph. + +### Integration point + +| File | Location | Change | +|------|----------|--------| +| `dincli/cli/dindao.py` | top of file | Added `_SUBGRAPH_URL` constant and `_query_subgraph()` helper | +| `dincli/cli/dindao.py` | `list_pending_requests` — model block | GraphQL-first, RPC fallback | +| `dincli/cli/dindao.py` | `list_pending_requests` — manifest block | GraphQL-first, RPC fallback | + +### Queries issued + +**Model registration requests** +```graphql +{ + modelRegistrationRequests(where: { processed: false }, first: 1000) { + requestId + requester + isOpenSource + feePaid + } +} +``` + +**Manifest update requests** +```graphql +{ + manifestUpdateRequests(where: { processed: false }, first: 1000) { + requestId + model { modelId } + requester + feePaid + } +} +``` + +### Fallback behaviour + +`_query_subgraph()` returns `None` on any of: + +- `requests.exceptions.ConnectionError` / `Timeout` (graph-node not running) +- HTTP 4xx / 5xx response +- GraphQL `errors` field present in the response body + +When `None` is returned, both blocks fall through to the original RPC enumeration loop, +so the command remains fully functional in environments where the subgraph is not deployed. + +### Configuration + +| Env var | Default | Purpose | +|---------|---------|---------| +| `DIN_SUBGRAPH_URL` | `http://localhost:8000/subgraphs/name/din-protocol/graphql` | Override the subgraph endpoint (CI, staging, custom port) | + +--- + +## Follow-up candidates + +The same RPC-enumeration pattern appears in at least two other places. These are +candidates for a P5 ticket once the subgraph schema is extended to cover task-level events: + +| File | Approx. line | Description | +|------|-------------|-------------| +| `dincli/cli/modelownerd/lms.py` | ~62 | Iterates `totalLocalModelSubmissions()` to list LMS entries for the current GI | +| `dincli/cli/modelownerd/aggregation.py` | ~76, 95, 136 | Iterates T1/T2 batch entries to build aggregation payloads | + +These loops touch `DINTaskCoordinator` state which is task-level (not covered by the +current platform-contract subgraph). They should be migrated once dynamic data sources +for task contracts are added in a future subgraph version. + +--- + +## Test coverage + +`tests/test_list_pending_requests.py` adds six unit tests (no live node required): + +| Test | What it verifies | +|------|-----------------| +| `test_graphql_model_path` | Two model requests returned from subgraph appear in output | +| `test_graphql_model_path_empty` | Empty subgraph response shows the "no pending" message | +| `test_graphql_manifest_path` | Manifest request with nested `model.modelId` renders correctly | +| `test_rpc_fallback_on_connection_error` | `ConnectionError` triggers RPC loop | +| `test_rpc_fallback_on_graphql_error_body` | `errors` in response body triggers RPC loop | +| `test_rpc_fallback_on_http_error` | HTTP 503 triggers RPC loop | + +The existing integration test `test_din_rep_lists_pending_requests` in +`tests/dincli/test_03_registration.py` continues to exercise the command end-to-end +against a live Hardhat node (graph-node is not running in that environment, so the RPC +fallback path is exercised there automatically). diff --git a/subgraph/package.json b/subgraph/package.json new file mode 100644 index 0000000..dc98799 --- /dev/null +++ b/subgraph/package.json @@ -0,0 +1,16 @@ +{ + "name": "din-protocol-subgraph", + "version": "0.1.0", + "description": "The Graph subgraph for DIN Protocol platform contracts", + "scripts": { + "codegen": "graph codegen", + "build": "graph build", + "create:local": "graph create --node http://localhost:8020/ din-protocol", + "deploy:local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 din-protocol", + "remove:local": "graph remove --node http://localhost:8020/ din-protocol" + }, + "dependencies": { + "@graphprotocol/graph-cli": "^0.71.0", + "@graphprotocol/graph-ts": "^0.35.1" + } +} diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql new file mode 100644 index 0000000..aeadc11 --- /dev/null +++ b/subgraph/schema.graphql @@ -0,0 +1,750 @@ +# DIN Protocol — The Graph subgraph schema +# +# Covers the four platform contracts: +# DINModelRegistry, DinValidatorStake, DinCoordinator, DinToken +# +# Task-level contracts (DINTaskCoordinator, DINTaskAuditor) are out of scope +# for this subgraph — deferred to P5+ dynamic data sources. +# +# Chain: chainId 1337 (local anvil / hardhat). +# Event field notes: two event additions are pending a reviewed PR +# (see subgraph/docs/event-coverage-audit.md §6). Fields marked +# [PENDING EVENT ADDITION] are populated once those events land; until then +# the mapping handler will set them from a storage call on approval. + +# ───────────────────────────────────────────────────────────────────────────── +# Model Registry — registration requests +# ───────────────────────────────────────────────────────────────────────────── + +"""A request submitted by a model owner for DAO review and approval.""" +type ModelRegistrationRequest @entity { + "requestId as decimal string" + id: ID! + + requestId: BigInt! + requester: Bytes! + + "Populated from event once §6.1 addition lands; derived from storage call until then" + isOpenSource: Boolean! + + "Fee paid at submission time in wei; populated once §6.1 addition lands" + feePaid: BigInt! + + manifestCID: Bytes! + taskCoordinator: Bytes! + taskAuditor: Bytes! + + processed: Boolean! + approved: Boolean + + "Set when the request is approved" + model: Model + + submittedAtBlock: BigInt! + submittedAtTimestamp: BigInt! + processedAtBlock: BigInt + processedAtTimestamp: BigInt +} + +# ───────────────────────────────────────────────────────────────────────────── +# Model Registry — approved models +# ───────────────────────────────────────────────────────────────────────────── + +"""An approved federated-learning model registered on the DIN protocol.""" +type Model @entity { + "modelId as decimal string" + id: ID! + + modelId: BigInt! + owner: Bytes! + isOpenSource: Boolean! + manifestCID: Bytes! + taskCoordinator: Bytes! + taskAuditor: Bytes! + + disabled: Boolean! + + "The registration request that created this model" + registrationRequest: ModelRegistrationRequest! + + manifestHistory: [ManifestUpdateRequest!]! @derivedFrom(field: "model") + disableEvents: [ModelKillSwitchEvent!]! @derivedFrom(field: "model") + globalIterations: [GlobalIteration!]! @derivedFrom(field: "model") + + createdAtBlock: BigInt! + createdAtTimestamp: BigInt! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Model Registry — manifest update requests +# ───────────────────────────────────────────────────────────────────────────── + +"""A manifest update request submitted by the model owner for DAO review.""" +type ManifestUpdateRequest @entity { + "requestId as decimal string" + id: ID! + + requestId: BigInt! + model: Model! + + "Populated from event once §6.2 addition lands; derivable from model.owner until then" + requester: Bytes! + + feePaid: BigInt! + newManifestCID: Bytes! + + processed: Boolean! + approved: Boolean + + submittedAtBlock: BigInt! + submittedAtTimestamp: BigInt! + processedAtBlock: BigInt + processedAtTimestamp: BigInt +} + +# ───────────────────────────────────────────────────────────────────────────── +# Model Registry — kill-switch history +# ───────────────────────────────────────────────────────────────────────────── + +"""Records each ModelDisabled / ModelEnabled transition.""" +type ModelKillSwitchEvent @entity { + "txHash-logIndex" + id: ID! + + model: Model! + disabled: Boolean! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Model Registry — fee governance +# ───────────────────────────────────────────────────────────────────────────── + +""" +A snapshot of protocol fees at the time of a fee-change event. +One snapshot per fee-change transaction; latest snapshot is the current fee schedule. +""" +type FeeSnapshot @entity { + "txHash-logIndex" + id: ID! + + openSourceFee: BigInt! + proprietaryFee: BigInt! + openSourceUpdateFee: BigInt! + proprietaryUpdateFee: BigInt! + + "atomic = fired by setFees(); granular = fired by an individual setter" + updateKind: String! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""An ETH fee withdrawal from DINModelRegistry.""" +type RegistryFeeWithdrawal @entity { + "txHash-logIndex" + id: ID! + + to: Bytes! + amount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Model Registry — governance key rotation +# ───────────────────────────────────────────────────────────────────────────── + +"""Records every DAOAdmin ownership transfer.""" +type DAOAdminTransfer @entity { + "txHash-logIndex" + id: ID! + + oldAdmin: Bytes! + newAdmin: Bytes! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Validator stake — per-validator aggregate +# ───────────────────────────────────────────────────────────────────────────── + +""" +Aggregate state for a single validator address, updated on every relevant event. +Derived status (Active / Exiting / Jailed / Blacklisted / None) is computed +from the event stream — no dedicated ValidatorStatusChanged event exists yet. +""" +type Validator @entity { + "validator address as hex string" + id: ID! + + address: Bytes! + + activeStake: BigInt! + pendingWithdrawal: BigInt! + withdrawAvailableAt: BigInt + + "Active | Exiting | Jailed | Blacklisted | None" + status: String! + + totalStaked: BigInt! + totalSlashed: BigInt! + totalWithdrawn: BigInt! + + blacklisted: Boolean! + + stakeEvents: [StakeEvent!]! @derivedFrom(field: "validator") + slashEvents: [SlashEvent!]! @derivedFrom(field: "validator") + unstakeRequests: [UnstakeRequest!]! @derivedFrom(field: "validator") + withdrawalClaims: [WithdrawalClaim!]! @derivedFrom(field: "validator") +} + +# ───────────────────────────────────────────────────────────────────────────── +# Validator stake — individual events +# ───────────────────────────────────────────────────────────────────────────── + +"""A DIN stake deposit by a validator.""" +type StakeEvent @entity { + "txHash-logIndex" + id: ID! + + validator: Validator! + amount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""A slash applied to a validator's stake.""" +type SlashEvent @entity { + "txHash-logIndex" + id: ID! + + validator: Validator! + amount: BigInt! + reason: Bytes! + + "Address of the slasher contract (DINTaskCoordinator or DINTaskAuditor)" + slasherContract: Bytes! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""An unstake (unbonding) request submitted by a validator.""" +type UnstakeRequest @entity { + "txHash-logIndex" + id: ID! + + validator: Validator! + amount: BigInt! + withdrawAvailableAt: BigInt! + + claimed: Boolean! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""A completed withdrawal after the unbonding period.""" +type WithdrawalClaim @entity { + "txHash-logIndex" + id: ID! + + validator: Validator! + amount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Validator stake — slasher registry +# ───────────────────────────────────────────────────────────────────────────── + +""" +A slasher contract authorized to call DinValidatorStake.slash(). +sourceContract distinguishes DinCoordinator-originated events from +DinValidatorStake-originated events — both emit identical SlasherContractAdded/ +Removed signatures (see event-coverage-audit.md §5). +The canonical active-slasher set is derived from DinValidatorStake events only. +""" +type SlasherRegistration @entity { + "slasherAddress-sourceContract as hex pair" + id: ID! + + slasherAddress: Bytes! + + "DinCoordinator or DinValidatorStake — the contract that emitted the event" + sourceContract: Bytes! + + active: Boolean! + + addedAtBlock: BigInt! + addedAtTimestamp: BigInt! + removedAtBlock: BigInt + removedAtTimestamp: BigInt +} + +# ───────────────────────────────────────────────────────────────────────────── +# DinCoordinator — ETH deposits and treasury +# ───────────────────────────────────────────────────────────────────────────── + +"""An ETH deposit that minted DIN tokens for a user.""" +type DINMintEvent @entity { + "txHash-logIndex" + id: ID! + + user: Bytes! + ethAmount: BigInt! + dinAmount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +""" +An ETH treasury withdrawal from DinCoordinator. +Requires the ETHTreasuryWithdrawn event addition (audit §6.3). +Until that event lands this entity will not be populated. +""" +type TreasuryWithdrawal @entity { + "txHash-logIndex" + id: ID! + + to: Bytes! + amount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""A change to the DIN-per-ETH exchange rate.""" +type ExchangeRateSnapshot @entity { + "txHash-logIndex" + id: ID! + + newRate: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# DinToken — mint and transfer history +# ───────────────────────────────────────────────────────────────────────────── + +"""A DIN token mint (always from DinCoordinator via depositAndMint).""" +type TokenMintEvent @entity { + "txHash-logIndex" + id: ID! + + to: Bytes! + amount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +""" +A DIN ERC20 transfer. Indexed from the standard Transfer event. +Includes zero-address mint/burn transfers for completeness. +""" +type TokenTransferEvent @entity { + "txHash-logIndex" + id: ID! + + from: Bytes! + to: Bytes! + amount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ============================================================================= +# Task-level contracts — dynamic data sources +# +# DINTaskCoordinator and DINTaskAuditor are deployed once per model (not once +# globally). The subgraph tracks them via data source templates instantiated +# when DINModelRegistry emits ModelApproved. +# +# Event additions required before several entities below are fully populated +# are marked [PENDING — see task-contract-event-audit.md §4]. +# ============================================================================= + +# ───────────────────────────────────────────────────────────────────────────── +# Global Iteration lifecycle +# ───────────────────────────────────────────────────────────────────────────── + +""" +A single Global Iteration (GI) for one federated-learning model. +Central entity linking registrations, batches, submissions, and scoring. +""" +type GlobalIteration @entity { + "taskCoordinatorAddress-gi (hex-decimal)" + id: ID! + + gi: BigInt! + taskCoordinator: Bytes! + + "Back-reference to the platform-level Model entity" + model: Model! + + "Current GIstates enum value as uint8. + Populated once GIStateChanged event lands [PENDING — audit §4.1]. + Starts at 5 (GIstarted) — the first state reachable after model registration." + currentState: Int! + + aggregatorRegistrations: [AggregatorRegistration!]! @derivedFrom(field: "globalIteration") + auditorRegistrations: [AuditorRegistration!]! @derivedFrom(field: "globalIteration") + tier1Batches: [Tier1Batch!]! @derivedFrom(field: "globalIteration") + tier2Batches: [Tier2Batch!]! @derivedFrom(field: "globalIteration") + auditBatches: [AuditBatch!]! @derivedFrom(field: "globalIteration") + localModelSubmissions: [LocalModelSubmission!]! @derivedFrom(field: "globalIteration") + aggregatorSlashEvents: [AggregatorSlashEvent!]! @derivedFrom(field: "globalIteration") + auditorSlashEvents: [AuditorSlashEvent!]! @derivedFrom(field: "globalIteration") + stateTransitions: [GIStateTransition!]! @derivedFrom(field: "globalIteration") + + "T2 winning CID — the new global model. Set once T2Finalized event lands [PENDING — audit §4.4]." + globalModelCID: Bytes + + "True once endGI() has been called and GIStateChanged(GIended) fires." + ended: Boolean! + + startedAtBlock: BigInt! + startedAtTimestamp: BigInt! + endedAtBlock: BigInt + endedAtTimestamp: BigInt +} + +# ───────────────────────────────────────────────────────────────────────────── +# Validator registrations per GI +# ───────────────────────────────────────────────────────────────────────────── + +"""An aggregator that registered for a specific Global Iteration.""" +type AggregatorRegistration @entity { + "taskCoordinatorAddress-gi-validatorAddress" + id: ID! + + globalIteration: GlobalIteration! + validator: Bytes! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""An auditor that registered for a specific Global Iteration.""" +type AuditorRegistration @entity { + "taskAuditorAddress-gi-auditorAddress" + id: ID! + + globalIteration: GlobalIteration! + auditor: Bytes! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Aggregation batches (T1 and T2) +# ───────────────────────────────────────────────────────────────────────────── + +""" +A Tier-1 aggregation batch. Created by autoCreateTier1AndTier2(). +Multiple T1 batches exist per GI; each covers a sub-set of approved models. +""" +type Tier1Batch @entity { + "taskCoordinatorAddress-gi-batchId" + id: ID! + + globalIteration: GlobalIteration! + batchId: BigInt! + + t1Submissions: [T1AggregationSubmission!]! @derivedFrom(field: "tier1Batch") + + "True once finalizeT1Aggregation() selects the majority CID." + finalized: Boolean! + + "Majority-agreed aggregation CID. Set once T1BatchFinalized event lands [PENDING — audit §4.4]." + winningCID: Bytes + + createdAtBlock: BigInt! + createdAtTimestamp: BigInt! + finalizedAtBlock: BigInt + finalizedAtTimestamp: BigInt +} + +""" +The single Tier-2 aggregation batch per GI. Created alongside T1 batches. +Its winningCID is the new global model for the iteration. +""" +type Tier2Batch @entity { + "taskCoordinatorAddress-gi (batchId is always 0)" + id: ID! + + globalIteration: GlobalIteration! + + t2Submissions: [T2AggregationSubmission!]! @derivedFrom(field: "tier2Batch") + + "True once finalizeT2Aggregation() selects the majority CID." + finalized: Boolean! + + "Majority-agreed CID — the new global model for this GI. Set once T2Finalized event lands [PENDING — audit §4.4]." + globalModelCID: Bytes + + createdAtBlock: BigInt! + createdAtTimestamp: BigInt! + finalizedAtBlock: BigInt + finalizedAtTimestamp: BigInt +} + +# ───────────────────────────────────────────────────────────────────────────── +# Audit batches +# ───────────────────────────────────────────────────────────────────────────── + +""" +An audit batch created by DINTaskAuditor.createAuditorsBatches(). +Groups a subset of auditors with a subset of local model submissions. +""" +type AuditBatch @entity { + "taskAuditorAddress-gi-batchId" + id: ID! + + globalIteration: GlobalIteration! + batchId: BigInt! + + scoreSubmissions: [AuditScoreSubmission!]! @derivedFrom(field: "auditBatch") + eligibilityVotes: [EligibilityVote!]! @derivedFrom(field: "auditBatch") + eligibilityResults: [EligibilityResult!]! @derivedFrom(field: "auditBatch") + auditorSlashEvents: [AuditorSlashEvent!]! @derivedFrom(field: "auditBatch") + + createdAtBlock: BigInt! + createdAtTimestamp: BigInt! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Local model submissions +# ───────────────────────────────────────────────────────────────────────────── + +""" +A local model submitted by a client during the LMS phase. +Requires the LocalModelSubmitted event addition [PENDING — audit §4.2]. +Until that event lands this entity will not be populated. +""" +type LocalModelSubmission @entity { + "taskAuditorAddress-gi-modelIndex" + id: ID! + + globalIteration: GlobalIteration! + modelIndex: BigInt! + client: Bytes! + modelCID: Bytes! + + "Set once EligibilityFinalized fires for this modelIndex." + eligible: Boolean + "Set once finalizeEvaluation() is called (derived from EligibilityFinalized + AuditScoreSubmitted)." + finalAvgScore: BigInt + "True if eligible and finalAvgScore >= passScore." + approved: Boolean + + scoreSubmissions: [AuditScoreSubmission!]! @derivedFrom(field: "localModelSubmission") + eligibilityVotes: [EligibilityVote!]! @derivedFrom(field: "localModelSubmission") + eligibilityResult: EligibilityResult @derivedFrom(field: "localModelSubmission") + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Aggregation submissions (T1 and T2) +# ───────────────────────────────────────────────────────────────────────────── + +""" +A T1 aggregation CID submitted by an aggregator for a specific batch. +Requires the T1AggregationSubmitted event addition [PENDING — audit §4.3]. +""" +type T1AggregationSubmission @entity { + "taskCoordinatorAddress-gi-batchId-aggregatorAddress" + id: ID! + + tier1Batch: Tier1Batch! + aggregator: Bytes! + cid: Bytes! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +""" +A T2 aggregation CID submitted by an aggregator. +batchId is always 0. Requires the T2AggregationSubmitted event addition [PENDING — audit §4.3]. +""" +type T2AggregationSubmission @entity { + "taskCoordinatorAddress-gi-aggregatorAddress" + id: ID! + + tier2Batch: Tier2Batch! + aggregator: Bytes! + cid: Bytes! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Audit scoring and eligibility +# ───────────────────────────────────────────────────────────────────────────── + +"""An audit score submitted by a single auditor for one local model.""" +type AuditScoreSubmission @entity { + "taskAuditorAddress-gi-batchId-auditorAddress-modelIndex" + id: ID! + + auditBatch: AuditBatch! + localModelSubmission: LocalModelSubmission! + auditor: Bytes! + score: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""An eligibility vote cast by a single auditor for one local model.""" +type EligibilityVote @entity { + "taskAuditorAddress-gi-batchId-modelIndex-auditorAddress" + id: ID! + + auditBatch: AuditBatch! + localModelSubmission: LocalModelSubmission! + auditor: Bytes! + vote: Boolean! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +""" +The finalized eligibility result for a local model once quorum is reached. +One record per modelIndex per GI. +""" +type EligibilityResult @entity { + "taskAuditorAddress-gi-batchId-modelIndex" + id: ID! + + auditBatch: AuditBatch! + localModelSubmission: LocalModelSubmission! + eligible: Boolean! + totalVotes: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Slash events (task-level) +# ───────────────────────────────────────────────────────────────────────────── + +"""A slash applied to an aggregator by DINTaskCoordinator.slashAggregators().""" +type AggregatorSlashEvent @entity { + "txHash-logIndex" + id: ID! + + globalIteration: GlobalIteration! + batchId: BigInt! + aggregator: Bytes! + reason: Bytes! + requestedAmount: BigInt! + actualAmount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +"""A slash applied to an auditor by DINTaskAuditor.slashAuditors().""" +type AuditorSlashEvent @entity { + "txHash-logIndex" + id: ID! + + globalIteration: GlobalIteration! + auditBatch: AuditBatch! + auditor: Bytes! + reason: Bytes! + requestedAmount: BigInt! + actualAmount: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# Pass score governance +# ───────────────────────────────────────────────────────────────────────────── + +"""A change to the auditor pass score on DINTaskAuditor.""" +type PassScoreUpdate @entity { + "txHash-logIndex" + id: ID! + + "The task auditor contract that emitted the event" + taskAuditor: Bytes! + oldScore: BigInt! + newScore: BigInt! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +# ───────────────────────────────────────────────────────────────────────────── +# GI state transition history +# ───────────────────────────────────────────────────────────────────────────── + +""" +A single GI state machine transition recorded from GIStateChanged. +Requires the GIStateChanged event addition [PENDING — audit §4.1]. +One record per state write — the full lifecycle of a GI is reconstructable +from the ordered sequence of GIStateTransition records for a GlobalIteration. +newState is the uint8 value of the GIstates enum in DINShared.sol. +""" +type GIStateTransition @entity { + "taskCoordinatorAddress-gi-newState" + id: ID! + + globalIteration: GlobalIteration! + + "uint8 value of the GIstates enum (0 = AwaitingDINTaskAuditorToBeSet … 22 = GIended)" + newState: Int! + + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} diff --git a/subgraph/src/coordinator.ts b/subgraph/src/coordinator.ts new file mode 100644 index 0000000..4a09678 --- /dev/null +++ b/subgraph/src/coordinator.ts @@ -0,0 +1,108 @@ +import { + EthDepositAndDINminted, + DinPerEthUpdated, + ValidatorStakeContractUpdated, + SlasherContractAdded, + SlasherContractRemoved, +} from "../generated/DinCoordinator/DinCoordinator" +import { + DINMintEvent, + ExchangeRateSnapshot, + SlasherRegistration, +} from "../generated/schema" +import { eventId } from "./utils" + +// ─── ETH deposits ───────────────────────────────────────────────────────────── + +export function handleEthDepositAndDINminted( + event: EthDepositAndDINminted +): void { + let e = new DINMintEvent(eventId(event.transaction.hash, event.logIndex)) + e.user = event.params.user + e.ethAmount = event.params.ethAmount + e.dinAmount = event.params.mintAmount + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} + +// ─── Exchange rate ──────────────────────────────────────────────────────────── + +export function handleDinPerEthUpdated(event: DinPerEthUpdated): void { + let s = new ExchangeRateSnapshot( + eventId(event.transaction.hash, event.logIndex) + ) + s.newRate = event.params.newRate + s.blockNumber = event.block.number + s.blockTimestamp = event.block.timestamp + s.transactionHash = event.transaction.hash + s.save() +} + +// ─── Validator stake contract pointer ──────────────────────────────────────── +// No schema entity for this — it is a one-time wiring step in the deployment +// sequence. Log at debug level so it is visible in graph-node output. + +export function handleValidatorStakeContractUpdated( + event: ValidatorStakeContractUpdated +): void { + // Intentionally a no-op at the entity level. + // The ValidatorStakeContractUpdated event records the address of the stake + // contract wired into DinCoordinator. Since this is a one-time deployment + // step (or extremely rare governance action), storing it in a dedicated + // entity adds noise without benefit. The graph-node debug log captures it. +} + +// ─── Slasher audit trail ────────────────────────────────────────────────────── +// +// DinCoordinator forwards addSlasherContract/removeSlasherContract to +// DinValidatorStake, causing both contracts to emit the same event. These +// handlers record the coordinator-sourced event as an audit-trail entry only. +// The canonical active-slasher set comes from staking.ts handlers. + +export function handleCoordinatorSlasherAdded( + event: SlasherContractAdded +): void { + let id = event.params.slasher.toHexString() + "-" + event.address.toHexString() + let s = new SlasherRegistration(id) + s.slasherAddress = event.params.slasher + s.sourceContract = event.address + s.active = true + s.addedAtBlock = event.block.number + s.addedAtTimestamp = event.block.timestamp + s.removedAtBlock = null + s.removedAtTimestamp = null + s.save() +} + +export function handleCoordinatorSlasherRemoved( + event: SlasherContractRemoved +): void { + let id = event.params.slasher.toHexString() + "-" + event.address.toHexString() + let s = SlasherRegistration.load(id) + if (s == null) return + s.active = false + s.removedAtBlock = event.block.number + s.removedAtTimestamp = event.block.timestamp + s.save() +} + +// ─── Treasury withdrawal ────────────────────────────────────────────────────── +// ETHTreasuryWithdrawn is not yet in the ABI (audit §6.3). The handler is +// kept here as a stub; uncomment and add the import once the event lands. + +// import { ETHTreasuryWithdrawn } from "../generated/DinCoordinator/DinCoordinator" +// import { TreasuryWithdrawal } from "../generated/schema" + +// export function handleETHTreasuryWithdrawn( +// event: ETHTreasuryWithdrawn +// ): void { +// let w = new TreasuryWithdrawal(eventId(event.transaction.hash, event.logIndex)) +// w.to = event.params.to +// w.amount = event.params.amount +// w.blockNumber = event.block.number +// w.blockTimestamp = event.block.timestamp +// w.transactionHash = event.transaction.hash +// w.save() +// } diff --git a/subgraph/src/registry.ts b/subgraph/src/registry.ts new file mode 100644 index 0000000..5629081 --- /dev/null +++ b/subgraph/src/registry.ts @@ -0,0 +1,306 @@ +import { BigInt, DataSourceContext, Address } from "@graphprotocol/graph-ts" +import { + ModelRegistrationRequested, + ModelApproved, + ModelRejected, + ManifestUpdateRequested, + ManifestUpdated, + ManifestUpdateRejected, + ModelDisabled, + ModelEnabled, + OpenSourceFeeUpdated, + ProprietaryFeeUpdated, + OpenSourceUpdateFeeUpdated, + ProprietaryUpdateFeeUpdated, + FeesUpdated, + FeesWithdrawn, + DAOAdminUpdated, + DINModelRegistry, +} from "../generated/DINModelRegistry/DINModelRegistry" +import { + ModelRegistrationRequest, + Model, + ManifestUpdateRequest, + ModelKillSwitchEvent, + FeeSnapshot, + RegistryFeeWithdrawal, + DAOAdminTransfer, +} from "../generated/schema" +import { eventId } from "./utils" +import { + DINTaskCoordinator as DINTaskCoordinatorTemplate, + DINTaskAuditor as DINTaskAuditorTemplate, +} from "../generated/templates" + +// ─── Registration request lifecycle ────────────────────────────────────────── + +export function handleModelRegistrationRequested( + event: ModelRegistrationRequested +): void { + let id = event.params.requestId.toString() + let req = new ModelRegistrationRequest(id) + + req.requestId = event.params.requestId + req.requester = event.params.requester + + // isOpenSource and fee are not in the event until audit §6.1 addition lands. + // Bridge: read from contract storage at this block. + let registry = DINModelRegistry.bind(event.address) + let stored = registry.modelRequests(event.params.requestId) + req.isOpenSource = stored.getIsOpenSource() + req.feePaid = stored.getFeePaid() + req.manifestCID = stored.getManifestCID() + req.taskCoordinator = stored.getTaskCoordinator() + req.taskAuditor = stored.getTaskAuditor() + + req.processed = false + req.approved = null + req.model = null + + req.submittedAtBlock = event.block.number + req.submittedAtTimestamp = event.block.timestamp + req.processedAtBlock = null + req.processedAtTimestamp = null + + req.save() +} + +export function handleModelApproved(event: ModelApproved): void { + let reqId = event.params.requestId.toString() + let req = ModelRegistrationRequest.load(reqId) + if (req == null) return + + req.processed = true + req.approved = true + req.processedAtBlock = event.block.number + req.processedAtTimestamp = event.block.timestamp + + let modelId = event.params.modelId.toString() + req.model = modelId + req.save() + + // Build the approved Model entity from storage — all fields are available + // via getModel() at this block without a separate event. + let registry = DINModelRegistry.bind(event.address) + let stored = registry.getModel(event.params.modelId) + + let model = new Model(modelId) + model.modelId = event.params.modelId + model.owner = stored.getOwner() + model.isOpenSource = stored.getIsOpenSource() + model.manifestCID = stored.getManifestCID() + model.taskCoordinator = stored.getTaskCoordinator() + model.taskAuditor = stored.getTaskAuditor() + model.disabled = false + model.registrationRequest = reqId + model.createdAtBlock = event.block.number + model.createdAtTimestamp = event.block.timestamp + model.save() + + // Spin up dynamic data source instances for this model's task contracts. + // Both templates receive the same context so handlers on either side can + // resolve the modelId and the paired contract address. + let ctx = new DataSourceContext() + ctx.setBigInt("modelId", event.params.modelId) + ctx.setBytes("taskCoordinatorAddress", model.taskCoordinator) + ctx.setBytes("taskAuditorAddress", model.taskAuditor) + + DINTaskCoordinatorTemplate.createWithContext( + Address.fromBytes(model.taskCoordinator), + ctx, + ) + DINTaskAuditorTemplate.createWithContext( + Address.fromBytes(model.taskAuditor), + ctx, + ) +} + +export function handleModelRejected(event: ModelRejected): void { + let req = ModelRegistrationRequest.load(event.params.requestId.toString()) + if (req == null) return + req.processed = true + req.approved = false + req.processedAtBlock = event.block.number + req.processedAtTimestamp = event.block.timestamp + req.save() +} + +// ─── Manifest update lifecycle ──────────────────────────────────────────────── + +export function handleManifestUpdateRequested( + event: ManifestUpdateRequested +): void { + let id = event.params.requestId.toString() + let mur = new ManifestUpdateRequest(id) + + mur.requestId = event.params.requestId + mur.model = event.params.modelId.toString() + + // requester is not in the event until audit §6.2 addition lands. + // Bridge: derive from Model.owner — only the model owner can call + // requestManifestUpdate (onlyModelOwner modifier). + let model = Model.load(event.params.modelId.toString()) + mur.requester = model != null ? model.owner : event.transaction.from + + // Read fee and CID from storage. + let registry = DINModelRegistry.bind(event.address) + let stored = registry.manifestRequests(event.params.requestId) + mur.feePaid = stored.getFeePaid() + mur.newManifestCID = stored.getNewManifestCID() + + mur.processed = false + mur.approved = null + + mur.submittedAtBlock = event.block.number + mur.submittedAtTimestamp = event.block.timestamp + mur.processedAtBlock = null + mur.processedAtTimestamp = null + + mur.save() +} + +export function handleManifestUpdated(event: ManifestUpdated): void { + let mur = ManifestUpdateRequest.load(event.params.requestId.toString()) + if (mur != null) { + mur.processed = true + mur.approved = true + mur.processedAtBlock = event.block.number + mur.processedAtTimestamp = event.block.timestamp + mur.save() + } + + let model = Model.load(event.params.modelId.toString()) + if (model != null) { + model.manifestCID = event.params.newCID + model.save() + } +} + +export function handleManifestUpdateRejected( + event: ManifestUpdateRejected +): void { + let mur = ManifestUpdateRequest.load(event.params.requestId.toString()) + if (mur == null) return + mur.processed = true + mur.approved = false + mur.processedAtBlock = event.block.number + mur.processedAtTimestamp = event.block.timestamp + mur.save() +} + +// ─── Kill switch ────────────────────────────────────────────────────────────── + +export function handleModelDisabled(event: ModelDisabled): void { + _applyKillSwitch(event.params.modelId, true, event) +} + +export function handleModelEnabled(event: ModelEnabled): void { + _applyKillSwitch(event.params.modelId, false, event) +} + +function _applyKillSwitch( + modelId: BigInt, + disabled: boolean, + event: ModelDisabled | ModelEnabled +): void { + let model = Model.load(modelId.toString()) + if (model != null) { + model.disabled = disabled + model.save() + } + + let ks = new ModelKillSwitchEvent( + eventId(event.transaction.hash, event.logIndex) + ) + ks.model = modelId.toString() + ks.disabled = disabled + ks.blockNumber = event.block.number + ks.blockTimestamp = event.block.timestamp + ks.transactionHash = event.transaction.hash + ks.save() +} + +// ─── Fee governance ─────────────────────────────────────────────────────────── + +// For the four individual setters we read the full current fee schedule from +// storage so each FeeSnapshot always carries a complete picture. + +export function handleOpenSourceFeeUpdated( + event: OpenSourceFeeUpdated +): void { + _saveGranularFeeSnapshot(event.address, "granular", event) +} + +export function handleProprietaryFeeUpdated( + event: ProprietaryFeeUpdated +): void { + _saveGranularFeeSnapshot(event.address, "granular", event) +} + +export function handleOpenSourceUpdateFeeUpdated( + event: OpenSourceUpdateFeeUpdated +): void { + _saveGranularFeeSnapshot(event.address, "granular", event) +} + +export function handleProprietaryUpdateFeeUpdated( + event: ProprietaryUpdateFeeUpdated +): void { + _saveGranularFeeSnapshot(event.address, "granular", event) +} + +function _saveGranularFeeSnapshot( + contractAddress: import("@graphprotocol/graph-ts").Address, + kind: string, + event: OpenSourceFeeUpdated | ProprietaryFeeUpdated | OpenSourceUpdateFeeUpdated | ProprietaryUpdateFeeUpdated +): void { + let registry = DINModelRegistry.bind(contractAddress) + let snap = new FeeSnapshot(eventId(event.transaction.hash, event.logIndex)) + snap.openSourceFee = registry.openSourceFee() + snap.proprietaryFee = registry.proprietaryFee() + snap.openSourceUpdateFee = registry.openSourceUpdateFee() + snap.proprietaryUpdateFee = registry.proprietaryUpdateFee() + snap.updateKind = kind + snap.blockNumber = event.block.number + snap.blockTimestamp = event.block.timestamp + snap.transactionHash = event.transaction.hash + snap.save() +} + +// FeesUpdated carries all four new values directly — no storage call needed. +export function handleFeesUpdated(event: FeesUpdated): void { + let snap = new FeeSnapshot(eventId(event.transaction.hash, event.logIndex)) + snap.openSourceFee = event.params.openSourceFee + snap.proprietaryFee = event.params.proprietaryFee + snap.openSourceUpdateFee = event.params.openSourceUpdateFee + snap.proprietaryUpdateFee = event.params.proprietaryUpdateFee + snap.updateKind = "atomic" + snap.blockNumber = event.block.number + snap.blockTimestamp = event.block.timestamp + snap.transactionHash = event.transaction.hash + snap.save() +} + +export function handleFeesWithdrawn(event: FeesWithdrawn): void { + let w = new RegistryFeeWithdrawal( + eventId(event.transaction.hash, event.logIndex) + ) + w.to = event.params.to + w.amount = event.params.amount + w.blockNumber = event.block.number + w.blockTimestamp = event.block.timestamp + w.transactionHash = event.transaction.hash + w.save() +} + +export function handleDAOAdminUpdated(event: DAOAdminUpdated): void { + let t = new DAOAdminTransfer( + eventId(event.transaction.hash, event.logIndex) + ) + t.oldAdmin = event.params.oldAdmin + t.newAdmin = event.params.newAdmin + t.blockNumber = event.block.number + t.blockTimestamp = event.block.timestamp + t.transactionHash = event.transaction.hash + t.save() +} diff --git a/subgraph/src/staking.ts b/subgraph/src/staking.ts new file mode 100644 index 0000000..9d896c8 --- /dev/null +++ b/subgraph/src/staking.ts @@ -0,0 +1,169 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { + ValidatorStaked, + ValidatorSlashed, + ValidatorUnstakeRequested, + ValidatorWithdrawalClaimed, + ValidatorBlacklisted, + ValidatorUnblacklisted, + SlasherContractAdded, + SlasherContractRemoved, +} from "../generated/DinValidatorStake/DinValidatorStake" +import { + StakeEvent, + SlashEvent, + UnstakeRequest, + WithdrawalClaim, + SlasherRegistration, +} from "../generated/schema" +import { eventId, loadOrCreateValidator, syncValidatorStatus } from "./utils" + +// ─── Stake ──────────────────────────────────────────────────────────────────── + +export function handleValidatorStaked(event: ValidatorStaked): void { + let v = loadOrCreateValidator(event.params.validator) + v.activeStake = v.activeStake.plus(event.params.amount) + v.totalStaked = v.totalStaked.plus(event.params.amount) + syncValidatorStatus(v) + v.save() + + let e = new StakeEvent(eventId(event.transaction.hash, event.logIndex)) + e.validator = event.params.validator.toHexString() + e.amount = event.params.amount + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} + +// ─── Slash ──────────────────────────────────────────────────────────────────── + +export function handleValidatorSlashed(event: ValidatorSlashed): void { + let v = loadOrCreateValidator(event.params.validator) + let amount = event.params.amount + + // Mirror DinValidatorStake.slash(): active stake is consumed first, then + // pending withdrawals. + if (v.activeStake.ge(amount)) { + v.activeStake = v.activeStake.minus(amount) + } else { + let remainder = amount.minus(v.activeStake) + v.activeStake = BigInt.zero() + v.pendingWithdrawal = v.pendingWithdrawal.gt(remainder) + ? v.pendingWithdrawal.minus(remainder) + : BigInt.zero() + if (v.pendingWithdrawal.equals(BigInt.zero())) { + v.withdrawAvailableAt = null + } + } + + v.totalSlashed = v.totalSlashed.plus(amount) + syncValidatorStatus(v) + v.save() + + let e = new SlashEvent(eventId(event.transaction.hash, event.logIndex)) + e.validator = event.params.validator.toHexString() + e.amount = amount + e.reason = event.params.reason + e.slasherContract = event.params.slasher + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} + +// ─── Unstake ────────────────────────────────────────────────────────────────── + +export function handleValidatorUnstakeRequested( + event: ValidatorUnstakeRequested +): void { + let v = loadOrCreateValidator(event.params.validator) + v.activeStake = v.activeStake.minus(event.params.amount) + v.pendingWithdrawal = event.params.amount + v.withdrawAvailableAt = BigInt.fromI64( + event.params.withdrawAvailableAt as i64 + ) + syncValidatorStatus(v) + v.save() + + let id = eventId(event.transaction.hash, event.logIndex) + let u = new UnstakeRequest(id) + u.validator = event.params.validator.toHexString() + u.amount = event.params.amount + u.withdrawAvailableAt = BigInt.fromI64( + event.params.withdrawAvailableAt as i64 + ) + u.claimed = false + u.blockNumber = event.block.number + u.blockTimestamp = event.block.timestamp + u.transactionHash = event.transaction.hash + u.save() +} + +export function handleValidatorWithdrawalClaimed( + event: ValidatorWithdrawalClaimed +): void { + let v = loadOrCreateValidator(event.params.validator) + v.totalWithdrawn = v.totalWithdrawn.plus(event.params.amount) + v.pendingWithdrawal = BigInt.zero() + v.withdrawAvailableAt = null + syncValidatorStatus(v) + v.save() + + let w = new WithdrawalClaim(eventId(event.transaction.hash, event.logIndex)) + w.validator = event.params.validator.toHexString() + w.amount = event.params.amount + w.blockNumber = event.block.number + w.blockTimestamp = event.block.timestamp + w.transactionHash = event.transaction.hash + w.save() +} + +// ─── Blacklist ──────────────────────────────────────────────────────────────── + +export function handleValidatorBlacklisted(event: ValidatorBlacklisted): void { + let v = loadOrCreateValidator(event.params.validator) + v.blacklisted = true + syncValidatorStatus(v) + v.save() +} + +export function handleValidatorUnblacklisted( + event: ValidatorUnblacklisted +): void { + let v = loadOrCreateValidator(event.params.validator) + v.blacklisted = false + syncValidatorStatus(v) + v.save() +} + +// ─── Slasher registry ───────────────────────────────────────────────────────── +// +// DinValidatorStake is the canonical source of the active slasher set. +// The entity ID encodes both the slasher address and the source contract so +// it stays distinct from the DinCoordinator audit-trail records. + +export function handleSlasherContractAdded(event: SlasherContractAdded): void { + let id = event.params.slasher.toHexString() + "-" + event.address.toHexString() + let s = new SlasherRegistration(id) + s.slasherAddress = event.params.slasher + s.sourceContract = event.address + s.active = true + s.addedAtBlock = event.block.number + s.addedAtTimestamp = event.block.timestamp + s.removedAtBlock = null + s.removedAtTimestamp = null + s.save() +} + +export function handleSlasherContractRemoved( + event: SlasherContractRemoved +): void { + let id = event.params.slasher.toHexString() + "-" + event.address.toHexString() + let s = SlasherRegistration.load(id) + if (s == null) return + s.active = false + s.removedAtBlock = event.block.number + s.removedAtTimestamp = event.block.timestamp + s.save() +} diff --git a/subgraph/src/taskAuditor.ts b/subgraph/src/taskAuditor.ts new file mode 100644 index 0000000..22cee34 --- /dev/null +++ b/subgraph/src/taskAuditor.ts @@ -0,0 +1,243 @@ +import { BigInt, Address, Bytes, dataSource } from "@graphprotocol/graph-ts" +import { + DINAuditorRegistered, + AuditorsBatchAuto, + AuditorsBatchesCreated, + AuditScoreSubmitted, + EligibilityVoted, + EligibilityFinalized, + PassScoreUpdated, + AuditorSlashed, +} from "../generated/templates/DINTaskAuditor/DINTaskAuditor" +import { + GlobalIteration, + AuditorRegistration, + AuditBatch, + AuditScoreSubmission, + EligibilityVote, + EligibilityResult, + LocalModelSubmission, + AuditorSlashEvent, + PassScoreUpdate, +} from "../generated/schema" +import { eventId } from "./utils" + +// ─── Context helpers ────────────────────────────────────────────────────────── + +function taAddress(): Address { + return Address.fromBytes(dataSource.context().getBytes("taskAuditorAddress")) +} + +function tcAddressHex(): string { + return dataSource.context().getBytes("taskCoordinatorAddress").toHex() +} + +function modelId(): BigInt { + return dataSource.context().getBigInt("modelId") +} + +/** GlobalIteration ID uses the taskCoordinator address as the stable anchor. */ +function giId(gi: BigInt): string { + return tcAddressHex() + "-" + gi.toString() +} + +function loadOrCreateGI(gi: BigInt, blockNumber: BigInt, blockTimestamp: BigInt): GlobalIteration { + let id = giId(gi) + let entity = GlobalIteration.load(id) + if (entity == null) { + entity = new GlobalIteration(id) + entity.gi = gi + entity.taskCoordinator = dataSource.context().getBytes("taskCoordinatorAddress") + entity.model = modelId().toString() + entity.currentState = 5 // GIstarted — first reachable state after model registration + entity.globalModelCID = null + entity.ended = false + entity.startedAtBlock = blockNumber + entity.startedAtTimestamp = blockTimestamp + entity.endedAtBlock = null + entity.endedAtTimestamp = null + entity.save() + } + return entity! +} + +// Creates a stub LocalModelSubmission so that required relationship fields on +// AuditScoreSubmission / EligibilityVote / EligibilityResult are never null. +// The stub has zero-value client and modelCID until the LocalModelSubmitted +// event lands (audit §4.2) and populates the real values. +function loadOrCreateLMS( + gi: BigInt, + modelIndex: BigInt, + giEntity: GlobalIteration, + blockNumber: BigInt, + blockTimestamp: BigInt, + txHash: Bytes, +): LocalModelSubmission { + let id = taAddress().toHex() + "-" + gi.toString() + "-" + modelIndex.toString() + let entity = LocalModelSubmission.load(id) + if (entity == null) { + entity = new LocalModelSubmission(id) + entity.globalIteration = giEntity.id + entity.modelIndex = modelIndex + entity.client = Address.zero() + entity.modelCID = Bytes.fromI32(0) + entity.eligible = null + entity.finalAvgScore = null + entity.approved = null + entity.blockNumber = blockNumber + entity.blockTimestamp = blockTimestamp + entity.transactionHash = txHash + entity.save() + } + return entity! +} + +function auditBatchId(gi: BigInt, batchId: BigInt): string { + return taAddress().toHex() + "-" + gi.toString() + "-" + batchId.toString() +} + +function loadOrCreateAuditBatch( + gi: BigInt, + batchId: BigInt, + giEntity: GlobalIteration, + blockNumber: BigInt, + blockTimestamp: BigInt, +): AuditBatch { + let id = auditBatchId(gi, batchId) + let entity = AuditBatch.load(id) + if (entity == null) { + entity = new AuditBatch(id) + entity.globalIteration = giEntity.id + entity.batchId = batchId + entity.createdAtBlock = blockNumber + entity.createdAtTimestamp = blockTimestamp + entity.save() + } + return entity! +} + +// ─── Auditor registration ────────────────────────────────────────────────────── + +export function handleAuditorRegistered(event: DINAuditorRegistered): void { + let gi = loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) + + let id = taAddress().toHex() + "-" + event.params.GI.toString() + "-" + event.params.auditor.toHex() + let reg = new AuditorRegistration(id) + reg.globalIteration = gi.id + reg.auditor = event.params.auditor + reg.blockNumber = event.block.number + reg.blockTimestamp = event.block.timestamp + reg.transactionHash = event.transaction.hash + reg.save() +} + +// ─── Audit batch creation ────────────────────────────────────────────────────── + +export function handleAuditBatchCreated(event: AuditorsBatchAuto): void { + let gi = loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) + loadOrCreateAuditBatch(event.params.GI, event.params.batchId, gi, event.block.number, event.block.timestamp) +} + +// AuditorsBatchesCreated is a summary event — all individual batches are +// already created by handleAuditBatchCreated above. No new entity needed. +export function handleAuditorsBatchesCreated(event: AuditorsBatchesCreated): void { + // Ensure the GI anchor exists even if no AuditorsBatchAuto fired yet + // (defensive; in practice they are emitted in the same tx). + loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) +} + +// ─── Scoring ─────────────────────────────────────────────────────────────────── + +export function handleAuditScoreSubmitted(event: AuditScoreSubmitted): void { + let gi = loadOrCreateGI(event.params.gi, event.block.number, event.block.timestamp) + let batch = loadOrCreateAuditBatch(event.params.gi, event.params.batchId, gi, event.block.number, event.block.timestamp) + let lms = loadOrCreateLMS(event.params.gi, event.params.modelIndex, gi, event.block.number, event.block.timestamp, event.transaction.hash) + + let id = taAddress().toHex() + "-" + event.params.gi.toString() + "-" + + event.params.batchId.toString() + "-" + event.params.auditor.toHex() + "-" + + event.params.modelIndex.toString() + let sub = new AuditScoreSubmission(id) + sub.auditBatch = batch.id + sub.localModelSubmission = lms.id + sub.auditor = event.params.auditor + sub.score = event.params.score + sub.blockNumber = event.block.number + sub.blockTimestamp = event.block.timestamp + sub.transactionHash = event.transaction.hash + sub.save() +} + +// ─── Eligibility ─────────────────────────────────────────────────────────────── + +export function handleEligibilityVoted(event: EligibilityVoted): void { + let gi = loadOrCreateGI(event.params.gi, event.block.number, event.block.timestamp) + let batch = loadOrCreateAuditBatch(event.params.gi, event.params.batchId, gi, event.block.number, event.block.timestamp) + let lms = loadOrCreateLMS(event.params.gi, event.params.modelIndex, gi, event.block.number, event.block.timestamp, event.transaction.hash) + + let id = taAddress().toHex() + "-" + event.params.gi.toString() + "-" + + event.params.batchId.toString() + "-" + event.params.modelIndex.toString() + "-" + + event.params.auditor.toHex() + let vote = new EligibilityVote(id) + vote.auditBatch = batch.id + vote.localModelSubmission = lms.id + vote.auditor = event.params.auditor + vote.vote = event.params.vote + vote.blockNumber = event.block.number + vote.blockTimestamp = event.block.timestamp + vote.transactionHash = event.transaction.hash + vote.save() +} + +export function handleEligibilityFinalized(event: EligibilityFinalized): void { + let gi = loadOrCreateGI(event.params.gi, event.block.number, event.block.timestamp) + let batch = loadOrCreateAuditBatch(event.params.gi, event.params.batchId, gi, event.block.number, event.block.timestamp) + let lms = loadOrCreateLMS(event.params.gi, event.params.modelIndex, gi, event.block.number, event.block.timestamp, event.transaction.hash) + + let id = taAddress().toHex() + "-" + event.params.gi.toString() + "-" + + event.params.batchId.toString() + "-" + event.params.modelIndex.toString() + let result = new EligibilityResult(id) + result.auditBatch = batch.id + result.localModelSubmission = lms.id + result.eligible = event.params.eligible + result.totalVotes = event.params.totalVotes + result.blockNumber = event.block.number + result.blockTimestamp = event.block.timestamp + result.transactionHash = event.transaction.hash + result.save() + + // Propagate eligible flag back to the LocalModelSubmission. + lms.eligible = event.params.eligible + lms.save() +} + +// ─── Pass score governance ───────────────────────────────────────────────────── + +export function handlePassScoreUpdated(event: PassScoreUpdated): void { + let e = new PassScoreUpdate(eventId(event.transaction.hash, event.logIndex)) + e.taskAuditor = event.address + e.oldScore = event.params.oldScore + e.newScore = event.params.newScore + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} + +// ─── Auditor slashing ────────────────────────────────────────────────────────── + +export function handleAuditorSlashed(event: AuditorSlashed): void { + let gi = loadOrCreateGI(event.params.gi, event.block.number, event.block.timestamp) + let batch = loadOrCreateAuditBatch(event.params.gi, event.params.batchId, gi, event.block.number, event.block.timestamp) + + let e = new AuditorSlashEvent(eventId(event.transaction.hash, event.logIndex)) + e.globalIteration = gi.id + e.auditBatch = batch.id + e.auditor = event.params.auditor + e.reason = event.params.reason + e.requestedAmount = event.params.requested + e.actualAmount = event.params.actual + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} diff --git a/subgraph/src/taskCoordinator.ts b/subgraph/src/taskCoordinator.ts new file mode 100644 index 0000000..7f9e06f --- /dev/null +++ b/subgraph/src/taskCoordinator.ts @@ -0,0 +1,115 @@ +import { BigInt, dataSource } from "@graphprotocol/graph-ts" +import { + DINValidatorRegistered, + Tier1BatchAuto, + Tier2BatchAuto, + AggregatorSlashed, +} from "../generated/templates/DINTaskCoordinator/DINTaskCoordinator" +import { + GlobalIteration, + AggregatorRegistration, + Tier1Batch, + Tier2Batch, + AggregatorSlashEvent, +} from "../generated/schema" +import { eventId } from "./utils" + +// ─── Context helpers ────────────────────────────────────────────────────────── + +function modelId(): BigInt { + return dataSource.context().getBigInt("modelId") +} + +function giEntityId(gi: BigInt): string { + return dataSource.context().getBytes("taskCoordinatorAddress").toHex() + "-" + gi.toString() +} + +function loadOrCreateGI(gi: BigInt, blockNumber: BigInt, blockTimestamp: BigInt): GlobalIteration { + let id = giEntityId(gi) + let entity = GlobalIteration.load(id) + if (entity == null) { + entity = new GlobalIteration(id) + entity.gi = gi + entity.taskCoordinator = dataSource.context().getBytes("taskCoordinatorAddress") + entity.model = modelId().toString() + entity.currentState = 5 // GIstarted — first reachable state after model registration + entity.globalModelCID = null + entity.ended = false + entity.startedAtBlock = blockNumber + entity.startedAtTimestamp = blockTimestamp + entity.endedAtBlock = null + entity.endedAtTimestamp = null + entity.save() + } + return entity! +} + +// ─── Aggregator registration ─────────────────────────────────────────────────── + +export function handleAggregatorRegistered(event: DINValidatorRegistered): void { + let gi = loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) + + let tcAddress = dataSource.context().getBytes("taskCoordinatorAddress") + let id = tcAddress.toHex() + "-" + event.params.GI.toString() + "-" + event.params.validator.toHex() + let reg = new AggregatorRegistration(id) + reg.globalIteration = gi.id + reg.validator = event.params.validator + reg.blockNumber = event.block.number + reg.blockTimestamp = event.block.timestamp + reg.transactionHash = event.transaction.hash + reg.save() +} + +// ─── Batch creation ──────────────────────────────────────────────────────────── + +export function handleTier1BatchCreated(event: Tier1BatchAuto): void { + let gi = loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) + + let tcAddress = dataSource.context().getBytes("taskCoordinatorAddress") + let id = tcAddress.toHex() + "-" + event.params.GI.toString() + "-" + event.params.batchId.toString() + let batch = new Tier1Batch(id) + batch.globalIteration = gi.id + batch.batchId = event.params.batchId + batch.finalized = false + batch.winningCID = null + batch.createdAtBlock = event.block.number + batch.createdAtTimestamp = event.block.timestamp + batch.finalizedAtBlock = null + batch.finalizedAtTimestamp = null + batch.save() +} + +export function handleTier2BatchCreated(event: Tier2BatchAuto): void { + let gi = loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) + + // batchId is always 0 — ID uses gi only to stay unique per model iteration + let tcAddress = dataSource.context().getBytes("taskCoordinatorAddress") + let id = tcAddress.toHex() + "-" + event.params.GI.toString() + let batch = new Tier2Batch(id) + batch.globalIteration = gi.id + batch.finalized = false + batch.globalModelCID = null + batch.createdAtBlock = event.block.number + batch.createdAtTimestamp = event.block.timestamp + batch.finalizedAtBlock = null + batch.finalizedAtTimestamp = null + batch.save() +} + +// ─── Aggregator slashing ─────────────────────────────────────────────────────── + +export function handleAggregatorSlashed(event: AggregatorSlashed): void { + let gi = loadOrCreateGI(event.params.GI, event.block.number, event.block.timestamp) + + let e = new AggregatorSlashEvent(eventId(event.transaction.hash, event.logIndex)) + e.globalIteration = gi.id + e.batchId = event.params.batchId + e.aggregator = event.params.aggregator + e.reason = event.params.reason + e.requestedAmount = event.params.requested + e.actualAmount = event.params.actual + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} diff --git a/subgraph/src/token.ts b/subgraph/src/token.ts new file mode 100644 index 0000000..2c6e23f --- /dev/null +++ b/subgraph/src/token.ts @@ -0,0 +1,26 @@ +import { TokensMinted, Transfer } from "../generated/DinToken/DinToken" +import { TokenMintEvent, TokenTransferEvent } from "../generated/schema" +import { eventId } from "./utils" + +export function handleTokensMinted(event: TokensMinted): void { + let e = new TokenMintEvent(eventId(event.transaction.hash, event.logIndex)) + e.to = event.params.to + e.amount = event.params.amount + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} + +// Standard ERC20 Transfer — covers mints (from = 0x0) and normal transfers. +// Burns are not possible in DinToken but the handler is intentionally general. +export function handleTransfer(event: Transfer): void { + let e = new TokenTransferEvent(eventId(event.transaction.hash, event.logIndex)) + e.from = event.params.from + e.to = event.params.to + e.amount = event.params.value + e.blockNumber = event.block.number + e.blockTimestamp = event.block.timestamp + e.transactionHash = event.transaction.hash + e.save() +} diff --git a/subgraph/src/utils.ts b/subgraph/src/utils.ts new file mode 100644 index 0000000..383a8bc --- /dev/null +++ b/subgraph/src/utils.ts @@ -0,0 +1,53 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { Validator } from "../generated/schema" + +export const MIN_STAKE = BigInt.fromString("10000000000000000000") // 10 DIN (18 decimals) + +/** Stable entity ID for event-based entities: txHash-logIndex. */ +export function eventId(txHash: Bytes, logIndex: BigInt): string { + return txHash.toHex() + "-" + logIndex.toString() +} + +/** Load a Validator aggregate entity, initialising it on first encounter. */ +export function loadOrCreateValidator(address: Bytes): Validator { + let id = address.toHexString() + let v = Validator.load(id) + if (v == null) { + v = new Validator(id) + v.address = address + v.activeStake = BigInt.zero() + v.pendingWithdrawal = BigInt.zero() + v.withdrawAvailableAt = null + v.totalStaked = BigInt.zero() + v.totalSlashed = BigInt.zero() + v.totalWithdrawn = BigInt.zero() + v.status = "None" + v.blacklisted = false + } + return v! +} + +/** + * Derive the validator status string from the aggregate's current field + * values, mirroring DinValidatorStake._syncValidatorStatus(). + * Called after every mutation that can change stake or withdrawal state. + */ +export function syncValidatorStatus(v: Validator): void { + if (v.blacklisted) { + v.status = "Blacklisted" + return + } + if (v.pendingWithdrawal.gt(BigInt.zero())) { + v.status = "Exiting" + return + } + if (v.activeStake.ge(MIN_STAKE)) { + v.status = "Active" + return + } + if (v.activeStake.gt(BigInt.zero())) { + v.status = "Exiting" + return + } + v.status = "None" +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml new file mode 100644 index 0000000..8b327ac --- /dev/null +++ b/subgraph/subgraph.yaml @@ -0,0 +1,341 @@ +specVersion: 0.0.5 +description: > + DIN Protocol subgraph. Indexes the four platform contracts (DINModelRegistry, + DinValidatorStake, DinCoordinator, DinToken) and task-level contracts + (DINTaskCoordinator, DINTaskAuditor) via dynamic data source templates on the + local development chain (chainId 1337). +repository: https://github.com/InfiniteZeroFoundation/DevNet +schema: + file: ./schema.graphql + +# ─── Network note ──────────────────────────────────────────────────────────── +# The network name "din-local" must match the network configured in the +# Graph node's config.toml or networks.toml (see docker-compose.yml). The +# Graph node maps this name to the local anvil/hardhat RPC at +# http://localhost:8545 (chainId 1337). +# +# ─── Address note ──────────────────────────────────────────────────────────── +# Placeholder addresses below must be replaced with the actual addresses +# after deploying the platform contracts to the local chain. The canonical +# deploy command is: +# cd hardhat && npx hardhat run scripts/deploy.ts --network localhost +# Deployed addresses are written to hardhat/deployments/local.json. +# A helper script (scripts/update-subgraph-addresses.sh) will patch this file +# automatically once P4-IDX2 setup tooling is in place. +# ───────────────────────────────────────────────────────────────────────────── + +dataSources: + + # ── DINModelRegistry ──────────────────────────────────────────────────────── + - kind: ethereum + name: DINModelRegistry + network: din-local + source: + address: "0x0000000000000000000000000000000000000001" # replace after deploy + abi: DINModelRegistry + startBlock: 0 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - ModelRegistrationRequest + - Model + - ManifestUpdateRequest + - ModelKillSwitchEvent + - FeeSnapshot + - RegistryFeeWithdrawal + - DAOAdminTransfer + abis: + - name: DINModelRegistry + file: ./abis/DINModelRegistry.json + eventHandlers: + # Registration request lifecycle + - event: ModelRegistrationRequested(indexed uint256,indexed address) + handler: handleModelRegistrationRequested + # NOTE: missing isOpenSource and fee fields until audit §6.1 addition + # lands. Handler will call modelRequests(requestId) as a temporary + # bridge to populate those fields. + - event: ModelApproved(indexed uint256,indexed uint256) + handler: handleModelApproved + - event: ModelRejected(indexed uint256) + handler: handleModelRejected + + # Manifest update lifecycle + - event: ManifestUpdateRequested(indexed uint256,indexed uint256) + handler: handleManifestUpdateRequested + # NOTE: missing requester field until audit §6.2 addition lands. + # Handler will derive requester from Model.owner as a bridge. + - event: ManifestUpdated(indexed uint256,indexed uint256,bytes32) + handler: handleManifestUpdated + - event: ManifestUpdateRejected(indexed uint256) + handler: handleManifestUpdateRejected + + # Kill switch + - event: ModelDisabled(indexed uint256) + handler: handleModelDisabled + - event: ModelEnabled(indexed uint256) + handler: handleModelEnabled + + # Fee governance — granular setters + - event: OpenSourceFeeUpdated(uint256) + handler: handleOpenSourceFeeUpdated + - event: ProprietaryFeeUpdated(uint256) + handler: handleProprietaryFeeUpdated + - event: OpenSourceUpdateFeeUpdated(uint256) + handler: handleOpenSourceUpdateFeeUpdated + - event: ProprietaryUpdateFeeUpdated(uint256) + handler: handleProprietaryUpdateFeeUpdated + + # Fee governance — atomic setter (governance proposals) + - event: FeesUpdated(uint256,uint256,uint256,uint256) + handler: handleFeesUpdated + + # Fee withdrawal and admin rotation + - event: FeesWithdrawn(indexed address,uint256) + handler: handleFeesWithdrawn + - event: DAOAdminUpdated(indexed address,indexed address) + handler: handleDAOAdminUpdated + file: ./src/registry.ts + + # ── DinValidatorStake ──────────────────────────────────────────────────────── + - kind: ethereum + name: DinValidatorStake + network: din-local + source: + address: "0x0000000000000000000000000000000000000002" # replace after deploy + abi: DinValidatorStake + startBlock: 0 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Validator + - StakeEvent + - SlashEvent + - UnstakeRequest + - WithdrawalClaim + - SlasherRegistration + abis: + - name: DinValidatorStake + file: ./abis/DinValidatorStake.json + eventHandlers: + - event: ValidatorStaked(indexed address,uint256) + handler: handleValidatorStaked + - event: ValidatorSlashed(indexed address,uint256,indexed bytes32,indexed address) + handler: handleValidatorSlashed + - event: ValidatorUnstakeRequested(indexed address,uint256,uint64) + handler: handleValidatorUnstakeRequested + - event: ValidatorWithdrawalClaimed(indexed address,uint256) + handler: handleValidatorWithdrawalClaimed + - event: ValidatorBlacklisted(indexed address) + handler: handleValidatorBlacklisted + - event: ValidatorUnblacklisted(indexed address) + handler: handleValidatorUnblacklisted + + # SlasherContractAdded/Removed are emitted by both DinCoordinator and + # DinValidatorStake with identical signatures (see event-coverage-audit.md §5). + # This data source produces the canonical slasher-set records + # (sourceContract = DinValidatorStake address). + - event: SlasherContractAdded(indexed address) + handler: handleSlasherContractAdded + - event: SlasherContractRemoved(indexed address) + handler: handleSlasherContractRemoved + file: ./src/staking.ts + + # ── DinCoordinator ─────────────────────────────────────────────────────────── + - kind: ethereum + name: DinCoordinator + network: din-local + source: + address: "0x0000000000000000000000000000000000000003" # replace after deploy + abi: DinCoordinator + startBlock: 0 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - DINMintEvent + - TreasuryWithdrawal + - ExchangeRateSnapshot + - SlasherRegistration + abis: + - name: DinCoordinator + file: ./abis/DinCoordinator.json + eventHandlers: + - event: EthDepositAndDINminted(indexed address,uint256,uint256) + handler: handleEthDepositAndDINminted + - event: DinPerEthUpdated(uint256) + handler: handleDinPerEthUpdated + - event: ValidatorStakeContractUpdated(indexed address) + handler: handleValidatorStakeContractUpdated + + # Audit-trail records only — canonical slasher set is owned by + # DinValidatorStake. sourceContract set to DinCoordinator address. + - event: SlasherContractAdded(indexed address) + handler: handleCoordinatorSlasherAdded + - event: SlasherContractRemoved(indexed address) + handler: handleCoordinatorSlasherRemoved + + # ETHTreasuryWithdrawn not yet in ABI — handler registered here for + # when the audit §6.3 addition lands and the ABI is updated. + # Uncomment once the event exists: + # - event: ETHTreasuryWithdrawn(indexed address,uint256) + # handler: handleETHTreasuryWithdrawn + file: ./src/coordinator.ts + + # ── DinToken ───────────────────────────────────────────────────────────────── + - kind: ethereum + name: DinToken + network: din-local + source: + address: "0x0000000000000000000000000000000000000004" # replace after deploy + abi: DinToken + startBlock: 0 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - TokenMintEvent + - TokenTransferEvent + abis: + - name: DinToken + file: ./abis/DinToken.json + eventHandlers: + - event: TokensMinted(indexed address,uint256) + handler: handleTokensMinted + # Standard ERC20 Transfer — includes mint (from=0x0) and burn (to=0x0) + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTransfer + file: ./src/token.ts + +# ───────────────────────────────────────────────────────────────────────────── +# Dynamic data source templates — task-level contracts +# +# One DINTaskCoordinator + DINTaskAuditor pair is deployed per model by the +# model owner. Template instances are created inside handleModelApproved() +# (registry.ts) when DINModelRegistry emits ModelApproved. The model ID and +# paired contract address are passed via DataSourceContext so task-level +# handlers can link entities back to the platform-level Model entity. +# +# Pending event additions (see task-contract-event-audit.md §4) are commented +# out below. Uncomment each once the corresponding event lands in the contract +# and the ABI is regenerated. +# ───────────────────────────────────────────────────────────────────────────── + +templates: + + # ── DINTaskCoordinator ────────────────────────────────────────────────────── + - kind: ethereum + name: DINTaskCoordinator + network: din-local + source: + abi: DINTaskCoordinator + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - GlobalIteration + - AggregatorRegistration + - Tier1Batch + - Tier2Batch + - AggregatorSlashEvent + # Pending entities (populated once proposed events land): + # - GIStateTransition + # - T1AggregationSubmission + # - T2AggregationSubmission + abis: + - name: DINTaskCoordinator + file: ./abis/DINTaskCoordinator.json + # DINModelRegistry ABI needed for storage calls in handleModelApproved + # template-creation context — already referenced by the registry mapping. + eventHandlers: + # ── Aggregator registration ────────────────────────────────────────── + - event: DINValidatorRegistered(indexed uint256,indexed address) + handler: handleAggregatorRegistered + + # ── Batch creation ─────────────────────────────────────────────────── + - event: Tier1BatchAuto(indexed uint256,indexed uint256) + handler: handleTier1BatchCreated + - event: Tier2BatchAuto(indexed uint256,indexed uint256) + handler: handleTier2BatchCreated + + # ── Aggregator slashing ────────────────────────────────────────────── + - event: AggregatorSlashed(indexed uint256,indexed uint256,indexed address,bytes32,uint256,uint256) + handler: handleAggregatorSlashed + + # ── Pending — uncomment once events land in the contract ───────────── + # - event: GIStateChanged(indexed uint256,indexed uint8) + # handler: handleGIStateChanged + # - event: T1AggregationSubmitted(indexed uint256,indexed uint256,indexed address,bytes32) + # handler: handleT1AggregationSubmitted + # - event: T2AggregationSubmitted(indexed uint256,indexed uint256,indexed address,bytes32) + # handler: handleT2AggregationSubmitted + # - event: T1BatchFinalized(indexed uint256,indexed uint256,bytes32) + # handler: handleT1BatchFinalized + # - event: T2Finalized(indexed uint256,bytes32) + # handler: handleT2Finalized + file: ./src/taskCoordinator.ts + + # ── DINTaskAuditor ─────────────────────────────────────────────────────────── + - kind: ethereum + name: DINTaskAuditor + network: din-local + source: + abi: DINTaskAuditor + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - GlobalIteration + - AuditorRegistration + - AuditBatch + - AuditScoreSubmission + - EligibilityVote + - EligibilityResult + - AuditorSlashEvent + - PassScoreUpdate + # Pending entity (populated once LocalModelSubmitted event lands): + # - LocalModelSubmission + abis: + - name: DINTaskAuditor + file: ./abis/DINTaskAuditor.json + eventHandlers: + # ── Auditor registration ───────────────────────────────────────────── + - event: DINAuditorRegistered(indexed uint256,indexed address) + handler: handleAuditorRegistered + + # ── Audit batch creation ───────────────────────────────────────────── + - event: AuditorsBatchAuto(indexed uint256,indexed uint256) + handler: handleAuditBatchCreated + - event: AuditorsBatchesCreated(indexed uint256,uint256) + handler: handleAuditorsBatchesCreated + + # ── Scoring and eligibility ────────────────────────────────────────── + - event: AuditScoreSubmitted(indexed uint256,indexed uint256,indexed address,uint256,uint256) + handler: handleAuditScoreSubmitted + - event: EligibilityVoted(indexed uint256,indexed uint256,indexed uint256,address,bool) + handler: handleEligibilityVoted + - event: EligibilityFinalized(indexed uint256,indexed uint256,indexed uint256,bool,uint256) + handler: handleEligibilityFinalized + + # ── Pass score governance ──────────────────────────────────────────── + - event: PassScoreUpdated(uint256,uint256) + handler: handlePassScoreUpdated + + # ── Auditor slashing ───────────────────────────────────────────────── + - event: AuditorSlashed(indexed uint256,indexed uint256,indexed address,bytes32,uint256,uint256) + handler: handleAuditorSlashed + + # RewardDeposited is declared in the ABI but never emitted (dead code — + # see task-contract-event-audit.md §5). No handler registered. + + # ── Pending — uncomment once LocalModelSubmitted event lands ───────── + # - event: LocalModelSubmitted(indexed uint256,indexed uint256,indexed address,bytes32) + # handler: handleLocalModelSubmitted + file: ./src/taskAuditor.ts diff --git a/tests/test_list_pending_requests.py b/tests/test_list_pending_requests.py new file mode 100644 index 0000000..f9a87b4 --- /dev/null +++ b/tests/test_list_pending_requests.py @@ -0,0 +1,165 @@ +""" +Unit tests for the GraphQL integration in dindao list-pending-requests. + +Covers: + - GraphQL path: model registration requests returned from subgraph + - GraphQL path: manifest update requests returned from subgraph + - Fallback to RPC when the subgraph endpoint is unreachable + - Fallback to RPC when the subgraph returns a GraphQL error body +""" + +from types import SimpleNamespace +from unittest.mock import MagicMock, patch + +import pytest + +import dincli.cli.dindao as dindao_module + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_response(data: dict | None = None, errors: list | None = None, status: int = 200): + """Build a minimal requests.Response-like mock.""" + body: dict = {} + if errors is not None: + body["errors"] = errors + elif data is not None: + body["data"] = data + resp = MagicMock() + resp.status_code = status + resp.raise_for_status = MagicMock( + side_effect=None if status < 400 else Exception(f"HTTP {status}") + ) + resp.json = MagicMock(return_value=body) + return resp + + +class _DummyW3: + def from_wei(self, amount: int, unit: str) -> str: + assert unit == "ether" + return str(amount / 10**18) + + +def _make_ctx(w3=None): + """Minimal ctx.obj that satisfies the command's usage.""" + w3 = w3 or _DummyW3() + console_messages: list = [] + + class _Console: + def print(self, msg, *args, **kwargs): + console_messages.append(msg) + + registry_contract = MagicMock() + # Default: no requests on-chain (used only in fallback paths) + registry_contract.functions.totalModelRequests.return_value.call.return_value = 0 + registry_contract.functions.totalManifestRequests.return_value.call.return_value = 0 + + ctx_obj = MagicMock() + ctx_obj.get_en_w3_account_console.return_value = ("local", w3, MagicMock(), _Console()) + ctx_obj.get_deployed_din_registry_contract.return_value = registry_contract + + ctx = MagicMock() + ctx.obj = ctx_obj + return ctx, console_messages, registry_contract + + +# --------------------------------------------------------------------------- +# GraphQL — model registration requests +# --------------------------------------------------------------------------- + + +def test_graphql_model_path(): + ctx, msgs, registry = _make_ctx() + fake_data = { + "modelRegistrationRequests": [ + {"requestId": "0", "requester": "0xABCD", "isOpenSource": True, "feePaid": "5000000000000000000"}, + {"requestId": "1", "requester": "0xDEAD", "isOpenSource": False, "feePaid": "10000000000000000000"}, + ] + } + with patch.object(dindao_module._requests, "post", return_value=_make_response(fake_data)): + dindao_module.list_pending_requests.callback(ctx, req_type="model") + + output = "\n".join(str(m) for m in msgs) + assert "Request ID 0" in output + assert "0xABCD" in output + assert "Request ID 1" in output + assert "0xDEAD" in output + # RPC must NOT have been called when GraphQL succeeded + registry.functions.totalModelRequests.assert_not_called() + + +def test_graphql_model_path_empty(): + ctx, msgs, registry = _make_ctx() + fake_data = {"modelRegistrationRequests": []} + with patch.object(dindao_module._requests, "post", return_value=_make_response(fake_data)): + dindao_module.list_pending_requests.callback(ctx, req_type="model") + + output = "\n".join(str(m) for m in msgs) + assert "No pending model registration requests" in output + registry.functions.totalModelRequests.assert_not_called() + + +# --------------------------------------------------------------------------- +# GraphQL — manifest update requests +# --------------------------------------------------------------------------- + + +def test_graphql_manifest_path(): + ctx, msgs, registry = _make_ctx() + fake_data = { + "manifestUpdateRequests": [ + { + "requestId": "0", + "model": {"modelId": "3"}, + "requester": "0xCAFE", + "feePaid": "1000000000000000000", + } + ] + } + with patch.object(dindao_module._requests, "post", return_value=_make_response(fake_data)): + dindao_module.list_pending_requests.callback(ctx, req_type="manifest") + + output = "\n".join(str(m) for m in msgs) + assert "Request ID 0" in output + assert "Model ID: 3" in output + assert "0xCAFE" in output + registry.functions.totalManifestRequests.assert_not_called() + + +# --------------------------------------------------------------------------- +# Fallback — connection error +# --------------------------------------------------------------------------- + + +def test_rpc_fallback_on_connection_error(): + ctx, msgs, registry = _make_ctx() + # Subgraph unreachable + with patch.object( + dindao_module._requests, "post", side_effect=ConnectionError("refused") + ): + dindao_module.list_pending_requests.callback(ctx, req_type="model") + + # RPC loop must have been invoked + registry.functions.totalModelRequests.return_value.call.assert_called_once() + + +def test_rpc_fallback_on_graphql_error_body(): + ctx, msgs, registry = _make_ctx() + error_resp = _make_response(errors=[{"message": "subgraph not found"}]) + with patch.object(dindao_module._requests, "post", return_value=error_resp): + dindao_module.list_pending_requests.callback(ctx, req_type="model") + + # GraphQL returned errors — must fall through to RPC + registry.functions.totalModelRequests.return_value.call.assert_called_once() + + +def test_rpc_fallback_on_http_error(): + ctx, msgs, registry = _make_ctx() + bad_resp = _make_response(status=503) + with patch.object(dindao_module._requests, "post", return_value=bad_resp): + dindao_module.list_pending_requests.callback(ctx, req_type="model") + + registry.functions.totalModelRequests.return_value.call.assert_called_once()