From 9ff2a3149598350c499a62464fedb6c9d703da5d Mon Sep 17 00:00:00 2001 From: umeradl Date: Fri, 15 May 2026 02:42:38 +0000 Subject: [PATCH 01/13] dindao also added --- Documentation/dindao.md | 214 +++++++++++++++++++++ dincli/cli/dindao.py | 293 +++++++++++++++++++++++++++-- dincli/main.py | 4 +- dist/dincli-0.1.0-py3-none-any.whl | Bin 74789 -> 76867 bytes dist/dincli-0.1.0.tar.gz | Bin 71282 -> 73380 bytes 5 files changed, 489 insertions(+), 22 deletions(-) create mode 100644 Documentation/dindao.md diff --git a/Documentation/dindao.md b/Documentation/dindao.md new file mode 100644 index 0000000..aea035b --- /dev/null +++ b/Documentation/dindao.md @@ -0,0 +1,214 @@ +# DIN DAO Documentation + +The DIN DAO (Decentralized Autonomous Organization) administers the core infrastructure contracts of the DIN network. This includes deploying the fundamental contracts and authorizing participants (slashers) who can penalize misbehaving validators. + +--- + +## 1. Deployment + +Deploy the core contracts in the order listed below. Each contract depends on the previous one being live. + +> [!NOTE] +> The `--artifact` flag must point to the compiled JSON output from Hardhat/ Foundry (contains the ABI and bytecode). + +### 1. DIN Coordinator + +The main coordinator contract that governs network-wide operations. + +```bash +dincli dindao deploy din-coordinator --artifact +``` + +### 2. Validator Stake + +The staking contract used by validators (Auditors, Aggregators). + +```bash +dincli dindao deploy din-validator-stake --artifact +``` + +### 3. Model Registry + +Records federated learning tasks, assigns a unique `model_id` to each task, and stores the initial global model reference and manifest for a task. + +```bash +dincli dindao deploy din-model-registry --artifact +``` + +--- + +## 2. Registry Management + +### View Total Models + +Check how many models are currently approved in the network. + +```bash +dincli dindao registry total-models +``` + +--- + +### Model Registration Approval + +Model registration follows a **request → approval** flow. Model Owners submit requests; the DAO reviews and approves or rejects them. + +**List pending registration requests:** + +```bash +dincli dindao registry list-requests [--pending] +``` + +**Approve a model registration request:** + +```bash +dincli dindao registry approve-model +``` + +> [!IMPORTANT] +> Approval revalidates the coordinator and auditor contracts at the time of the call. If either contract has lost slasher status or been transferred to a different owner since the request was submitted, the transaction will revert. The requester must submit a new request. + +**Reject a model registration request:** + +```bash +dincli dindao registry reject-model +``` + +The registration fee is retained by the contract in both cases. + +--- + +### Manifest Update Approval + +Manifest updates also follow a request → approval flow. + +**Approve a manifest update:** + +```bash +dincli dindao registry approve-manifest-update +``` + +> [!NOTE] +> Approving a manifest update for a disabled model will revert. Enable the model first if the update is intentional. + +**Reject a manifest update:** + +```bash +dincli dindao registry reject-manifest-update +``` + +--- + +### Kill Switch — Disable / Enable Models + +Disable a model immediately. This blocks manifest update requests from the model owner and should be checked by downstream contracts (`TaskCoordinator`, `TaskAuditor`) before executing any model tasks. + +```bash +# Disable a model (emergency stop) +dincli dindao registry disable-model + +# Re-enable a model +dincli dindao registry enable-model +``` + +> [!CAUTION] +> Disabling a model does not delete it. All on-chain history is preserved. Downstream contracts must actively check `modelDisabled(modelId)` for the kill switch to have operational effect. + +--- + +## 3. Fee Governance + +The registry charges fees for model registration and manifest update requests. All four fee parameters are DAO-controlled. + +| Parameter | Default | Applies To | +|-----------|---------|-----------| +| `openSourceFee` | 0.000001 ETH | Open-source model registration | +| `proprietaryFee` | 0.00001 ETH | Proprietary model registration | +| `openSourceUpdateFee` | 0.0000001 ETH | Open-source manifest update requests | +| `proprietaryUpdateFee` | 0.000001 ETH | Proprietary manifest update requests | + +**Update a single fee:** + +```bash +dincli dindao registry set-fee --open-source-fee +dincli dindao registry set-fee --proprietary-fee +dincli dindao registry set-fee --open-source-update-fee +dincli dindao registry set-fee --proprietary-update-fee +``` + +**Update all fees atomically (preferred for governance proposals):** + +```bash +dincli dindao registry set-fees \ + --open-source-fee \ + --proprietary-fee \ + --open-source-update-fee \ + --proprietary-update-fee +``` + +**Withdraw accumulated fees:** + +```bash +dincli dindao registry withdraw-fees --to
+``` + +--- + +## 4. Slasher Management + +Slashers are contracts authorized to penalize misbehaving participants. The Task Coordinator and Task Auditor contracts must be registered as slashers before they can enforce penalties. + +### Register Task Coordinator as a Slasher + +> **Prerequisite** — the following key must be set in your `.env` file: +> - `_DINTaskCoordinator_Contract_Address` +> *(e.g. `SEPOLIA_OP_DEVNET_DINTaskCoordinator_Contract_Address`)* + +```bash +dincli dindao add-slasher --taskCoordinator +``` + +### Register Task Auditor as a Slasher + +> **Prerequisite** — the following keys must be set in your `.env` file: +> - `_DINTaskCoordinator_Contract_Address` +> *(e.g. `SEPOLIA_OP_DEVNET_DINTaskCoordinator_Contract_Address`)* +> - `__DINTaskAuditor_Contract_Address` +> *(e.g. `SEPOLIA_OP_DEVNET_0x1234...7890_DINTaskAuditor_Contract_Address`)* + +```bash +dincli dindao add-slasher --taskAuditor +``` + +### Register by Address Directly + +If you already know the contract address, you can pass it explicitly instead of relying on the `.env` file: + +```bash +dincli dindao add-slasher --contract +``` + +--- + +## 5. DAO Admin Transfer + +The DAO admin role can be transferred to a multisig or on-chain timelock without redeploying the registry. + +```bash +dincli dindao registry set-admin +``` + +> [!CAUTION] +> This action is irreversible from the old admin address. Confirm the new address is correct before proceeding. + +--- + +## Workflow + +1. **Deploy** — Coordinator → Validator Stake → Model Registry (in order). +2. **Configure Slashers** — After each new task is created, register its Task Coordinator and Task Auditor as slashers. +3. **Process Registration Requests** — Review pending `ModelRequest` entries; approve or reject each one. +4. **Process Manifest Update Requests** — Review pending `ManifestUpdateRequest` entries. +5. **Monitor** — Use registry commands to track network growth and model status. +6. **Emergency** — Use `disable-model` if a model needs to be stopped immediately. + diff --git a/dincli/cli/dindao.py b/dincli/cli/dindao.py index 5d4370e..6b75f27 100644 --- a/dincli/cli/dindao.py +++ b/dincli/cli/dindao.py @@ -258,31 +258,284 @@ def total_models(ctx: typer.Context, console.print(f"[bold green]Total models: {models_length}[/bold green]") -@registry_app.command("update-proprietary-fee") -def update_proprietary_fee(ctx: typer.Context, - amount: float = typer.Argument(..., help="Amount of ETH as proprietary fee in DIN model registry") -): +def build_and_send_tx(ctx, contract_function, action_msg, success_msg, error_msg): effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + tx_params = ctx.obj.get_tx_params() + try: + tx_params["gas"] = int(w3.eth.estimate_gas(contract_function.build_transaction(tx_params)) * 1.1) + except Exception as e: + console.print(f"[bold red] X Transaction estimation failed: {e}[/bold red]") + raise typer.Exit(1) + + tx = contract_function.build_transaction(tx_params) + signed_tx = account.sign_transaction(tx) + console.print(f"[bold green]{action_msg}...[/bold green]") + tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) + tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) + if tx_receipt.status == 1: + console.print(f"[bold green] ✓ {success_msg}[/bold green]") + else: + console.print(f"[bold red] X {error_msg}[/bold red]") + raise typer.Exit(1) +@registry_app.command("approve-model") +def approve_model(ctx: typer.Context, request_id: int = typer.Argument(..., help="Model request ID to approve")): + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.approveModel(request_id), + f"Approving model request {request_id}", + f"Model request {request_id} approved successfully", + f"Failed to approve model request {request_id}" + ) + +@registry_app.command("reject-model") +def reject_model(ctx: typer.Context, request_id: int = typer.Argument(..., help="Model request ID to reject")): + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.rejectModel(request_id), + f"Rejecting model request {request_id}", + f"Model request {request_id} rejected successfully", + f"Failed to reject model request {request_id}" + ) + +@registry_app.command("approve-manifest-update") +def approve_manifest_update(ctx: typer.Context, request_id: int = typer.Argument(..., help="Manifest update request ID to approve")): + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.approveManifestUpdate(request_id), + f"Approving manifest update request {request_id}", + f"Manifest update request {request_id} approved successfully", + f"Failed to approve manifest update request {request_id}" + ) + +@registry_app.command("reject-manifest-update") +def reject_manifest_update(ctx: typer.Context, request_id: int = typer.Argument(..., help="Manifest update request ID to reject")): + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.rejectManifestUpdate(request_id), + f"Rejecting manifest update request {request_id}", + f"Manifest update request {request_id} rejected successfully", + f"Failed to reject manifest update request {request_id}" + ) + +@registry_app.command("disable-model") +def disable_model(ctx: typer.Context, model_id: int = typer.Argument(..., help="Model ID to disable")): + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.disableModel(model_id), + f"Disabling model {model_id}", + f"Model {model_id} disabled successfully", + f"Failed to disable model {model_id}" + ) + +@registry_app.command("enable-model") +def enable_model(ctx: typer.Context, model_id: int = typer.Argument(..., help="Model ID to enable")): + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.enableModel(model_id), + f"Enabling model {model_id}", + f"Model {model_id} enabled successfully", + f"Failed to enable model {model_id}" + ) + +@registry_app.command("set-open-source-fee") +def set_open_source_fee(ctx: typer.Context, amount: float = typer.Argument(..., help="Amount of ETH")): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() - amount_wei = w3.to_wei(amount, 'ether') + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.setOpenSourceFee(amount_wei), + f"Updating open source fee to {amount} ETH", + "Open source fee updated successfully", + "Failed to update open source fee" + ) + +@registry_app.command("set-proprietary-fee") +def set_proprietary_fee(ctx: typer.Context, amount: float = typer.Argument(..., help="Amount of ETH")): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + amount_wei = w3.to_wei(amount, 'ether') + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.setProprietaryFee(amount_wei), + f"Updating proprietary fee to {amount} ETH", + "Proprietary fee updated successfully", + "Failed to update proprietary fee" + ) + +@registry_app.command("set-open-source-update-fee") +def set_open_source_update_fee(ctx: typer.Context, amount: float = typer.Argument(..., help="Amount of ETH")): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + amount_wei = w3.to_wei(amount, 'ether') + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.setOpenSourceUpdateFee(amount_wei), + f"Updating open source update fee to {amount} ETH", + "Open source update fee updated successfully", + "Failed to update open source update fee" + ) + +@registry_app.command("set-proprietary-update-fee") +def set_proprietary_update_fee(ctx: typer.Context, amount: float = typer.Argument(..., help="Amount of ETH")): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + amount_wei = w3.to_wei(amount, 'ether') + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.setProprietaryUpdateFee(amount_wei), + f"Updating proprietary update fee to {amount} ETH", + "Proprietary update fee updated successfully", + "Failed to update proprietary update fee" + ) + +@registry_app.command("set-fees") +def set_fees( + ctx: typer.Context, + open_source: float = typer.Option(..., "--open-source", help="Open source fee in ETH"), + proprietary: float = typer.Option(..., "--proprietary", help="Proprietary fee in ETH"), + open_source_update: float = typer.Option(..., "--open-source-update", help="Open source update fee in ETH"), + proprietary_update: float = typer.Option(..., "--proprietary-update", help="Proprietary update fee in ETH") +): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.setFees( + w3.to_wei(open_source, 'ether'), + w3.to_wei(proprietary, 'ether'), + w3.to_wei(open_source_update, 'ether'), + w3.to_wei(proprietary_update, 'ether') + ), + "Updating all fees atomically", + "All fees updated successfully", + "Failed to update all fees" + ) + +@registry_app.command("withdraw-fees") +def withdraw_fees(ctx: typer.Context, to: str = typer.Argument(..., help="Address to withdraw fees to")): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + target_address = w3.to_checksum_address(to) + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.withdrawFees(target_address), + f"Withdrawing fees to {target_address}", + "Fees withdrawn successfully", + "Failed to withdraw fees" + ) + +@registry_app.command("set-dao-admin") +def set_dao_admin(ctx: typer.Context, new_admin: str = typer.Argument(..., help="New DAO admin address")): + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + target_address = w3.to_checksum_address(new_admin) + build_and_send_tx( + ctx, + DINModelRegistry_Contract.functions.setDAOAdmin(target_address), + f"Setting DAO admin to {target_address}", + "DAO admin set successfully", + "Failed to set DAO admin" + ) + +@registry_app.command("unprocessed-requests") +def unprocessed_requests(ctx: typer.Context, req_type: str = typer.Option(None, "--type", "-t", help="Type of request: 'model' or 'manifest'")): + """Get all unprocessed Model and ManifestUpdate requests""" + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() - tx_params = ctx.obj.get_tx_params() - tx_params["gas"] = int(w3.eth.estimate_gas(DINModelRegistry_Contract.functions.setProprietaryFee(amount_wei).build_transaction(tx_params)) * 1.1) # Add 10% buffer - - tx = DINModelRegistry_Contract.functions.setProprietaryFee(amount_wei).build_transaction(tx_params) - - # Sign transaction - signed_tx = account.sign_transaction(tx) - - console.print(f"[bold green]Updating proprietary fee to {amount} ETH in DINModelRegistry contract...[/bold green]") + if req_type == "model" or req_type is None: + console.print("[bold cyan]Unprocessed Model Requests:[/bold cyan]") + + totalModelRequests = DINModelRegistry_Contract.functions.totalModelRequests().call() + found_model = False + for idx in range(totalModelRequests): + req = DINModelRegistry_Contract.functions.modelRequests(idx).call() + # req[6] is 'processed' + if not req[6]: + console.print(f" [green]Request ID {idx}[/green] - Requester: {req[0]}") + found_model = True + if not found_model: + console.print(" [gray]No unprocessed model requests[/gray]") + + elif req_type == "manifest" or req_type is None: + console.print("\n[bold cyan]Unprocessed 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() + # req[4] is 'processed' + if not req[4]: + console.print(f" [green]Request ID {idx}[/green] - Model ID: {req[0]}, Requester: {req[2]}") + found_manifest = True + if not found_manifest: + console.print(" [gray]No unprocessed manifest update requests[/gray]") - # Send raw transaction - tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) - tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) + else: + console.print("[bold red]Invalid request type. Must be 'model' or 'manifest'.[/bold red]") + raise typer.Exit(1) - if tx_receipt.status == 1: - console.print("[bold green] ✓ Proprietary fee updated successfully[/bold green]") +@registry_app.command("explore-request") +def explore_request( + ctx: typer.Context, + req_type: str = typer.Option(..., "--type", "-t", help="Type of request: 'model' or 'manifest'"), + request_id: int = typer.Argument(..., help="Request ID to explore") +): + """Explore a specific ModelRequest or ManifestUpdateRequest""" + effective_network, w3, account, console = ctx.obj.get_en_w3_account_console() + DINModelRegistry_Contract = ctx.obj.get_deployed_din_registry_contract() + + from dincli.services.cid_utils import get_cid_from_bytes32 + + if req_type.lower() == 'model': + try: + req = DINModelRegistry_Contract.functions.modelRequests(request_id).call() + console.print(f"[bold cyan]Model Request {request_id}:[/bold cyan]") + console.print(f" Requester: {req[0]}") + console.print(f" Is Open Source: {req[1]}") + + try: + manifest_cid = get_cid_from_bytes32(req[2].hex()) + except Exception: + manifest_cid = req[2].hex() + + console.print(f" Manifest CID: {manifest_cid}") + console.print(f" Task Coordinator: {req[3]}") + console.print(f" Task Auditor: {req[4]}") + console.print(f" Fee Paid: {w3.from_wei(req[5], 'ether')} ETH") + console.print(f" Processed: {req[6]}") + console.print(f" Approved: {req[7]}") + import datetime + console.print(f" Created At: {datetime.datetime.fromtimestamp(req[8])}") + except Exception as e: + console.print(f"[bold red]Failed to retrieve Model Request {request_id}. It may not exist.[/bold red]") + + elif req_type.lower() == 'manifest': + try: + req = DINModelRegistry_Contract.functions.manifestRequests(request_id).call() + console.print(f"[bold cyan]Manifest Update Request {request_id}:[/bold cyan]") + console.print(f" Model ID: {req[0]}") + + try: + new_manifest_cid = get_cid_from_bytes32(req[1].hex()) + except Exception: + new_manifest_cid = req[1].hex() + + console.print(f" New Manifest CID: {new_manifest_cid}") + console.print(f" Requester: {req[2]}") + console.print(f" Fee Paid: {w3.from_wei(req[3], 'ether')} ETH") + console.print(f" Processed: {req[4]}") + console.print(f" Approved: {req[5]}") + except Exception as e: + console.print(f"[bold red]Failed to retrieve Manifest Update Request {request_id}. It may not exist.[/bold red]") else: - console.print("[bold red] X Failed to update proprietary fee[/bold red]") + console.print("[bold red]Invalid request type. Must be 'model' or 'manifest'.[/bold red]") + raise typer.Exit(1) + diff --git a/dincli/main.py b/dincli/main.py index b26230c..411d295 100644 --- a/dincli/main.py +++ b/dincli/main.py @@ -6,7 +6,7 @@ from dincli.cli.client import app as client_app from dincli.cli.context import DinContext from dincli.cli.core import GlobalOptionsGroup -# from dincli.cli.dindao import app as dindao_app +from dincli.cli.dindao import app as dindao_app from dincli.cli.ipfs import app as ipfs_app from dincli.cli.modelowner import app as model_owner_app # Import role-specific subcommands @@ -21,7 +21,7 @@ # Add subcommands for roles app.add_typer(system_app, name="system") -# app.add_typer(dindao_app, name="dindao") +app.add_typer(dindao_app, name="dindao") app.add_typer(model_owner_app, name="model-owner") app.add_typer(aggregators_app, name="aggregator") app.add_typer(auditor_app, name="auditor") diff --git a/dist/dincli-0.1.0-py3-none-any.whl b/dist/dincli-0.1.0-py3-none-any.whl index df07e7178996a640ddb750b4433f9652d015f5ed..cea72a754adc03b35759080c9745747465386307 100644 GIT binary patch delta 8744 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 delta 6624 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 literal 71282 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�%Y9%82y1|Y9AaXkrj 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

mReoL2f^^!&hUBWf; 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 Date: Sun, 17 May 2026 17:42:15 +0000 Subject: [PATCH 02/13] Rename ReadMe.md to README.md and added contribution section --- ReadMe.md => README.md | 9 +++++++++ 1 file changed, 9 insertions(+) rename ReadMe.md => README.md (79%) diff --git a/ReadMe.md b/README.md similarity index 79% rename from ReadMe.md rename to README.md index fd5d2b4..df5377b 100644 --- a/ReadMe.md +++ b/README.md @@ -39,6 +39,15 @@ To configure the IPFS API, please see [dincli Documentation](Documentation/setup --- +## Contributing + +We use the `develop` branch for open-source contributions. Please review the contribution guide before opening a pull request: +https://github.com/InfiniteZeroFoundation/DevNet/blob/develop/Developer/CONTRIBUTING.md + +If you want a place to start, check the [Good first issues](https://github.com/InfiniteZeroFoundation/DevNet/blob/develop/Developer/GOOD_FIRST_ISSUES.md). + +--- + ## Learn More - [White Paper](https://github.com/InfiniteZeroFoundation/White-Paper) From d7f28a8336eb37a078262f96a179f6cb04b94b9c Mon Sep 17 00:00:00 2001 From: "Dr. Abraham Nash" <61595494+abrahamnash@users.noreply.github.com> Date: Sun, 17 May 2026 16:29:40 -0400 Subject: [PATCH 03/13] Update README.md --- README.md | 78 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index df5377b..a731386 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,81 @@ # InfiniteZero Network — DevNet -> **Open infrastructure for training AI as a public good.** +

-InfiniteZero is building the commons for AI — the way the internet itself is a public good. Millions of devices contribute quietly to shared AI models, with raw data never leaving the user's device. Only anonymised, encrypted patterns join the network. +![Ethereum](https://img.shields.io/badge/Built%20on-Ethereum-3C3C3D?style=flat&logo=ethereum&logoColor=white) +![License](https://img.shields.io/badge/License-Open%20Source-brightgreen?style=flat) +![Status](https://img.shields.io/badge/Status-Active-blue?style=flat) +![Oxford](https://img.shields.io/badge/Founded%20at-University%20of%20Oxford-002147?style=flat) + +
+ +
+ +> **The internet gave everyone a voice.** +> **Open-source gave everyone a tool.** +> **AI is still waiting.** +> +> InfiniteZero is building the commons that changes that. + +--- + +## What is this? + +InfiniteZero is open infrastructure for a global AI commons — the way the internet itself is a public good. Millions of devices contribute quietly to shared AI models, with raw data never leaving the user's device. Only anonymised, encrypted patterns join the network. This repository contains the DevNet setup and tooling for developers and validators building on the InfiniteZero Network protocol. > Built on Ethereum. Governed by the community. Models belong to the commons. +The DevNet is live. Validator nodes are running 24/7 from Japan to Canada. This is not a simulation — it's real infrastructure, and it needs builders. + --- ## Overview -This project consists of multiple components: - -- **CLI**: `dincli` is a command line interface for interacting with the InfiniteZero Network -- **Hardhat / Foundry (Optional)**: Used for smart contract compilation and development -- **IPFS Provider**: Handles uploading and fetching of network artifacts +- **CLI**: `dincli` — command line interface for interacting with the InfiniteZero Network +- **Hardhat / Foundry (Optional)**: Smart contract compilation and development +- **IPFS Provider**: Handles uploading and fetching of network artifacts --- -## Validator Setup +## Quickstart -For step-by-step guidance on how to register as an aggregator (validator), please refer to the documentation: -https://github.com/InfiniteZeroFoundation/DevNet/blob/main/Documentation/GettingStarted.md +New here? Start with the validator setup guide: + +[Getting Started →](https://github.com/InfiniteZeroFoundation/DevNet/blob/main/Documentation/GettingStarted.md) --- -## How to Install dincli +## Install dincli -To install `dincli`, please see [dincli Documentation](Documentation/setup.md). +Full installation guide: [dincli Documentation](Documentation/setup.md) --- -## Configure IPFS API +## Configure IPFS -To configure the IPFS API, please see [dincli Documentation](Documentation/setup.md). +Full configuration guide: [dincli Documentation](Documentation/setup.md) --- ## Contributing -We use the `develop` branch for open-source contributions. Please review the contribution guide before opening a pull request: -https://github.com/InfiniteZeroFoundation/DevNet/blob/develop/Developer/CONTRIBUTING.md +The network is open and experimental. We use the `develop` branch for contributions — and we mean it when we say no contribution is too small. Documentation, testing, tooling, smart contracts, privacy mechanisms — all of it matters. + +[Contribution Guide →](https://github.com/InfiniteZeroFoundation/DevNet/blob/develop/Developer/CONTRIBUTING.md) -If you want a place to start, check the [Good first issues](https://github.com/InfiniteZeroFoundation/DevNet/blob/develop/Developer/GOOD_FIRST_ISSUES.md). +Not sure where to start? We've got you. + +[Good first issues →](https://github.com/InfiniteZeroFoundation/DevNet/blob/develop/Developer/GOOD_FIRST_ISSUES.md) + +--- + +## Why contribute here? + +Most AI infrastructure is being built behind closed doors, by a handful of companies, for profit. InfiniteZero is the alternative — a live, open, trustless network where the models trained belong to everyone who helped build them. + +If you care about privacy-preserving ML, decentralised systems, or just building something that genuinely matters — this is a good place to spend your time. --- @@ -52,5 +83,14 @@ If you want a place to start, check the [Good first issues](https://github.com/I - [White Paper](https://github.com/InfiniteZeroFoundation/White-Paper) - [Documentation](https://github.com/InfiniteZeroFoundation/DevNet/blob/main/Documentation/GettingStarted.md) -- [API Reference](#) -- [SDK](#) +- [Support the network on Giveth](https://giveth.io/project/infinitezero-network) +- [Say hello](mailto:abrahamnash@protonmail.com) + +--- + +
+ +*InfiniteZero Foundation — open AI infrastructure, built by everyone, for everyone.* +*Open protocol · Community governed · © 2026* + +
From adc1f7dd5140680097b858c7c21ecbb5cd6087af Mon Sep 17 00:00:00 2001 From: "Dr. Abraham Nash" <61595494+abrahamnash@users.noreply.github.com> Date: Sun, 17 May 2026 16:34:46 -0400 Subject: [PATCH 04/13] Update README.md --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a731386..d7f4e5e 100644 --- a/README.md +++ b/README.md @@ -47,15 +47,9 @@ New here? Start with the validator setup guide: --- -## Install dincli +## Setup -Full installation guide: [dincli Documentation](Documentation/setup.md) - ---- - -## Configure IPFS - -Full configuration guide: [dincli Documentation](Documentation/setup.md) +Full installation and IPFS configuration guide: [dincli Documentation](Documentation/setup.md) --- From db17f1db1eb3b528c1bde863cc8cb8d4e53351b8 Mon Sep 17 00:00:00 2001 From: "Dr. Abraham Nash" <61595494+abrahamnash@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:58:58 -0400 Subject: [PATCH 05/13] Update GettingStarted.md From e83c589444b3090dc410f5f3716a3b1dc7ae73fc Mon Sep 17 00:00:00 2001 From: "Dr. Abraham Nash" <61595494+abrahamnash@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:11:08 -0400 Subject: [PATCH 06/13] Create FUNDING.json --- FUNDING.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 FUNDING.json diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 0000000..bfc35a5 --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0xe516723D5fE6B6fe1fD8Da8753eBEf6c8f4904fD" + } + } +} From b3204d35085083cfdd97fd5b77b3975f33ba709f Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 21:29:55 +0100 Subject: [PATCH 07/13] docs(din-dao): add DIN DAO architecture and staged rollout design doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces Documentation/technical/din-dao/README.md as the primary design specification for progressive DAO governance of the DIN platform. Covers: four-stage rollout (multisig → timelock → token-vote governor → guardian) mapped to devnet/testnet milestones; category threshold table; locked-DIN voting power decision; Safe-vs-own-multisig tradeoff; two- timelock delay model (24 h / 48 h); CLI command mapping for dincli dindao; integration points for PR #13, P3-5.x, slashing spec, and indexer; and resolution of all open design questions from decentralized-governance.md. No contracts yet — design-doc-first per issue sequencing guidance. --- Documentation/technical/din-dao/README.md | 332 ++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 Documentation/technical/din-dao/README.md diff --git a/Documentation/technical/din-dao/README.md b/Documentation/technical/din-dao/README.md new file mode 100644 index 0000000..5ec2289 --- /dev/null +++ b/Documentation/technical/din-dao/README.md @@ -0,0 +1,332 @@ +# DIN DAO — Architecture & Staged Rollout + +> **Spec status:** Initial design. Decisions recorded here supersede open questions in +> `Developer/issues/decentralized-governance.md`. +> **Branch:** `feat/din-dao` +> **Roadmap ref:** P3-5.1 · ROADMAP Discussion §1 + +--- + +## 1. Purpose + +The DIN platform is currently governed by a single DIN-Representative admin key that +holds `owner()` on `DinCoordinator`, `DinValidatorStake`, and `DINModelRegistry`, and +will hold ownership of the `ProxyAdmin` created in PR #13. This document specifies how +that authority is progressively transferred to a DAO-governed contract stack. + +DIN does **not** need live DAO governance today. The activation schedule is: + +| Network milestone | DAO stage active | +|---------------------|---------------------| +| Devnet 1.0 | None (admin key) | +| Devnet 2.0 | Stage A shadow-only | +| Devnet 3.0 | Stage B live | +| Testnet 1.0 | Stage C live | +| Testnet 2.0 / audit | Stage D live | + +Contracts ship behind `feat/din-dao` now so the architecture can be reviewed and +hardened before any network milestone requires them. + +--- + +## 2. Stage Overview + +``` +Stage A ────────────────────────────────────────────────────────────── + DinMultisig + N-of-M typed-proposal multisig. Replaces single admin key. + Shadow-operates in devnet 2.0 (no on-chain authority yet). + +Stage B ────────────────────────────────────────────────────────────── + DinTimelockShort (24 h delay) ← parameter + operational proposals + DinTimelockLong (48 h delay) ← treasury + upgrade proposals + DinMultisig → PROPOSER_ROLE + CANCELLER_ROLE on both timelocks. + Timelocks receive owner() of platform contracts + ProxyAdmin. + Live at devnet 3.0. + +Stage C ────────────────────────────────────────────────────────────── + DinGovernanceStaking (stDIN) ← lock DIN → non-transferable votes + DinGovernor ← OZ Governor executing through timelocks + Token-vote governance replaces multisig as primary proposer. + DinMultisig retains CANCELLER_ROLE as safety brake. + Live at testnet 1.0. + +Stage D ────────────────────────────────────────────────────────────── + DinGuardian ← narrow, time-limited emergency authority + Guardian role held by DinMultisig; revocable by DinGovernor. + Live alongside Stage C at testnet 1.0 → 2.0. +``` + +--- + +## 3. Contract Architecture + +### 3.1 Deployed contracts (foundry/src/dao/) + +| Contract | Stage | Description | +|------------------------|-------|-----------------------------------------------------------| +| `DinMultisig.sol` | A | N-of-M multisig with per-category typed proposals | +| `DinTimelock.sol` | B | Thin OZ `TimelockController` wrapper; deployed twice | +| `DinGovernanceStaking` | C | Lock DIN → stDIN voting power (non-transferable, IVotes) | +| `DinGovernor.sol` | C | OZ Governor executing through DinTimelockLong/Short | +| `DinGuardian.sol` | D | Narrow emergency authority with ratification window | + +### 3.2 Governance flow at each stage + +**Stage A / B** (multisig-led) +``` +Signer → DinMultisig.propose() + → confirm × threshold + → DinMultisig.execute() + → DinTimelockShort.schedule() or DinTimelockLong.schedule() + → (delay elapses) + → DinTimelock.execute() + → platform contract call +``` + +**Stage C / D** (token-vote-led, multisig as safety brake) +``` +Proposer → DinGovernor.propose() + → voting delay + → token holders vote + → DinGovernor.queue() → DinTimelockShort or DinTimelockLong + → (delay elapses) + → DinGovernor.execute() + → platform contract call + +Emergency path (Stage D only): +DinMultisig → DinGuardian.performAction() + → (ratification window: 7 days) + → DinGovernor ratifies or action is reversed +``` + +### 3.3 Role wiring + +``` + ┌──────────────────────────┐ + │ DinMultisig │ + │ (3-of-5 for v1) │ + └────────┬─────────┬────────┘ + │ │ + PROPOSER_ROLE │ │ CANCELLER_ROLE (retained at Stage C+) + ▼ ▼ + ┌──────────────────────────────────┐ + │ DinTimelockShort DinTimelockLong│ + │ (24 h delay) (48 h delay) │ + └──────────┬───────────────┬────────┘ + │ │ + owner() ───┴───────────────┘ + │ + ┌───────────────┼──────────────────┐ + ▼ ▼ ▼ + DinCoordinator DinValidatorStake DINModelRegistry ProxyAdmin +``` + +At Stage C, `DinGovernor` is added as `PROPOSER_ROLE` on both timelocks alongside +`DinMultisig`. The multisig retains `CANCELLER_ROLE` to veto a proposal before its +delay elapses. + +--- + +## 4. Proposal Categories & Thresholds + +### 4.1 DinMultisig (Stage A / B) + +| Category | Examples | Confirmations required | +|---------------|----------------------------------------------------|------------------------| +| `Parameter` | fee updates, dinPerEth, stake thresholds | 2-of-N | +| `Operational` | slasher auth, model disable/enable, blacklisting | 2-of-N | +| `Treasury` | ETH/fee withdrawal, grant disbursement | 3-of-N | +| `Upgrade` | platform contract upgrade, ProxyAdmin transfer | N-of-N (unanimous) | + +N = number of signers at deployment (v1: 3 signers). + +### 4.2 DinGovernor (Stage C) + +| Category | Quorum | Threshold | Timelock | +|---------------|--------|------------------|----------------| +| `Parameter` | 4 % | Simple majority | Short (24 h) | +| `Operational` | 10 % | Simple majority | Short (24 h) | +| `Treasury` | 15 % | Supermajority ≥66%| Long (48 h) | +| `Upgrade` | 20 % | Supermajority ≥66%| Long (48 h) | + +DinGovernor is single-track (one Governor instance). Routing to the correct timelock +is handled by the proposer encoding the timelock address as the execution target of the +queued operation. This avoids two Governor instances while preserving the two-delay +model. + +All governance parameters (`votingDelay`, `votingPeriod`, `proposalThreshold`, +`quorumNumerator`) are `GovernorSettings`-settable by governance itself after +deployment. + +--- + +## 5. Voting Power Model + +**Decision: locked DIN (stDIN).** + +`DinGovernanceStaking` locks DIN and mints a non-transferable ERC-20 (stDIN) that +implements OZ `IVotes` via `ERC20Votes`. Voting power is checkpointed at the block the +proposal becomes active. + +### Rationale + +- Free-balance DIN voting is rejected because DIN is a freely transferable ERC-20; + snapshot-at-proposal without a locking commitment invites flash-loan and last-minute + purchase attacks. +- Validator stake in `DinValidatorStake` is **not** counted toward governance power in + v1. Hybrid stake-voting would create a conflict of interest when governance votes on + slashing conditions or blacklisting appeals. Validators can participate by separately + locking DIN in `DinGovernanceStaking`. +- Quadratic voting is explicitly excluded. Non-binding signaling off-chain only, if + ever introduced. + +### Delegation + +Full OZ `ERC20Votes` delegation is supported. An address must call `delegate(self)` to +activate its own voting power; undelegated stDIN does not count toward quorum. This +matches the OZ Governor standard expectation. + +### Ossification path + +Because `DinGovernanceStaking` mints the stDIN that governs DinGovernor, governance can +vote to lock the upgrade path permanently by: +1. Removing `UPGRADER_ROLE` from all addresses on the ProxyAdmin (if UUPS) or + renouncing ProxyAdmin ownership. +2. Removing the `PROPOSER_ROLE` from itself on the timelocks. + +This is a one-proposal endgame — no migration required. + +--- + +## 6. Own Multisig vs. Gnosis Safe + +### 6.1 Build our own (chosen for devnet) + +`DinMultisig` is an in-tree, in-scope contract written to DIN's conventions (solc +0.8.28, custom errors, NatSpec, events-first). This gives: + +- full auditability alongside the contracts it governs +- typed proposals with per-category thresholds not available out of the box in Safe +- no external deploy dependency for devnet self-containment + +### 6.2 Gnosis Safe for mainnet treasury + +For mainnet, the multisig signers should migrate to a Gnosis Safe for: +- battle-tested security and audits +- existing tooling (Safe UI, SDK, transaction service) +- hardware-wallet signing support + +The migration path: `DinMultisig` transfers `PROPOSER_ROLE` and `CANCELLER_ROLE` on +both timelocks to a Safe address via a normal `Upgrade`-category proposal. No platform +contracts change. + +### 6.3 Recommendation + +Run `DinMultisig` for devnet 2.0 through testnet 1.0. Begin Safe migration planning +during testnet 2.0 alongside Stage D activation and the audit preparation window. + +--- + +## 7. Timelock Delay Rationale + +Two delay tiers rather than per-operation salt conventions because: +- the timelock delay is set globally on `TimelockController` and cannot be overridden + per-operation without a custom extension +- two independent `DinTimelock` instances each with a fixed delay is simpler to reason + about, deploy, and audit than a single instance with conditional delay logic + +Short delay (24 h): bounded parameter changes whose blast radius is limited. A wrong +fee can be corrected in the next proposal. + +Long delay (48 h): treasury withdrawals and upgrade proposals. On Optimism Sepolia the +execution gas is nearly free; 48 h gives DIN participants (validators, clients, +aggregators) adequate time to observe a pending upgrade and exit if they disagree. A +compromised multisig cannot swap in malicious logic silently — there is a 48 h window +to observe and cancel. + +--- + +## 8. CLI Command Mapping (dincli dindao) + +Current `dincli dindao ...` commands map to DAO flows as follows. CLI implementation +is out of scope for this issue but must be documented here so DIN-SDK (#20) and +DIN-daemon (#21) can plan for it. + +| Current dincli command | DAO equivalent (Stage B+) | +|--------------------------------------------|-----------------------------------------| +| `dindao approve-model-request` | `propose` + `confirm` + `execute` via multisig → timelock | +| `dindao reject-model-request` | Same | +| `dindao approve-manifest-update` | Same | +| `dindao add-slasher-contract` | `Operational` proposal through multisig | +| `dindao remove-slasher-contract` | Same | +| `dindao withdraw-eth` | `Treasury` proposal through multisig | +| `dindao update-din-per-eth` | `Parameter` proposal through multisig | +| `dindao blacklist-validator` | `Operational` or guardian emergency | +| `dindao unblacklist-validator` | Full governance proposal only (Stage C) | +| Contract upgrade (no current CLI command) | `Upgrade` proposal; unanimous multisig | + +--- + +## 9. Integration Points + +### 9.1 PR #13 (upgradeable contracts) + +The `ProxyAdmin` created in PR #13 is the handle for upgrade governance. Its ownership +transfers to `DinTimelockLong` at Stage B. No changes to the four platform contracts are +required for Stages A or B; Stage D requires platform contracts to expose an +`onlyGuardian` path for emergency protective actions (tracked separately). + +### 9.2 P3-5.x (token utility, emission, fees) + +`DinCoordinator.updateDinPerEth`, `DINModelRegistry` fee setters, and future emission +rate setters (P3-5.2) are `onlyOwner` parameter setters. They are the primary `Parameter` +and `Treasury` category targets for DAO proposals. No signature changes are required if +they follow the plain-setter convention; any setter that bundles side effects beyond the +parameter update should be flagged before Stage B activation. + +### 9.3 Slashing / dispute resolution (P3-4.x) + +The blacklisting and confiscation-of-blacklisted-stake path (described in +`Developer/issues/staking-mechanism.md`) must execute via accepted governance proposal +in the long run. `DinGuardian` covers the emergency-blacklist case at Stage D; the +confiscation path goes through `DinGovernor` as a `Treasury` category proposal. + +### 9.4 Indexer (P4-IDX) + +All DAO contracts emit events on every state transition. No on-chain enumeration helpers +are added — proposal lists, voter histories, quorum checks, and guardian action +dashboards are indexer responsibilities. Refer to `Developer/issues/indexer.md`. + +--- + +## 10. Open Design Questions — Resolved + +Answers to the open questions from `Developer/issues/decentralized-governance.md`: + +| Question | Decision | +|----------|----------| +| Model approval / manifest approval — direct DAO vote or elected committee? | Direct multisig proposal (Stage B). Move to Governor (Stage C) with `Operational` threshold. No elected committee in v1. | +| Validator blacklisting and unblacklisting — same threshold? | Different. Blacklisting is `Operational` (lower threshold; also available as guardian emergency action). Unblacklisting is `Operational` via full governance only — no emergency path. Restorative actions always go through normal governance. | +| Treasury and upgrade proposals — supermajority? | Yes. ≥66 % of participating votes, higher quorum (15–20 %). See §4.2. | +| Voting power: staked DIN, locked DIN, or hybrid? | Locked DIN (stDIN). Validator stake not counted in v1. See §5. | +| Quadratic voting anywhere? | No. Non-binding signaling off-chain only, if ever introduced. | +| One chamber or separated by role? | One chamber for v1. Bicameral governance (validators vs. token holders) is a Phase 4 consideration per `decentralized-governance.md`. | + +--- + +## 11. Non-Goals (this issue) + +- Quadratic voting (on-chain or off-chain) +- Task-level governance — task contracts remain model-owner-driven +- On-chain proposal enumeration helpers (indexer responsibility) +- Mainnet treasury custody decisions (Safe migration documented but not committed) +- Cross-layer governance (L1 ↔ L2 bridge-routed upgrades) — out of scope for v1 + +--- + +## 12. Ownership-Transfer Runbook + +See `Documentation/dindao.md §5` (to be updated). The exact ordered steps to transfer +`owner()` of each platform contract to the appropriate timelock, with rollback notes, +will be added there once Stage B contracts are finalized and reviewed. From d12380ac44154b690e62a6add4db9c7f6f1e847d Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 21:40:11 +0100 Subject: [PATCH 08/13] =?UTF-8?q?feat(din-dao):=20Stage=20A=20=E2=80=94=20?= =?UTF-8?q?DinMultisig=20with=20typed=20proposals=20and=20per-category=20t?= =?UTF-8?q?hresholds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements DinMultisig.sol: an N-of-M multisig with four proposal categories (Parameter, Operational, Treasury, Upgrade), independent confirmation thresholds per category, and an explicit NotExist → Open → Executable → Executed / Cancelled state machine. Inspired by the typed-proposal pattern in DAOFLcode MultiSigC, modernised to solc 0.8.28 conventions: custom errors, full NatSpec, events on every state transition (indexer-first per Developer/issues/indexer.md). Also includes: - IDinMultisig.sol interface with ProposalCategory and ProposalState enums - DinMultisig.t.sol: 22 unit tests covering constructor validation, full propose/confirm/revoke/execute/cancel lifecycle, category threshold enforcement, and failure paths - foundry/.gitignore: unblock test/dao/ so DAO tests are tracked --- foundry/.gitignore | 2 + foundry/src/dao/DinMultisig.sol | 238 ++++++++++++++++++ foundry/src/dao/interfaces/IDinMultisig.sol | 148 ++++++++++++ foundry/test/dao/DinMultisig.t.sol | 255 ++++++++++++++++++++ 4 files changed, 643 insertions(+) create mode 100644 foundry/src/dao/DinMultisig.sol create mode 100644 foundry/src/dao/interfaces/IDinMultisig.sol create mode 100644 foundry/test/dao/DinMultisig.t.sol diff --git a/foundry/.gitignore b/foundry/.gitignore index 606ebfe..c8d0e94 100644 --- a/foundry/.gitignore +++ b/foundry/.gitignore @@ -17,4 +17,6 @@ docs/ lib/* node_modules/* test/* +!test/dao/ +!test/dao/** script/* diff --git a/foundry/src/dao/DinMultisig.sol b/foundry/src/dao/DinMultisig.sol new file mode 100644 index 0000000..120d30a --- /dev/null +++ b/foundry/src/dao/DinMultisig.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "./interfaces/IDinMultisig.sol"; + +// ───────────────────────────────────────────────────────────────────────────── +// Custom errors +// ───────────────────────────────────────────────────────────────────────────── + +/// @dev Caller is not an authorised signer on this multisig. +error MS_NotSigner(); +/// @dev The proposal is not in the Open state; confirmation cannot be added. +error MS_ProposalNotOpen(); +/// @dev The proposal is not in the Executable state; it cannot be dispatched. +error MS_ProposalNotExecutable(); +/// @dev The proposal is not in a cancellable state (Open or Executable). +error MS_ProposalNotCancellable(); +/// @dev This signer has already confirmed the proposal. +error MS_AlreadyConfirmed(); +/// @dev This signer has not confirmed the proposal; nothing to revoke. +error MS_NotConfirmed(); +/// @dev The supplied address is the zero address. +error MS_ZeroAddress(); +/// @dev The signer list must contain at least one address. +error MS_EmptySignerList(); +/// @dev The signer list contains a duplicate address. +error MS_DuplicateSigner(); +/// @dev A confirmation threshold must be at least 1 and at most the signer count. +error MS_InvalidThreshold(); +/// @dev The proposal ID does not correspond to any existing proposal. +error MS_InvalidProposalId(); +/// @dev The low-level call dispatched by execute() reverted. +error MS_ExecutionFailed(); + +// ───────────────────────────────────────────────────────────────────────────── +// DinMultisig +// ───────────────────────────────────────────────────────────────────────────── + +/// @title DIN Multisig +/// @notice N-of-M multisig with per-category typed proposals for DAO governance +/// of the DIN platform. Stage A of the progressive-decentralisation rollout. +/// @dev Proposal categories carry independent confirmation thresholds so that +/// high-risk actions (treasury withdrawals, contract upgrades) require broader +/// signer consensus than routine parameter changes. After Stage B is deployed, +/// this contract holds PROPOSER_ROLE and CANCELLER_ROLE on DinTimelock. +/// +/// Signers and per-category thresholds are immutable after construction. +/// To change signers, deploy a new DinMultisig and transfer roles on the +/// timelocks via an Upgrade-category proposal through the existing instance. +contract DinMultisig is IDinMultisig { + // ─── Storage ────────────────────────────────────────────────────────────── + + struct Proposal { + address target; + bytes data; + uint256 value; + ProposalCategory category; + ProposalState state; + uint256 confirmCount; + uint256 createdAt; + } + + address[] private _signers; + mapping(address => bool) private _isSigner; + mapping(ProposalCategory => uint256) private _threshold; + + uint256 private _proposalCount; + mapping(uint256 => Proposal) private _proposals; + mapping(uint256 => mapping(address => bool)) private _confirmed; + + // ─── Constructor ────────────────────────────────────────────────────────── + + /// @notice Deploy the multisig with a fixed signer set and per-category thresholds. + /// @dev Thresholds are supplied as a fixed-length array indexed by + /// `ProposalCategory`: [Parameter, Operational, Treasury, Upgrade]. + /// Each threshold must be in [1, signers.length]. + /// @param signers_ Ordered list of authorised signer addresses (no duplicates). + /// @param thresholds_ Confirmation counts required per category (4-element array). + constructor(address[] memory signers_, uint256[4] memory thresholds_) { + uint256 n = signers_.length; + if (n == 0) revert MS_EmptySignerList(); + + for (uint256 i; i < n; ++i) { + address s = signers_[i]; + if (s == address(0)) revert MS_ZeroAddress(); + if (_isSigner[s]) revert MS_DuplicateSigner(); + _isSigner[s] = true; + _signers.push(s); + } + + for (uint256 c; c < 4; ++c) { + uint256 t = thresholds_[c]; + if (t == 0 || t > n) revert MS_InvalidThreshold(); + _threshold[ProposalCategory(c)] = t; + } + } + + // ─── Modifiers ──────────────────────────────────────────────────────────── + + modifier onlySigner() { + if (!_isSigner[msg.sender]) revert MS_NotSigner(); + _; + } + + // ─── Core actions ───────────────────────────────────────────────────────── + + /// @inheritdoc IDinMultisig + function propose( + address target, + bytes calldata data, + uint256 value, + ProposalCategory category + ) external onlySigner returns (uint256 proposalId) { + if (target == address(0)) revert MS_ZeroAddress(); + + proposalId = _proposalCount++; + + Proposal storage p = _proposals[proposalId]; + p.target = target; + p.data = data; + p.value = value; + p.category = category; + p.state = ProposalState.Open; + p.createdAt = block.timestamp; + + emit ProposalCreated(proposalId, msg.sender, target, category); + } + + /// @inheritdoc IDinMultisig + function confirm(uint256 proposalId) external onlySigner { + Proposal storage p = _proposals[proposalId]; + if (p.state != ProposalState.Open) revert MS_ProposalNotOpen(); + if (_confirmed[proposalId][msg.sender]) revert MS_AlreadyConfirmed(); + + _confirmed[proposalId][msg.sender] = true; + uint256 count = ++p.confirmCount; + + emit ProposalConfirmed(proposalId, msg.sender, count); + + if (count >= _threshold[p.category]) { + p.state = ProposalState.Executable; + emit ProposalExecutable(proposalId); + } + } + + /// @inheritdoc IDinMultisig + function revoke(uint256 proposalId) external onlySigner { + Proposal storage p = _proposals[proposalId]; + if (p.state != ProposalState.Open && p.state != ProposalState.Executable) { + revert MS_ProposalNotCancellable(); + } + if (!_confirmed[proposalId][msg.sender]) revert MS_NotConfirmed(); + + _confirmed[proposalId][msg.sender] = false; + uint256 count = --p.confirmCount; + + emit ProposalRevoked(proposalId, msg.sender, count); + + if (p.state == ProposalState.Executable && count < _threshold[p.category]) { + p.state = ProposalState.Open; + } + } + + /// @inheritdoc IDinMultisig + function execute(uint256 proposalId) external payable onlySigner { + Proposal storage p = _proposals[proposalId]; + if (p.state != ProposalState.Executable) revert MS_ProposalNotExecutable(); + + p.state = ProposalState.Executed; + + (bool success, ) = p.target.call{value: p.value}(p.data); + if (!success) revert MS_ExecutionFailed(); + + emit ProposalExecuted(proposalId, msg.sender); + } + + /// @inheritdoc IDinMultisig + function cancel(uint256 proposalId) external onlySigner { + Proposal storage p = _proposals[proposalId]; + if (p.state != ProposalState.Open && p.state != ProposalState.Executable) { + revert MS_ProposalNotCancellable(); + } + + p.state = ProposalState.Cancelled; + emit ProposalCancelled(proposalId, msg.sender); + } + + // ─── Views ──────────────────────────────────────────────────────────────── + + /// @inheritdoc IDinMultisig + function getProposal(uint256 proposalId) + external + view + returns ( + address target, + bytes memory data, + uint256 value, + ProposalCategory category, + ProposalState state, + uint256 confirmCount, + uint256 createdAt + ) + { + if (proposalId >= _proposalCount) revert MS_InvalidProposalId(); + Proposal storage p = _proposals[proposalId]; + return (p.target, p.data, p.value, p.category, p.state, p.confirmCount, p.createdAt); + } + + /// @inheritdoc IDinMultisig + function signers() external view returns (address[] memory) { + return _signers; + } + + /// @inheritdoc IDinMultisig + function threshold(ProposalCategory category) external view returns (uint256) { + return _threshold[category]; + } + + /// @inheritdoc IDinMultisig + function isSigner(address account) external view returns (bool) { + return _isSigner[account]; + } + + /// @inheritdoc IDinMultisig + function hasConfirmed(uint256 proposalId, address signer) external view returns (bool) { + return _confirmed[proposalId][signer]; + } + + /// @inheritdoc IDinMultisig + function proposalCount() external view returns (uint256) { + return _proposalCount; + } + + // ─── ETH receiver ───────────────────────────────────────────────────────── + + /// @notice Allows the multisig to receive ETH (e.g. for value-bearing proposals). + receive() external payable {} +} diff --git a/foundry/src/dao/interfaces/IDinMultisig.sol b/foundry/src/dao/interfaces/IDinMultisig.sol new file mode 100644 index 0000000..e4705c0 --- /dev/null +++ b/foundry/src/dao/interfaces/IDinMultisig.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +/// @notice Risk category of a multisig proposal. +/// @dev Maps to per-category confirmation thresholds stored in DinMultisig. +enum ProposalCategory { + Parameter, // 0 — fee updates, rate changes, bounded numeric parameters + Operational, // 1 — slasher auth, model disable/enable, validator blacklisting + Treasury, // 2 — ETH or token withdrawals, grant disbursements + Upgrade // 3 — platform contract upgrades, ProxyAdmin transfer +} + +/// @notice Lifecycle state of a multisig proposal. +enum ProposalState { + NotExist, // 0 — proposal ID not yet allocated + Open, // 1 — created; collecting confirmations + Executable, // 2 — confirmation threshold reached; ready to execute + Executed, // 3 — call dispatched successfully + Cancelled // 4 — cancelled by any signer before execution +} + +/// @title IDinMultisig +/// @notice Interface for the DIN N-of-M multisig with typed, per-category proposals. +interface IDinMultisig { + // ─── Events ────────────────────────────────────────────────────────────── + + /// @notice Emitted when a new proposal is created. + /// @param proposalId Auto-incremented proposal identifier. + /// @param proposer Signer who submitted the proposal. + /// @param target Contract the proposal will call. + /// @param category Risk category governing the confirmation threshold. + event ProposalCreated( + uint256 indexed proposalId, + address indexed proposer, + address target, + ProposalCategory category + ); + + /// @notice Emitted when a signer confirms a proposal. + /// @param proposalId Identifier of the confirmed proposal. + /// @param signer Address that added its confirmation. + /// @param confirmCount Running total of confirmations after this one. + event ProposalConfirmed( + uint256 indexed proposalId, + address indexed signer, + uint256 confirmCount + ); + + /// @notice Emitted when a signer revokes a previously cast confirmation. + /// @param proposalId Identifier of the affected proposal. + /// @param signer Address that withdrew its confirmation. + /// @param confirmCount Running total of confirmations after the revocation. + event ProposalRevoked( + uint256 indexed proposalId, + address indexed signer, + uint256 confirmCount + ); + + /// @notice Emitted when a proposal transitions from Open to Executable. + /// @param proposalId Identifier of the proposal that reached its threshold. + event ProposalExecutable(uint256 indexed proposalId); + + /// @notice Emitted when an executable proposal is successfully dispatched. + /// @param proposalId Identifier of the executed proposal. + /// @param executor Address that triggered execution. + event ProposalExecuted(uint256 indexed proposalId, address indexed executor); + + /// @notice Emitted when a proposal is cancelled. + /// @param proposalId Identifier of the cancelled proposal. + /// @param canceller Signer who cancelled it. + event ProposalCancelled(uint256 indexed proposalId, address indexed canceller); + + // ─── Core actions ───────────────────────────────────────────────────────── + + /// @notice Submit a new proposal. + /// @param target Contract address to call upon execution. + /// @param data ABI-encoded calldata for the target call. + /// @param value ETH value to forward with the call. + /// @param category Risk category that determines the required confirmation count. + /// @return proposalId Auto-incremented identifier assigned to this proposal. + function propose( + address target, + bytes calldata data, + uint256 value, + ProposalCategory category + ) external returns (uint256 proposalId); + + /// @notice Add the caller's confirmation to an open proposal. + /// @dev Automatically transitions state to Executable when the threshold is met. + /// @param proposalId Identifier of the proposal to confirm. + function confirm(uint256 proposalId) external; + + /// @notice Withdraw the caller's confirmation from a proposal. + /// @dev If the proposal was Executable, it reverts to Open. + /// @param proposalId Identifier of the proposal to revoke confirmation for. + function revoke(uint256 proposalId) external; + + /// @notice Dispatch an executable proposal. + /// @param proposalId Identifier of the proposal to execute. + function execute(uint256 proposalId) external payable; + + /// @notice Cancel an open or executable proposal. + /// @param proposalId Identifier of the proposal to cancel. + function cancel(uint256 proposalId) external; + + // ─── Views ──────────────────────────────────────────────────────────────── + + /// @notice Returns the full details of a proposal. + /// @param proposalId Identifier to query. + /// @return target Contract to be called. + /// @return data Calldata for the target. + /// @return value ETH value attached to the call. + /// @return category Risk category of the proposal. + /// @return state Current lifecycle state. + /// @return confirmCount Number of confirmations currently held. + /// @return createdAt Block timestamp when the proposal was created. + function getProposal(uint256 proposalId) + external + view + returns ( + address target, + bytes memory data, + uint256 value, + ProposalCategory category, + ProposalState state, + uint256 confirmCount, + uint256 createdAt + ); + + /// @notice Returns the full signer list. + function signers() external view returns (address[] memory); + + /// @notice Returns the confirmation threshold for a given category. + /// @param category Category to query. + function threshold(ProposalCategory category) external view returns (uint256); + + /// @notice Returns whether the given address is an authorized signer. + /// @param account Address to check. + function isSigner(address account) external view returns (bool); + + /// @notice Returns whether a specific signer has confirmed a proposal. + /// @param proposalId Proposal to query. + /// @param signer Signer address to check. + function hasConfirmed(uint256 proposalId, address signer) external view returns (bool); + + /// @notice Returns the total number of proposals created (including cancelled/executed). + function proposalCount() external view returns (uint256); +} diff --git a/foundry/test/dao/DinMultisig.t.sol b/foundry/test/dao/DinMultisig.t.sol new file mode 100644 index 0000000..d148422 --- /dev/null +++ b/foundry/test/dao/DinMultisig.t.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; +import "../../src/dao/DinMultisig.sol"; +import "../../src/dao/interfaces/IDinMultisig.sol"; + +/// @dev Minimal call target used to verify proposal dispatch. +contract CallTarget { + uint256 public value; + + function setValue(uint256 v) external { + value = v; + } + + function revertAlways() external pure { + revert("always"); + } +} + +contract DinMultisigTest is Test { + DinMultisig internal ms; + CallTarget internal target; + + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + address internal carol = makeAddr("carol"); + address internal eve = makeAddr("eve"); // non-signer + + // Thresholds: Parameter=2, Operational=2, Treasury=3, Upgrade=3 + uint256[4] internal thresholds = [uint256(2), 2, 3, 3]; + + function setUp() public { + address[] memory s = new address[](3); + s[0] = alice; + s[1] = bob; + s[2] = carol; + ms = new DinMultisig(s, thresholds); + target = new CallTarget(); + } + + // ─── Constructor ────────────────────────────────────────────────────────── + + function test_Constructor_SetsSigners() public view { + assertTrue(ms.isSigner(alice)); + assertTrue(ms.isSigner(bob)); + assertTrue(ms.isSigner(carol)); + assertFalse(ms.isSigner(eve)); + } + + function test_Constructor_SetsThresholds() public view { + assertEq(ms.threshold(ProposalCategory.Parameter), 2); + assertEq(ms.threshold(ProposalCategory.Operational), 2); + assertEq(ms.threshold(ProposalCategory.Treasury), 3); + assertEq(ms.threshold(ProposalCategory.Upgrade), 3); + } + + function test_Constructor_RejectsEmptySigners() public { + address[] memory empty = new address[](0); + vm.expectRevert(MS_EmptySignerList.selector); + new DinMultisig(empty, thresholds); + } + + function test_Constructor_RejectsZeroAddress() public { + address[] memory s = new address[](1); + s[0] = address(0); + vm.expectRevert(MS_ZeroAddress.selector); + new DinMultisig(s, thresholds); + } + + function test_Constructor_RejectsDuplicateSigner() public { + address[] memory s = new address[](2); + s[0] = alice; + s[1] = alice; + vm.expectRevert(MS_DuplicateSigner.selector); + new DinMultisig(s, thresholds); + } + + function test_Constructor_RejectsZeroThreshold() public { + uint256[4] memory bad = [uint256(0), 1, 1, 1]; + address[] memory s = new address[](2); + s[0] = alice; s[1] = bob; + vm.expectRevert(MS_InvalidThreshold.selector); + new DinMultisig(s, bad); + } + + function test_Constructor_RejectsThresholdAboveSignerCount() public { + uint256[4] memory bad = [uint256(4), 1, 1, 1]; + address[] memory s = new address[](3); + s[0] = alice; s[1] = bob; s[2] = carol; + vm.expectRevert(MS_InvalidThreshold.selector); + new DinMultisig(s, bad); + } + + // ─── propose ────────────────────────────────────────────────────────────── + + function test_Propose_SucceedsForSigner() public { + bytes memory data = abi.encodeCall(CallTarget.setValue, (42)); + vm.prank(alice); + uint256 id = ms.propose(address(target), data, 0, ProposalCategory.Parameter); + assertEq(id, 0); + assertEq(ms.proposalCount(), 1); + + (, , , ProposalCategory cat, ProposalState state, , ) = ms.getProposal(id); + assertEq(uint8(cat), uint8(ProposalCategory.Parameter)); + assertEq(uint8(state), uint8(ProposalState.Open)); + } + + function test_Propose_RevertsForNonSigner() public { + vm.prank(eve); + vm.expectRevert(MS_NotSigner.selector); + ms.propose(address(target), "", 0, ProposalCategory.Parameter); + } + + function test_Propose_RevertsOnZeroTarget() public { + vm.prank(alice); + vm.expectRevert(MS_ZeroAddress.selector); + ms.propose(address(0), "", 0, ProposalCategory.Parameter); + } + + // ─── confirm / threshold transition ─────────────────────────────────────── + + function test_Confirm_IncreasesCount() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + + vm.prank(alice); + ms.confirm(id); + (, , , , , uint256 count, ) = ms.getProposal(id); + assertEq(count, 1); + } + + function test_Confirm_TransitionsToExecutable() public { + uint256 id = _createProposal(ProposalCategory.Parameter); // threshold=2 + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); + (, , , , ProposalState state, , ) = ms.getProposal(id); + assertEq(uint8(state), uint8(ProposalState.Executable)); + } + + function test_Confirm_RevertsOnNonOpen() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); // now Executable + vm.prank(carol); + vm.expectRevert(MS_ProposalNotOpen.selector); + ms.confirm(id); + } + + function test_Confirm_RevertsOnDuplicate() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(alice); + vm.expectRevert(MS_AlreadyConfirmed.selector); + ms.confirm(id); + } + + // ─── revoke ─────────────────────────────────────────────────────────────── + + function test_Revoke_DecreasesCount() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(alice); ms.revoke(id); + (, , , , , uint256 count, ) = ms.getProposal(id); + assertEq(count, 0); + } + + function test_Revoke_FromExecutableBackToOpen() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); // Executable + vm.prank(alice); ms.revoke(id); // drops below threshold → Open + (, , , , ProposalState state, , ) = ms.getProposal(id); + assertEq(uint8(state), uint8(ProposalState.Open)); + } + + function test_Revoke_RevertsIfNotConfirmed() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); + vm.expectRevert(MS_NotConfirmed.selector); + ms.revoke(id); + } + + // ─── execute ────────────────────────────────────────────────────────────── + + function test_Execute_DispatchesCall() public { + bytes memory data = abi.encodeCall(CallTarget.setValue, (99)); + vm.prank(alice); + uint256 id = ms.propose(address(target), data, 0, ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); + vm.prank(carol); ms.execute(id); + assertEq(target.value(), 99); + + (, , , , ProposalState state, , ) = ms.getProposal(id); + assertEq(uint8(state), uint8(ProposalState.Executed)); + } + + function test_Execute_RevertsIfNotExecutable() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); + vm.expectRevert(MS_ProposalNotExecutable.selector); + ms.execute(id); + } + + function test_Execute_RevertsOnFailedCall() public { + bytes memory data = abi.encodeCall(CallTarget.revertAlways, ()); + vm.prank(alice); + uint256 id = ms.propose(address(target), data, 0, ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); + vm.prank(carol); + vm.expectRevert(MS_ExecutionFailed.selector); + ms.execute(id); + } + + // ─── cancel ─────────────────────────────────────────────────────────────── + + function test_Cancel_TransitionsToCancelled() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); ms.cancel(id); + (, , , , ProposalState state, , ) = ms.getProposal(id); + assertEq(uint8(state), uint8(ProposalState.Cancelled)); + } + + function test_Cancel_RevertsAfterExecution() public { + uint256 id = _createProposal(ProposalCategory.Parameter); + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); + vm.prank(alice); ms.execute(id); + vm.prank(carol); + vm.expectRevert(MS_ProposalNotCancellable.selector); + ms.cancel(id); + } + + // ─── Category threshold enforcement ─────────────────────────────────────── + + function test_TreasuryRequiresThreeConfirms() public { + uint256 id = _createProposal(ProposalCategory.Treasury); // threshold=3 + vm.prank(alice); ms.confirm(id); + vm.prank(bob); ms.confirm(id); + (, , , , ProposalState state, , ) = ms.getProposal(id); + assertEq(uint8(state), uint8(ProposalState.Open)); // still open after 2 + vm.prank(carol); ms.confirm(id); + (, , , , state, , ) = ms.getProposal(id); + assertEq(uint8(state), uint8(ProposalState.Executable)); + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + function _createProposal(ProposalCategory cat) internal returns (uint256 id) { + bytes memory data = abi.encodeCall(CallTarget.setValue, (1)); + vm.prank(alice); + id = ms.propose(address(target), data, 0, cat); + } +} From ab3f3c66d8394460253d5dcf079ce6a53215a882 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 21:41:33 +0100 Subject: [PATCH 09/13] =?UTF-8?q?feat(din-dao):=20Stage=20B=20=E2=80=94=20?= =?UTF-8?q?DinTimelock=20wrapping=20OZ=20TimelockController?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thin DinTimelock.sol instantiation of OZ TimelockController, deployed as two instances at Stage B activation (devnet 3.0): DinTimelockShort — 24 h delay for Parameter and Operational proposals DinTimelockLong — 48 h delay for Treasury and Upgrade proposals At deployment both instances receive DinMultisig as PROPOSER_ROLE and CANCELLER_ROLE; EXECUTOR_ROLE is open (address(0)) so any address may execute once the delay has elapsed. DEFAULT_ADMIN_ROLE is renounced by passing address(0) as admin_ — no post-deploy admin authority exists. DinTimelock.t.sol covers: role assignment, open-executor grant, admin renouncement, short and long delay enforcement, schedule-and-execute lifecycle, pre-delay execution rejection, and canceller path. --- foundry/src/dao/DinTimelock.sol | 52 ++++++++++ foundry/test/dao/DinTimelock.t.sol | 150 +++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 foundry/src/dao/DinTimelock.sol create mode 100644 foundry/test/dao/DinTimelock.t.sol diff --git a/foundry/src/dao/DinTimelock.sol b/foundry/src/dao/DinTimelock.sol new file mode 100644 index 0000000..8d2fdca --- /dev/null +++ b/foundry/src/dao/DinTimelock.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts/governance/TimelockController.sol"; + +// ───────────────────────────────────────────────────────────────────────────── +// DinTimelock +// ───────────────────────────────────────────────────────────────────────────── + +/// @title DIN Timelock +/// @notice Thin instantiation of OZ TimelockController for DIN DAO governance. +/// @dev Deployed as two independent instances with different minimum delays: +/// +/// - DinTimelockShort (24 h) — Parameter and Operational proposals. +/// Bounded parameter changes whose blast radius is limited; a wrong +/// fee can be corrected in the next proposal cycle. +/// +/// - DinTimelockLong (48 h) — Treasury and Upgrade proposals. +/// Higher blast radius; 48 h gives DIN participants (validators, +/// clients, aggregators) adequate time to observe a pending upgrade +/// and exit if they disagree. +/// +/// Both instances should be deployed with: +/// - proposers = [DinMultisig] +/// - executors = [address(0)] (open execution — anyone can execute +/// once the delay has elapsed) +/// - admin = address(0) (renounce DEFAULT_ADMIN_ROLE at +/// construction; no post-deploy admin) +/// +/// At Stage C, DinGovernor is added as an additional proposer on both +/// instances via a governance proposal routed through the existing multisig. +/// +/// The governance contracts themselves (Governor, Timelock) are +/// non-upgradeable — the contract guarding upgrades must not itself be +/// upgradeable or the trust problem shifts up a level. +contract DinTimelock is TimelockController { + /// @notice Deploy a DIN Timelock instance. + /// @param minDelay_ Minimum seconds between scheduling and execution. + /// Pass 86400 (24 h) for the short instance or + /// 172800 (48 h) for the long instance. + /// @param proposers_ Addresses granted PROPOSER_ROLE (initially DinMultisig). + /// @param executors_ Addresses granted EXECUTOR_ROLE. Pass [address(0)] for + /// open execution (anyone may execute after the delay). + /// @param admin_ Address granted DEFAULT_ADMIN_ROLE. Pass address(0) to + /// renounce all admin authority at construction. + constructor( + uint256 minDelay_, + address[] memory proposers_, + address[] memory executors_, + address admin_ + ) TimelockController(minDelay_, proposers_, executors_, admin_) {} +} diff --git a/foundry/test/dao/DinTimelock.t.sol b/foundry/test/dao/DinTimelock.t.sol new file mode 100644 index 0000000..fcd8e9d --- /dev/null +++ b/foundry/test/dao/DinTimelock.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; +import "../../src/dao/DinTimelock.sol"; + +/// @dev Minimal target whose state changes verify that scheduled calls execute. +contract TimelockTarget { + uint256 public value; + + function setValue(uint256 v) external { + value = v; + } +} + +contract DinTimelockTest is Test { + DinTimelock internal tlShort; // 24 h + DinTimelock internal tlLong; // 48 h + TimelockTarget internal target; + + address internal proposer = makeAddr("proposer"); + address internal executor = makeAddr("executor"); + + uint256 internal constant SHORT_DELAY = 1 days; + uint256 internal constant LONG_DELAY = 2 days; + + bytes32 internal constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); + bytes32 internal constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + bytes32 internal constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); + bytes32 internal constant ADMIN_ROLE = 0x00; + + function setUp() public { + address[] memory proposers_ = new address[](1); + proposers_[0] = proposer; + + address[] memory executors_ = new address[](1); + executors_[0] = address(0); // open execution + + tlShort = new DinTimelock(SHORT_DELAY, proposers_, executors_, address(0)); + tlLong = new DinTimelock(LONG_DELAY, proposers_, executors_, address(0)); + + target = new TimelockTarget(); + } + + // ─── Role assignments ───────────────────────────────────────────────────── + + function test_ShortDelay_ProposerHasRole() public view { + assertTrue(tlShort.hasRole(PROPOSER_ROLE, proposer)); + } + + function test_LongDelay_ProposerHasRole() public view { + assertTrue(tlLong.hasRole(PROPOSER_ROLE, proposer)); + } + + function test_OpenExecutorGranted() public view { + // address(0) as executor grants EXECUTOR_ROLE to everyone + assertTrue(tlShort.hasRole(EXECUTOR_ROLE, address(0))); + } + + function test_AdminRoleRenounced() public view { + // Passing address(0) as admin_ causes TimelockController to skip + // granting DEFAULT_ADMIN_ROLE to any external address. + assertFalse(tlShort.hasRole(ADMIN_ROLE, address(this))); + } + + function test_MinDelayShort() public view { + assertEq(tlShort.getMinDelay(), SHORT_DELAY); + } + + function test_MinDelayLong() public view { + assertEq(tlLong.getMinDelay(), LONG_DELAY); + } + + // ─── Propose → wait → execute (short timelock) ─────────────────────────── + + function test_ShortTimelock_ScheduleAndExecute() public { + bytes memory data = abi.encodeCall(TimelockTarget.setValue, (7)); + bytes32 salt = bytes32(0); + bytes32 id = tlShort.hashOperation(address(target), 0, data, bytes32(0), salt); + + vm.prank(proposer); + tlShort.schedule(address(target), 0, data, bytes32(0), salt, SHORT_DELAY); + + assertTrue(tlShort.isOperationPending(id)); + + vm.warp(block.timestamp + SHORT_DELAY); + + // Anyone can execute once the delay has elapsed (open EXECUTOR_ROLE) + tlShort.execute(address(target), 0, data, bytes32(0), salt); + + assertTrue(tlShort.isOperationDone(id)); + assertEq(target.value(), 7); + } + + function test_ShortTimelock_CannotExecuteBeforeDelay() public { + bytes memory data = abi.encodeCall(TimelockTarget.setValue, (1)); + bytes32 salt = bytes32(0); + + vm.prank(proposer); + tlShort.schedule(address(target), 0, data, bytes32(0), salt, SHORT_DELAY); + + vm.warp(block.timestamp + SHORT_DELAY - 1); + vm.expectRevert(); + tlShort.execute(address(target), 0, data, bytes32(0), salt); + } + + // ─── Cancel (canceller role) ────────────────────────────────────────────── + + function test_ProposerCanCancel() public { + bytes memory data = abi.encodeCall(TimelockTarget.setValue, (1)); + bytes32 salt = bytes32(0); + bytes32 id = tlShort.hashOperation(address(target), 0, data, bytes32(0), salt); + + vm.prank(proposer); + tlShort.schedule(address(target), 0, data, bytes32(0), salt, SHORT_DELAY); + + // Proposer also holds CANCELLER_ROLE by OZ default + vm.prank(proposer); + tlShort.cancel(id); + + assertFalse(tlShort.isOperationPending(id)); + } + + // ─── Long timelock delay enforcement ───────────────────────────────────── + + function test_LongTimelock_DelayEnforced() public { + bytes memory data = abi.encodeCall(TimelockTarget.setValue, (99)); + bytes32 salt = bytes32(0); + + vm.prank(proposer); + tlLong.schedule(address(target), 0, data, bytes32(0), salt, LONG_DELAY); + + // Warp only to short delay — should still be too early for the long lock + vm.warp(block.timestamp + SHORT_DELAY); + vm.expectRevert(); + tlLong.execute(address(target), 0, data, bytes32(0), salt); + } + + function test_LongTimelock_ExecutesAfterFullDelay() public { + bytes memory data = abi.encodeCall(TimelockTarget.setValue, (42)); + bytes32 salt = bytes32(0); + + vm.prank(proposer); + tlLong.schedule(address(target), 0, data, bytes32(0), salt, LONG_DELAY); + + vm.warp(block.timestamp + LONG_DELAY); + tlLong.execute(address(target), 0, data, bytes32(0), salt); + assertEq(target.value(), 42); + } +} From 8db7ae64ce9b1f1755cee1de883fcb795323c85c Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 21:47:05 +0100 Subject: [PATCH 10/13] =?UTF-8?q?feat(din-dao):=20Stage=20C=20=E2=80=94=20?= =?UTF-8?q?DinGovernanceStaking=20(stDIN)=20and=20DinGovernor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DinGovernanceStaking.sol (stDIN): - Lock DIN → mint 1:1 non-transferable stDIN implementing OZ IVotes via ERC20Votes - transfer() and transferFrom() unconditionally revert (GS_NonTransferable) - Block-number clock (CLOCK_MODE = blocknumber) aligned with DinGovernor - Accounts must self-delegate or delegate to activate voting power; undelegated stDIN does not count toward Governor quorum - Validator stake in DinValidatorStake excluded from governance power in v1 DinGovernor.sol: - Composes OZ Governor + GovernorSettings + GovernorCountingSimple + GovernorVotes + GovernorVotesQuorumFraction + GovernorTimelockControl - All governance parameters (votingDelay, votingPeriod, proposalThreshold, quorumNumerator) are constructor-configurable and governance-settable post-deploy - Executes through DinTimelock; proposal routing to short/long delay via encoded timelock target in proposal targets array Tests: - DinGovernanceStaking.t.sol: lock/unlock mechanics, non-transferability, delegation + checkpoint invariants, totalLocked == DIN balance invariant - DinGovernor.t.sol: full lifecycle (propose → vote → queue → execute), quorum-not-met defeat, below-threshold proposal rejection --- foundry/src/dao/DinGovernanceStaking.sol | 129 +++++++++++ foundry/src/dao/DinGovernor.sol | 173 +++++++++++++++ .../dao/interfaces/IDinGovernanceStaking.sol | 43 ++++ foundry/test/dao/DinGovernanceStaking.t.sol | 166 ++++++++++++++ foundry/test/dao/DinGovernor.t.sol | 204 ++++++++++++++++++ 5 files changed, 715 insertions(+) create mode 100644 foundry/src/dao/DinGovernanceStaking.sol create mode 100644 foundry/src/dao/DinGovernor.sol create mode 100644 foundry/src/dao/interfaces/IDinGovernanceStaking.sol create mode 100644 foundry/test/dao/DinGovernanceStaking.t.sol create mode 100644 foundry/test/dao/DinGovernor.t.sol diff --git a/foundry/src/dao/DinGovernanceStaking.sol b/foundry/src/dao/DinGovernanceStaking.sol new file mode 100644 index 0000000..3176d7f --- /dev/null +++ b/foundry/src/dao/DinGovernanceStaking.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import "./interfaces/IDinGovernanceStaking.sol"; + +// ───────────────────────────────────────────────────────────────────────────── +// Custom errors +// ───────────────────────────────────────────────────────────────────────────── + +/// @dev Lock amount must be greater than zero. +error GS_ZeroAmount(); +/// @dev Caller holds insufficient stDIN to unlock the requested amount. +error GS_InsufficientBalance(); +/// @dev stDIN is non-transferable; direct transfers are not permitted. +error GS_NonTransferable(); + +// ───────────────────────────────────────────────────────────────────────────── +// DinGovernanceStaking +// ───────────────────────────────────────────────────────────────────────────── + +/// @title DIN Governance Staking (stDIN) +/// @notice Lock DIN tokens to receive non-transferable, checkpointed voting power +/// for use with DinGovernor. Implements OZ IVotes via ERC20Votes. +/// @dev Voting power is based on locked DIN, not free balances or validator +/// stake, enforcing the two standing decisions from the governance spec: +/// no free-balance voting, no raw quadratic voting. +/// +/// stDIN is 1:1 with locked DIN but non-transferable — `transfer` and +/// `transferFrom` always revert. Accounts must self-delegate or delegate +/// to another address to activate voting power; undelegated stDIN does +/// not count toward Governor quorum. +/// +/// Validator stake in DinValidatorStake is explicitly excluded from +/// governance power in v1 to avoid conflicts of interest when governance +/// votes on slashing conditions or blacklisting appeals. +contract DinGovernanceStaking is ERC20, ERC20Votes, ReentrancyGuard, IDinGovernanceStaking { + using SafeERC20 for IERC20; + + // ─── Immutables ─────────────────────────────────────────────────────────── + + IERC20 private immutable _dinToken; + + // ─── Constructor ────────────────────────────────────────────────────────── + + /// @notice Deploy stDIN bound to a specific DIN token address. + /// @param dinToken_ Address of the DIN ERC-20 token to accept as collateral. + constructor(address dinToken_) + ERC20("Staked DIN", "stDIN") + EIP712("Staked DIN", "1") + { + require(dinToken_ != address(0)); + _dinToken = IERC20(dinToken_); + } + + // ─── Core actions ───────────────────────────────────────────────────────── + + /// @inheritdoc IDinGovernanceStaking + function lock(uint256 amount) external nonReentrant { + if (amount == 0) revert GS_ZeroAmount(); + _dinToken.safeTransferFrom(msg.sender, address(this), amount); + _mint(msg.sender, amount); + emit Locked(msg.sender, amount); + } + + /// @inheritdoc IDinGovernanceStaking + function unlock(uint256 amount) external nonReentrant { + if (amount == 0) revert GS_ZeroAmount(); + if (balanceOf(msg.sender) < amount) revert GS_InsufficientBalance(); + _burn(msg.sender, amount); + _dinToken.safeTransfer(msg.sender, amount); + emit Unlocked(msg.sender, amount); + } + + // ─── Non-transferability ────────────────────────────────────────────────── + + /// @dev Reverts unconditionally; stDIN cannot be moved between accounts. + function transfer(address, uint256) public pure override(ERC20, IERC20) returns (bool) { + revert GS_NonTransferable(); + } + + /// @dev Reverts unconditionally; stDIN cannot be moved between accounts. + function transferFrom(address, address, uint256) + public + pure + override(ERC20, IERC20) + returns (bool) + { + revert GS_NonTransferable(); + } + + // ─── Views ──────────────────────────────────────────────────────────────── + + /// @inheritdoc IDinGovernanceStaking + function dinToken() external view returns (address) { + return address(_dinToken); + } + + /// @inheritdoc IDinGovernanceStaking + function totalLocked() external view returns (uint256) { + return _dinToken.balanceOf(address(this)); + } + + // ─── ERC20Votes overrides ───────────────────────────────────────────────── + + /// @dev Required by ERC20Votes to compute voting units from balance. + function _update(address from, address to, uint256 value) + internal + override(ERC20, ERC20Votes) + { + super._update(from, to, value); + } + + /// @dev Required by ERC20Votes; returns the block number as the clock value. + function clock() public view override returns (uint48) { + return uint48(block.number); + } + + /// @dev Declares that this contract uses block numbers as the clock mode. + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public pure override returns (string memory) { + return "mode=blocknumber&from=default"; + } +} diff --git a/foundry/src/dao/DinGovernor.sol b/foundry/src/dao/DinGovernor.sol new file mode 100644 index 0000000..8a05fda --- /dev/null +++ b/foundry/src/dao/DinGovernor.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts/governance/Governor.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; + +// ───────────────────────────────────────────────────────────────────────────── +// DinGovernor +// ───────────────────────────────────────────────────────────────────────────── + +/// @title DIN Governor +/// @notice On-chain token-vote governor for the DIN DAO. Stage C of the +/// progressive-decentralisation rollout (testnet 1.0). +/// @dev Composition: +/// - GovernorSettings — votingDelay, votingPeriod, proposalThreshold +/// (all governance-settable post-deployment) +/// - GovernorCountingSimple — For / Against / Abstain counting +/// - GovernorVotes — voting power from DinGovernanceStaking (IVotes) +/// - GovernorVotesQuorumFraction — quorumNumerator as % of total supply +/// - GovernorTimelockControl — queues and executes through DinTimelock +/// +/// Proposal routing: the proposer encodes the target timelock address +/// in the proposal's targets array. Short-delay proposals target +/// DinTimelockShort; long-delay proposals target DinTimelockLong. +/// This avoids two Governor instances while preserving the two-delay model. +/// +/// DinMultisig retains CANCELLER_ROLE on both timelocks as a safety +/// brake — it can cancel a queued proposal before execution if the +/// technical team identifies a critical flaw. +/// +/// All governance parameters are adjustable by governance itself after +/// deployment. Initial values should be conservative and tightened once +/// token distribution matures. +contract DinGovernor is + Governor, + GovernorSettings, + GovernorCountingSimple, + GovernorVotes, + GovernorVotesQuorumFraction, + GovernorTimelockControl +{ + // ─── Constructor ────────────────────────────────────────────────────────── + + /// @notice Deploy DinGovernor bound to a stDIN voting token and a timelock. + /// @dev Pass DinTimelockLong as the primary timelock. Short-delay proposals + /// are routed by encoding DinTimelockShort as the call target in the + /// proposal's targets array rather than requiring a second Governor. + /// @param token_ DinGovernanceStaking (stDIN) address — the IVotes source. + /// @param timelock_ DinTimelockLong address for proposal execution. + /// @param initialVotingDelay Blocks before voting opens after a proposal is created. + /// Suggested: 7200 (≈ 24 h at 12-second blocks on L1; + /// use 1800 for OP-stack L2 with 2-second blocks). + /// @param initialVotingPeriod Blocks the voting window stays open. + /// Suggested: 50400 (≈ 7 days on L1; 151200 on OP-stack). + /// @param initialProposalThreshold Minimum voting power required to submit a proposal. + /// @param initialQuorumFraction Quorum as a percentage of total stDIN supply (e.g. 4 + /// for 4%). Upgrade and treasury proposals should be + /// submitted with a higher quorum target encoded in the + /// proposal description until per-category quorum is + /// implemented in a future Governor extension. + constructor( + IVotes token_, + TimelockController timelock_, + uint48 initialVotingDelay, + uint32 initialVotingPeriod, + uint256 initialProposalThreshold, + uint256 initialQuorumFraction + ) + Governor("DIN Governor") + GovernorSettings(initialVotingDelay, initialVotingPeriod, initialProposalThreshold) + GovernorVotes(token_) + GovernorVotesQuorumFraction(initialQuorumFraction) + GovernorTimelockControl(timelock_) + {} + + // ─── Required overrides ─────────────────────────────────────────────────── + + /// @inheritdoc IGovernor + function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) { + return super.votingDelay(); + } + + /// @inheritdoc IGovernor + function votingPeriod() public view override(Governor, GovernorSettings) returns (uint256) { + return super.votingPeriod(); + } + + /// @inheritdoc IGovernor + function quorum(uint256 blockNumber) + public + view + override(Governor, GovernorVotesQuorumFraction) + returns (uint256) + { + return super.quorum(blockNumber); + } + + /// @inheritdoc Governor + function proposalThreshold() + public + view + override(Governor, GovernorSettings) + returns (uint256) + { + return super.proposalThreshold(); + } + + /// @inheritdoc Governor + function state(uint256 proposalId) + public + view + override(Governor, GovernorTimelockControl) + returns (ProposalState) + { + return super.state(proposalId); + } + + /// @inheritdoc Governor + function proposalNeedsQueuing(uint256 proposalId) + public + view + override(Governor, GovernorTimelockControl) + returns (bool) + { + return super.proposalNeedsQueuing(proposalId); + } + + /// @inheritdoc Governor + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + /// @inheritdoc Governor + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + /// @inheritdoc Governor + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + /// @inheritdoc Governor + function _executor() + internal + view + override(Governor, GovernorTimelockControl) + returns (address) + { + return super._executor(); + } +} diff --git a/foundry/src/dao/interfaces/IDinGovernanceStaking.sol b/foundry/src/dao/interfaces/IDinGovernanceStaking.sol new file mode 100644 index 0000000..7428a98 --- /dev/null +++ b/foundry/src/dao/interfaces/IDinGovernanceStaking.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +/// @title IDinGovernanceStaking +/// @notice Interface for the DIN Governance Staking contract (stDIN). +/// Locking DIN mints non-transferable stDIN that carries checkpointed +/// voting power for DinGovernor. +interface IDinGovernanceStaking { + // ─── Events ────────────────────────────────────────────────────────────── + + /// @notice Emitted when a user locks DIN and receives stDIN. + /// @param account Address that performed the lock. + /// @param amount DIN amount locked (and stDIN minted). + event Locked(address indexed account, uint256 amount); + + /// @notice Emitted when a user unlocks DIN by burning stDIN. + /// @param account Address that performed the unlock. + /// @param amount DIN amount returned (and stDIN burned). + event Unlocked(address indexed account, uint256 amount); + + // ─── Core actions ───────────────────────────────────────────────────────── + + /// @notice Lock DIN and receive an equivalent amount of stDIN voting power. + /// @dev Caller must have approved this contract for at least `amount` DIN. + /// stDIN is non-transferable; voting power is immediately checkpointed. + /// Caller must call `delegate(self)` to activate their own votes. + /// @param amount DIN amount to lock. + function lock(uint256 amount) external; + + /// @notice Unlock DIN by burning an equivalent amount of stDIN. + /// @dev Voting power is immediately reduced. Any active delegation is + /// automatically adjusted by the underlying ERC20Votes checkpoint. + /// @param amount stDIN amount to burn (equal to DIN returned). + function unlock(uint256 amount) external; + + // ─── Views ──────────────────────────────────────────────────────────────── + + /// @notice Returns the address of the underlying DIN ERC-20 token. + function dinToken() external view returns (address); + + /// @notice Returns the total DIN currently held in this contract. + function totalLocked() external view returns (uint256); +} diff --git a/foundry/test/dao/DinGovernanceStaking.t.sol b/foundry/test/dao/DinGovernanceStaking.t.sol new file mode 100644 index 0000000..4a673a3 --- /dev/null +++ b/foundry/test/dao/DinGovernanceStaking.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; +import "../../src/dao/DinGovernanceStaking.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @dev Minimal ERC-20 used as the DIN stand-in in tests. +contract MockDIN is ERC20 { + constructor() ERC20("DIN Token", "DIN") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} + +contract DinGovernanceStakingTest is Test { + MockDIN internal din; + DinGovernanceStaking internal stDIN; + + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + + uint256 internal constant INITIAL = 1_000e18; + + function setUp() public { + din = new MockDIN(); + stDIN = new DinGovernanceStaking(address(din)); + + din.mint(alice, INITIAL); + din.mint(bob, INITIAL); + } + + // ─── Constructor ────────────────────────────────────────────────────────── + + function test_DinTokenAddress() public view { + assertEq(stDIN.dinToken(), address(din)); + } + + function test_NameAndSymbol() public view { + assertEq(stDIN.name(), "Staked DIN"); + assertEq(stDIN.symbol(), "stDIN"); + } + + // ─── lock ───────────────────────────────────────────────────────────────── + + function test_Lock_MintsStDIN() public { + vm.startPrank(alice); + din.approve(address(stDIN), 100e18); + stDIN.lock(100e18); + vm.stopPrank(); + + assertEq(stDIN.balanceOf(alice), 100e18); + assertEq(din.balanceOf(address(stDIN)), 100e18); + assertEq(stDIN.totalLocked(), 100e18); + } + + function test_Lock_RevertsOnZero() public { + vm.prank(alice); + vm.expectRevert(GS_ZeroAmount.selector); + stDIN.lock(0); + } + + // ─── unlock ─────────────────────────────────────────────────────────────── + + function test_Unlock_BurnsStDINAndReturnsDIN() public { + _lock(alice, 100e18); + + vm.prank(alice); + stDIN.unlock(60e18); + + assertEq(stDIN.balanceOf(alice), 40e18); + assertEq(din.balanceOf(alice), INITIAL - 100e18 + 60e18); + } + + function test_Unlock_RevertsOnInsufficientBalance() public { + _lock(alice, 50e18); + vm.prank(alice); + vm.expectRevert(GS_InsufficientBalance.selector); + stDIN.unlock(51e18); + } + + function test_Unlock_RevertsOnZero() public { + _lock(alice, 50e18); + vm.prank(alice); + vm.expectRevert(GS_ZeroAmount.selector); + stDIN.unlock(0); + } + + // ─── Non-transferability ────────────────────────────────────────────────── + + function test_Transfer_AlwaysReverts() public { + _lock(alice, 100e18); + vm.prank(alice); + vm.expectRevert(GS_NonTransferable.selector); + stDIN.transfer(bob, 10e18); + } + + function test_TransferFrom_AlwaysReverts() public { + _lock(alice, 100e18); + vm.prank(alice); + stDIN.approve(bob, 50e18); + vm.prank(bob); + vm.expectRevert(GS_NonTransferable.selector); + stDIN.transferFrom(alice, bob, 50e18); + } + + // ─── Delegation + voting power checkpoints ──────────────────────────────── + + function test_VotingPowerZeroWithoutDelegation() public { + _lock(alice, 100e18); + // No self-delegation → getVotes returns 0 + assertEq(stDIN.getVotes(alice), 0); + } + + function test_VotingPowerAfterSelfDelegate() public { + _lock(alice, 100e18); + vm.prank(alice); + stDIN.delegate(alice); + assertEq(stDIN.getVotes(alice), 100e18); + } + + function test_DelegationTransfersVotingPower() public { + _lock(alice, 100e18); + vm.prank(alice); + stDIN.delegate(bob); + assertEq(stDIN.getVotes(alice), 0); + assertEq(stDIN.getVotes(bob), 100e18); + } + + function test_PastVotesCheckpointed() public { + _lock(alice, 200e18); + vm.prank(alice); + stDIN.delegate(alice); + + uint256 snap = vm.getBlockNumber(); + vm.roll(snap + 1); + + // Unlock half — past votes at `snap` should still reflect 200e18 + vm.prank(alice); + stDIN.unlock(100e18); + + assertEq(stDIN.getPastVotes(alice, snap), 200e18); + assertEq(stDIN.getVotes(alice), 100e18); + } + + // ─── Invariant: totalLocked == DIN held by contract ─────────────────────── + + function test_TotalLockedMatchesDINBalance() public { + _lock(alice, 300e18); + _lock(bob, 150e18); + vm.prank(alice); stDIN.unlock(100e18); + + assertEq(stDIN.totalLocked(), din.balanceOf(address(stDIN))); + assertEq(stDIN.totalLocked(), 350e18); + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + function _lock(address account, uint256 amount) internal { + vm.startPrank(account); + din.approve(address(stDIN), amount); + stDIN.lock(amount); + vm.stopPrank(); + } +} diff --git a/foundry/test/dao/DinGovernor.t.sol b/foundry/test/dao/DinGovernor.t.sol new file mode 100644 index 0000000..6302d40 --- /dev/null +++ b/foundry/test/dao/DinGovernor.t.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; +import "../../src/dao/DinGovernor.sol"; +import "../../src/dao/DinGovernanceStaking.sol"; +import "../../src/dao/DinTimelock.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @dev Minimal ERC-20 used as the DIN stand-in in tests. +contract GovMockDIN is ERC20 { + constructor() ERC20("DIN Token", "DIN") {} + function mint(address to, uint256 amount) external { _mint(to, amount); } +} + +/// @dev Minimal call target whose state changes verify proposal execution. +contract GovTarget { + uint256 public value; + function setValue(uint256 v) external { value = v; } +} + +contract DinGovernorTest is Test { + GovMockDIN internal din; + DinGovernanceStaking internal stDIN; + DinTimelock internal timelock; + DinGovernor internal gov; + GovTarget internal target; + + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + address internal carol = makeAddr("carol"); + address internal deployer = makeAddr("deployer"); + + // Governor settings (block-based) + uint48 internal constant VOTING_DELAY = 1; // 1 block + uint32 internal constant VOTING_PERIOD = 50; // 50 blocks + uint256 internal constant PROPOSAL_THRESH = 1e18; // 1 stDIN + uint256 internal constant QUORUM_FRACTION = 4; // 4% + uint256 internal constant TIMELOCK_DELAY = 2 days; + + function setUp() public { + vm.startPrank(deployer); + + din = new GovMockDIN(); + stDIN = new DinGovernanceStaking(address(din)); + + // Build timelock with governor as proposer (wired below after deploy) + address[] memory noProposers = new address[](0); + address[] memory openExec = new address[](1); + openExec[0] = address(0); + + timelock = new DinTimelock(TIMELOCK_DELAY, noProposers, openExec, deployer); + + gov = new DinGovernor( + IVotes(address(stDIN)), + TimelockController(payable(address(timelock))), + VOTING_DELAY, + VOTING_PERIOD, + PROPOSAL_THRESH, + QUORUM_FRACTION + ); + + // Grant governor PROPOSER_ROLE and CANCELLER_ROLE on the timelock + timelock.grantRole(timelock.PROPOSER_ROLE(), address(gov)); + timelock.grantRole(timelock.CANCELLER_ROLE(), address(gov)); + // Renounce deployer's admin + timelock.renounceRole(timelock.DEFAULT_ADMIN_ROLE(), deployer); + + target = new GovTarget(); + + vm.stopPrank(); + + // Mint DIN and lock stDIN so voters have voting power + din.mint(alice, 1_000e18); + din.mint(bob, 1_000e18); + _lockAndDelegate(alice, 600e18, alice); + _lockAndDelegate(bob, 400e18, bob); + } + + // ─── Governor settings ──────────────────────────────────────────────────── + + function test_VotingDelay() public view { + assertEq(gov.votingDelay(), VOTING_DELAY); + } + + function test_VotingPeriod() public view { + assertEq(gov.votingPeriod(), VOTING_PERIOD); + } + + function test_ProposalThreshold() public view { + assertEq(gov.proposalThreshold(), PROPOSAL_THRESH); + } + + // ─── Full proposal lifecycle ─────────────────────────────────────────────── + + function test_ProposalLifecycle_ProposeVoteQueueExecute() public { + // Build a proposal to call target.setValue(77) through the timelock + bytes memory data = abi.encodeCall(GovTarget.setValue, (77)); + + address[] memory targets = new address[](1); + uint256[] memory values = new uint256[](1); + bytes[] memory calldatas = new bytes[](1); + targets[0] = address(target); + values[0] = 0; + calldatas[0] = data; + + string memory desc = "Set target value to 77"; + + // 1. Propose + vm.prank(alice); + uint256 proposalId = gov.propose(targets, values, calldatas, desc); + assertEq(uint8(gov.state(proposalId)), uint8(IGovernor.ProposalState.Pending)); + + // 2. Advance past voting delay + vm.roll(block.number + VOTING_DELAY + 1); + assertEq(uint8(gov.state(proposalId)), uint8(IGovernor.ProposalState.Active)); + + // 3. Vote + vm.prank(alice); gov.castVote(proposalId, 1); // For + vm.prank(bob); gov.castVote(proposalId, 1); // For + + // 4. Advance past voting period + vm.roll(block.number + VOTING_PERIOD + 1); + assertEq(uint8(gov.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded)); + + // 5. Queue into timelock + gov.queue(targets, values, calldatas, keccak256(bytes(desc))); + assertEq(uint8(gov.state(proposalId)), uint8(IGovernor.ProposalState.Queued)); + + // 6. Advance past timelock delay + vm.warp(block.timestamp + TIMELOCK_DELAY + 1); + + // 7. Execute + gov.execute(targets, values, calldatas, keccak256(bytes(desc))); + assertEq(uint8(gov.state(proposalId)), uint8(IGovernor.ProposalState.Executed)); + assertEq(target.value(), 77); + } + + // ─── Quorum ─────────────────────────────────────────────────────────────── + + function test_ProposalDefeatedWhenQuorumNotMet() public { + // Deploy fresh stDIN with tiny supply so quorum fraction fails + GovMockDIN smallDin = new GovMockDIN(); + DinGovernanceStaking smallStDIN = new DinGovernanceStaking(address(smallDin)); + smallDin.mint(alice, 10e18); + + vm.startPrank(alice); + smallDin.approve(address(smallStDIN), 10e18); + smallStDIN.lock(10e18); + smallStDIN.delegate(alice); + vm.stopPrank(); + + address[] memory noProposers = new address[](0); + address[] memory openExec = new address[](1); + openExec[0] = address(0); + DinTimelock tl2 = new DinTimelock(0, noProposers, openExec, address(this)); + + DinGovernor gov2 = new DinGovernor( + IVotes(address(smallStDIN)), + TimelockController(payable(address(tl2))), + 1, + 50, + 0, + 50 // 50% quorum — impossible to reach with 10e18 / 10e18 if alice votes only 1 For + ); + + tl2.grantRole(tl2.PROPOSER_ROLE(), address(gov2)); + + bytes memory data = abi.encodeCall(GovTarget.setValue, (1)); + address[] memory targets = new address[](1); targets[0] = address(target); + uint256[] memory vals = new uint256[](1); vals[0] = 0; + bytes[] memory cds = new bytes[](1); cds[0] = data; + + vm.prank(alice); + uint256 pid = gov2.propose(targets, vals, cds, "test"); + vm.roll(block.number + 2); + + // Alice votes Against — should remain below quorum for For side + vm.prank(alice); + gov2.castVote(pid, 0); // Against + + vm.roll(block.number + 51); + assertEq(uint8(gov2.state(pid)), uint8(IGovernor.ProposalState.Defeated)); + } + + // ─── Proposal below threshold ────────────────────────────────────────────── + + function test_ProposeRevertsWhenBelowThreshold() public { + address poorAccount = makeAddr("poor"); + vm.prank(poorAccount); + vm.expectRevert(); + gov.propose(new address[](1), new uint256[](1), new bytes[](1), "no power"); + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + function _lockAndDelegate(address account, uint256 amount, address delegatee) internal { + vm.startPrank(account); + din.approve(address(stDIN), amount); + stDIN.lock(amount); + stDIN.delegate(delegatee); + vm.stopPrank(); + } +} From 0a33800e9dabb32730ad4fd601b82f5bfb7033cd Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 21:50:31 +0100 Subject: [PATCH 11/13] =?UTF-8?q?feat(din-dao):=20Stage=20D=20=E2=80=94=20?= =?UTF-8?q?DinGuardian=20with=20ratification=20window=20and=20auto-reversa?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DinGuardian.sol provides narrow, time-limited emergency authority for the DIN platform. Guardian role held by DinMultisig; ratification authority held by DinGovernor. Each emergency action carries a reversal payload and a 7-day ratification window. Lifecycle: performAction() — guardian dispatches emergency call; window starts ratifyAction() — governor confirms action was appropriate; no reversal expireAction() — anyone triggers reversal if window elapses unratified Scope is intentionally restrictive: protective actions only (disable model, deauthorize slasher, pause dangerous flows). Restorative actions always go through normal governance — no emergency path for unblacklisting or re-enabling. Platform contracts will need a follow-up `onlyGuardian` integration task before Stage D can be activated on devnet 3.0 / testnet 1.0. DinGuardian.t.sol covers: construction, performAction dispatch and failure paths, ratifyAction guard, expireAction with auto-reversal, window enforcement, empty-reversal noop, setGovernor, action count, and invalid-ID rejection. --- foundry/src/dao/DinGuardian.sol | 211 ++++++++++++++++++ foundry/src/dao/interfaces/IDinGuardian.sol | 129 +++++++++++ foundry/test/dao/DinGuardian.t.sol | 225 ++++++++++++++++++++ 3 files changed, 565 insertions(+) create mode 100644 foundry/src/dao/DinGuardian.sol create mode 100644 foundry/src/dao/interfaces/IDinGuardian.sol create mode 100644 foundry/test/dao/DinGuardian.t.sol diff --git a/foundry/src/dao/DinGuardian.sol b/foundry/src/dao/DinGuardian.sol new file mode 100644 index 0000000..37e00ec --- /dev/null +++ b/foundry/src/dao/DinGuardian.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "./interfaces/IDinGuardian.sol"; + +// ───────────────────────────────────────────────────────────────────────────── +// Custom errors +// ───────────────────────────────────────────────────────────────────────────── + +/// @dev Caller is not the designated guardian (DinMultisig). +error DG_NotGuardian(); +/// @dev Caller is not the designated governor (DinGovernor). +error DG_NotGovernor(); +/// @dev The action ID does not correspond to any existing emergency action. +error DG_InvalidActionId(); +/// @dev The action is not in the Active state and cannot be ratified. +error DG_ActionNotActive(); +/// @dev The ratification window has not elapsed; the action cannot be expired yet. +error DG_WindowNotElapsed(); +/// @dev The action was already ratified or expired; it cannot be expired again. +error DG_ActionNotExpirable(); +/// @dev The emergency call dispatched to the target reverted. +error DG_ActionCallFailed(); +/// @dev The reversal call dispatched on expiry reverted. +error DG_ReversalCallFailed(); +/// @dev The supplied address is the zero address. +error DG_ZeroAddress(); + +// ───────────────────────────────────────────────────────────────────────────── +// DinGuardian +// ───────────────────────────────────────────────────────────────────────────── + +/// @title DIN Guardian +/// @notice Narrow-scope emergency authority for the DIN platform. Stage D of +/// the progressive-decentralisation rollout (testnet 1.0 alongside Stage C). +/// @dev Every emergency action has a fixed ratification window. If DinGovernor +/// does not ratify within the window, any address may call `expireAction` +/// to dispatch the stored reversal calldata and undo the original action. +/// +/// Scope is deliberately limited to protective operations: +/// - disable a malicious model in DINModelRegistry +/// - deauthorize a dangerous slasher contract in DinCoordinator +/// - trigger a guardian-gated pause on any platform contract that +/// exposes one +/// +/// Restorative actions (re-enable model, re-authorize slasher, +/// unblacklist validator) are never permitted as emergency actions — +/// they must always go through normal governance. +/// +/// Platform contracts must expose an `onlyGuardian` path for each +/// emergency-eligible function before Stage D activation. That integration +/// is tracked as a separate follow-up task. +/// +/// The guardian role is held by DinMultisig and is revocable by governance +/// via a standard Upgrade-category proposal that deploys a new guardian and +/// transfers the guardian address on each platform contract. +contract DinGuardian is IDinGuardian { + // ─── Storage ────────────────────────────────────────────────────────────── + + struct Action { + address target; + bytes data; + bytes reversalData; + string description; + ActionState state; + uint256 expiry; + } + + address private _guardian; + address private _governor; + uint256 private immutable _ratificationWindow; + uint256 private _actionCount; + mapping(uint256 => Action) private _actions; + + // ─── Constructor ────────────────────────────────────────────────────────── + + /// @notice Deploy the guardian with a fixed guardian address and ratification window. + /// @dev `governor_` may be address(0) at deployment if Stage C is not yet live. + /// Call `setGovernor` once DinGovernor is deployed. + /// @param guardian_ Address of DinMultisig (holds the guardian role). + /// @param governor_ Address of DinGovernor (permitted to ratify actions). + /// @param ratificationWindow_ Seconds after an action is performed during which + /// governance must ratify it. 7 days is the recommended + /// initial value — long enough for a governance vote to + /// complete, short enough to avoid indefinitely suspended + /// emergency state. + constructor(address guardian_, address governor_, uint256 ratificationWindow_) { + if (guardian_ == address(0)) revert DG_ZeroAddress(); + _guardian = guardian_; + _governor = governor_; + _ratificationWindow = ratificationWindow_; + } + + // ─── Modifiers ──────────────────────────────────────────────────────────── + + modifier onlyGuardian() { + if (msg.sender != _guardian) revert DG_NotGuardian(); + _; + } + + modifier onlyGovernor() { + if (msg.sender != _governor) revert DG_NotGovernor(); + _; + } + + // ─── Core actions ───────────────────────────────────────────────────────── + + /// @inheritdoc IDinGuardian + function performAction( + address target, + bytes calldata data, + bytes calldata reversalData, + string calldata description + ) external onlyGuardian returns (uint256 actionId) { + if (target == address(0)) revert DG_ZeroAddress(); + + actionId = _actionCount++; + uint256 expiry = block.timestamp + _ratificationWindow; + + _actions[actionId] = Action({ + target: target, + data: data, + reversalData: reversalData, + description: description, + state: ActionState.Active, + expiry: expiry + }); + + (bool ok, ) = target.call(data); + if (!ok) revert DG_ActionCallFailed(); + + emit EmergencyActionPerformed(actionId, target, data, expiry, description); + } + + /// @inheritdoc IDinGuardian + function ratifyAction(uint256 actionId) external onlyGovernor { + Action storage a = _actions[actionId]; + if (a.state == ActionState.NotExist) revert DG_InvalidActionId(); + if (a.state != ActionState.Active) revert DG_ActionNotActive(); + + a.state = ActionState.Ratified; + emit ActionRatified(actionId); + } + + /// @inheritdoc IDinGuardian + function expireAction(uint256 actionId) external { + Action storage a = _actions[actionId]; + if (a.state == ActionState.NotExist) revert DG_InvalidActionId(); + if (a.state != ActionState.Active) revert DG_ActionNotExpirable(); + if (block.timestamp < a.expiry) revert DG_WindowNotElapsed(); + + a.state = ActionState.Expired; + + bytes memory reversal = a.reversalData; + if (reversal.length > 0) { + (bool ok, ) = a.target.call(reversal); + if (!ok) revert DG_ReversalCallFailed(); + } + + emit ActionExpired(actionId, reversal); + } + + // ─── Admin ──────────────────────────────────────────────────────────────── + + /// @inheritdoc IDinGuardian + function setGovernor(address newGovernor) external onlyGuardian { + if (newGovernor == address(0)) revert DG_ZeroAddress(); + _governor = newGovernor; + emit GovernorUpdated(newGovernor); + } + + // ─── Views ──────────────────────────────────────────────────────────────── + + /// @inheritdoc IDinGuardian + function guardian() external view returns (address) { + return _guardian; + } + + /// @inheritdoc IDinGuardian + function governor() external view returns (address) { + return _governor; + } + + /// @inheritdoc IDinGuardian + function ratificationWindow() external view returns (uint256) { + return _ratificationWindow; + } + + /// @inheritdoc IDinGuardian + function getAction(uint256 actionId) + external + view + returns ( + address target, + bytes memory data, + bytes memory reversalData, + string memory description, + ActionState state, + uint256 expiry + ) + { + if (actionId >= _actionCount) revert DG_InvalidActionId(); + Action storage a = _actions[actionId]; + return (a.target, a.data, a.reversalData, a.description, a.state, a.expiry); + } + + /// @inheritdoc IDinGuardian + function actionCount() external view returns (uint256) { + return _actionCount; + } +} diff --git a/foundry/src/dao/interfaces/IDinGuardian.sol b/foundry/src/dao/interfaces/IDinGuardian.sol new file mode 100644 index 0000000..0db300d --- /dev/null +++ b/foundry/src/dao/interfaces/IDinGuardian.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +/// @notice Lifecycle state of an emergency action. +enum ActionState { + NotExist, // 0 — action ID not allocated + Active, // 1 — action dispatched; ratification window open + Ratified, // 2 — DinGovernor ratified the action; no reversal needed + Expired // 3 — window elapsed without ratification; action reversed +} + +/// @title IDinGuardian +/// @notice Interface for the DIN Guardian emergency authority contract. +/// Emergency actions are narrow, time-limited, and require governance +/// ratification within a fixed window or the action is reversed. +interface IDinGuardian { + // ─── Events ────────────────────────────────────────────────────────────── + + /// @notice Emitted when an emergency action is dispatched. + /// @param actionId Auto-incremented action identifier. + /// @param target Contract the emergency call was made against. + /// @param data ABI-encoded calldata that was dispatched. + /// @param expiry Timestamp by which governance must ratify the action. + /// @param description Human-readable description of the emergency. + event EmergencyActionPerformed( + uint256 indexed actionId, + address indexed target, + bytes data, + uint256 expiry, + string description + ); + + /// @notice Emitted when DinGovernor ratifies an active emergency action. + /// @param actionId Identifier of the ratified action. + event ActionRatified(uint256 indexed actionId); + + /// @notice Emitted when an action expires without ratification and is reversed. + /// @param actionId Identifier of the expired action. + /// @param reversalData Calldata dispatched to reverse the original action. + event ActionExpired(uint256 indexed actionId, bytes reversalData); + + /// @notice Emitted when the governor address is updated. + /// @param newGovernor Address of the new DinGovernor. + event GovernorUpdated(address indexed newGovernor); + + // ─── Core actions ───────────────────────────────────────────────────────── + + /// @notice Dispatch an emergency protective action. + /// @dev Only callable by the guardian (DinMultisig). The action is + /// recorded and a ratification window starts. If governance does + /// not ratify within the window, `expireAction` may be called to + /// reverse it. + /// + /// Scope is deliberately narrow — protective actions only: + /// - disable a model in DINModelRegistry + /// - deauthorize a slasher in DinCoordinator + /// - pause a dangerous flow (any contract exposing a guardian-gated pause) + /// + /// The reversal calldata must undo the original action exactly. + /// Platform contracts must expose an `onlyGuardian` path for each + /// emergency-eligible function (tracked as a follow-up integration task). + /// + /// @param target Contract to call. + /// @param data ABI-encoded calldata for the emergency call. + /// @param reversalData ABI-encoded calldata that undoes the action if not ratified. + /// @param description Human-readable description (emitted in event for indexers). + /// @return actionId Auto-incremented identifier assigned to this action. + function performAction( + address target, + bytes calldata data, + bytes calldata reversalData, + string calldata description + ) external returns (uint256 actionId); + + /// @notice Ratify an active emergency action, preventing reversal. + /// @dev Only callable by DinGovernor (after a successful governance vote + /// confirming the emergency action was appropriate). + /// @param actionId Identifier of the action to ratify. + function ratifyAction(uint256 actionId) external; + + /// @notice Reverse an emergency action that was not ratified within the window. + /// @dev Callable by anyone after the ratification window has elapsed. + /// Dispatches the stored reversal calldata against the original target. + /// @param actionId Identifier of the expired action to reverse. + function expireAction(uint256 actionId) external; + + // ─── Admin ──────────────────────────────────────────────────────────────── + + /// @notice Update the DinGovernor address that is permitted to ratify actions. + /// @dev Only callable by the guardian (DinMultisig). Should be called once + /// Stage C (DinGovernor) is deployed. Revocable by governance via a + /// standard governance proposal. + /// @param newGovernor Address of the deployed DinGovernor. + function setGovernor(address newGovernor) external; + + // ─── Views ──────────────────────────────────────────────────────────────── + + /// @notice Returns the guardian address (DinMultisig). + function guardian() external view returns (address); + + /// @notice Returns the DinGovernor address authorised to ratify actions. + function governor() external view returns (address); + + /// @notice Returns the ratification window in seconds. + function ratificationWindow() external view returns (uint256); + + /// @notice Returns the full details of an emergency action. + /// @param actionId Identifier to query. + /// @return target Contract the emergency call was dispatched to. + /// @return data Calldata that was dispatched. + /// @return reversalData Calldata to undo the action if not ratified. + /// @return description Human-readable description. + /// @return state Current action lifecycle state. + /// @return expiry Timestamp after which expireAction may be called. + function getAction(uint256 actionId) + external + view + returns ( + address target, + bytes memory data, + bytes memory reversalData, + string memory description, + ActionState state, + uint256 expiry + ); + + /// @notice Returns the total number of emergency actions created. + function actionCount() external view returns (uint256); +} diff --git a/foundry/test/dao/DinGuardian.t.sol b/foundry/test/dao/DinGuardian.t.sol new file mode 100644 index 0000000..fc5c564 --- /dev/null +++ b/foundry/test/dao/DinGuardian.t.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; +import "../../src/dao/DinGuardian.sol"; +import "../../src/dao/interfaces/IDinGuardian.sol"; + +/// @dev Minimal target that records guardian calls and supports reversal. +contract GuardianTarget { + bool public modelDisabled; + bool public slasherDeauthorized; + + function disableModel() external { + modelDisabled = true; + } + + function enableModel() external { + modelDisabled = false; + } + + function deauthorizeSlasher() external { + slasherDeauthorized = true; + } + + function reauthorizeSlasher() external { + slasherDeauthorized = false; + } + + function revertAlways() external pure { + revert("always fails"); + } +} + +contract DinGuardianTest is Test { + DinGuardian internal guardian; + GuardianTarget internal target; + + address internal multisig = makeAddr("multisig"); // guardian role + address internal governor = makeAddr("governor"); // ratifier role + address internal anyone = makeAddr("anyone"); + + uint256 internal constant WINDOW = 7 days; + + function setUp() public { + guardian = new DinGuardian(multisig, governor, WINDOW); + target = new GuardianTarget(); + } + + // ─── Constructor ────────────────────────────────────────────────────────── + + function test_GuardianAddress() public view { + assertEq(guardian.guardian(), multisig); + } + + function test_GovernorAddress() public view { + assertEq(guardian.governor(), governor); + } + + function test_RatificationWindow() public view { + assertEq(guardian.ratificationWindow(), WINDOW); + } + + function test_Constructor_RejectsZeroGuardian() public { + vm.expectRevert(DG_ZeroAddress.selector); + new DinGuardian(address(0), governor, WINDOW); + } + + // ─── performAction ──────────────────────────────────────────────────────── + + function test_PerformAction_DispatchesCall() public { + bytes memory data = abi.encodeCall(GuardianTarget.disableModel, ()); + bytes memory reversal = abi.encodeCall(GuardianTarget.enableModel, ()); + + vm.prank(multisig); + uint256 id = guardian.performAction(address(target), data, reversal, "disable model 1"); + + assertEq(id, 0); + assertTrue(target.modelDisabled()); + + ( , , , , ActionState state, uint256 expiry) = guardian.getAction(id); + assertEq(uint8(state), uint8(ActionState.Active)); + assertEq(expiry, block.timestamp + WINDOW); + } + + function test_PerformAction_RevertsForNonGuardian() public { + vm.prank(anyone); + vm.expectRevert(DG_NotGuardian.selector); + guardian.performAction(address(target), "", "", ""); + } + + function test_PerformAction_RevertsOnZeroTarget() public { + vm.prank(multisig); + vm.expectRevert(DG_ZeroAddress.selector); + guardian.performAction(address(0), "", "", ""); + } + + function test_PerformAction_RevertsWhenCallFails() public { + bytes memory data = abi.encodeCall(GuardianTarget.revertAlways, ()); + vm.prank(multisig); + vm.expectRevert(DG_ActionCallFailed.selector); + guardian.performAction(address(target), data, "", "bad call"); + } + + // ─── ratifyAction ───────────────────────────────────────────────────────── + + function test_RatifyAction_Succeeds() public { + uint256 id = _performDisableModel(); + + vm.prank(governor); + guardian.ratifyAction(id); + + ( , , , , ActionState state, ) = guardian.getAction(id); + assertEq(uint8(state), uint8(ActionState.Ratified)); + } + + function test_RatifyAction_RevertsForNonGovernor() public { + uint256 id = _performDisableModel(); + vm.prank(anyone); + vm.expectRevert(DG_NotGovernor.selector); + guardian.ratifyAction(id); + } + + function test_RatifyAction_RevertsIfNotActive() public { + uint256 id = _performDisableModel(); + vm.prank(governor); guardian.ratifyAction(id); // Ratified + vm.prank(governor); + vm.expectRevert(DG_ActionNotActive.selector); + guardian.ratifyAction(id); + } + + // ─── expireAction ───────────────────────────────────────────────────────── + + function test_ExpireAction_ReversesAfterWindow() public { + uint256 id = _performDisableModel(); + assertTrue(target.modelDisabled()); // action took effect + + vm.warp(block.timestamp + WINDOW + 1); + vm.prank(anyone); + guardian.expireAction(id); + + assertFalse(target.modelDisabled()); // reversal applied + + ( , , , , ActionState state, ) = guardian.getAction(id); + assertEq(uint8(state), uint8(ActionState.Expired)); + } + + function test_ExpireAction_RevertsBeforeWindow() public { + uint256 id = _performDisableModel(); + vm.warp(block.timestamp + WINDOW - 1); + vm.expectRevert(DG_WindowNotElapsed.selector); + guardian.expireAction(id); + } + + function test_ExpireAction_RevertsIfRatified() public { + uint256 id = _performDisableModel(); + vm.prank(governor); guardian.ratifyAction(id); + vm.warp(block.timestamp + WINDOW + 1); + vm.expectRevert(DG_ActionNotExpirable.selector); + guardian.expireAction(id); + } + + function test_ExpireAction_NoReversalDataIsNoop() public { + bytes memory data = abi.encodeCall(GuardianTarget.disableModel, ()); + vm.prank(multisig); + uint256 id = guardian.performAction(address(target), data, "", "no reversal"); + + vm.warp(block.timestamp + WINDOW + 1); + guardian.expireAction(id); // should not revert even with empty reversal + + ( , , , , ActionState state, ) = guardian.getAction(id); + assertEq(uint8(state), uint8(ActionState.Expired)); + } + + // ─── setGovernor ────────────────────────────────────────────────────────── + + function test_SetGovernor_UpdatesAddress() public { + address newGov = makeAddr("newGov"); + vm.prank(multisig); + guardian.setGovernor(newGov); + assertEq(guardian.governor(), newGov); + } + + function test_SetGovernor_RevertsForNonGuardian() public { + vm.prank(anyone); + vm.expectRevert(DG_NotGuardian.selector); + guardian.setGovernor(makeAddr("x")); + } + + function test_SetGovernor_RevertsOnZeroAddress() public { + vm.prank(multisig); + vm.expectRevert(DG_ZeroAddress.selector); + guardian.setGovernor(address(0)); + } + + // ─── Action count ───────────────────────────────────────────────────────── + + function test_ActionCountIncrements() public { + _performDisableModel(); + _performDeauthorizeSlasher(); + assertEq(guardian.actionCount(), 2); + } + + // ─── Invalid action ID ──────────────────────────────────────────────────── + + function test_GetAction_RevertsOnInvalidId() public { + vm.expectRevert(DG_InvalidActionId.selector); + guardian.getAction(999); + } + + // ─── Helpers ────────────────────────────────────────────────────────────── + + function _performDisableModel() internal returns (uint256 id) { + bytes memory data = abi.encodeCall(GuardianTarget.disableModel, ()); + bytes memory reversal = abi.encodeCall(GuardianTarget.enableModel, ()); + vm.prank(multisig); + id = guardian.performAction(address(target), data, reversal, "disable model"); + } + + function _performDeauthorizeSlasher() internal returns (uint256 id) { + bytes memory data = abi.encodeCall(GuardianTarget.deauthorizeSlasher, ()); + bytes memory reversal = abi.encodeCall(GuardianTarget.reauthorizeSlasher, ()); + vm.prank(multisig); + id = guardian.performAction(address(target), data, reversal, "deauthorize slasher"); + } +} From 3ad985afcdd8904f4ab83afbc98a3c40819e90fe Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 22:01:42 +0100 Subject: [PATCH 12/13] docs(din-dao): add ownership-transfer runbook and record governance decisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation/dindao.md §5a — Step-by-step runbook for transferring owner() of each platform contract (DinCoordinator, DinToken, DinValidatorStake, DINModelRegistry) and ProxyAdmin to DinTimelockLong at Stage B activation (devnet 3.0). Includes verification commands, rollback notes, and prerequisite checklist. Rehearsal on Optimism Sepolia devnet required before any testnet execution. Developer/issues/decentralized-governance.md — Append Decisions Made section recording all open design questions resolved during feat/din-dao: voting power model (locked DIN stDIN), quadratic voting rejection, UUPS proxy migration plan, multisig choice, timelock delays, blacklist/unblacklist asymmetry, supermajority thresholds, single-chamber governance, and stage activation schedule. --- Developer/issues/decentralized-governance.md | 698 +++++++++++++++++++ Documentation/dindao.md | 125 ++++ 2 files changed, 823 insertions(+) create mode 100644 Developer/issues/decentralized-governance.md diff --git a/Developer/issues/decentralized-governance.md b/Developer/issues/decentralized-governance.md new file mode 100644 index 0000000..3dce06c --- /dev/null +++ b/Developer/issues/decentralized-governance.md @@ -0,0 +1,698 @@ +# Decentralized Governance + +## Summary + +This issue covers how DIN should move from an admin-owned platform to decentralized governance through one or more DAOs. + +Today, important platform actions are still controlled by: + +- `daoAdmin` in `DINModelRegistry` +- `owner()` on platform-level contracts such as `DinCoordinator` +- direct owner or coordinator-controlled flows around staking, slasher management, fees, and registry decisions + +That is acceptable for an early devnet, but it is not the right long-term governance model for production. + +The broader goal is to replace centralized control over platform-level actions with a DAO-driven proposal, voting, and execution system. + +This should cover actions such as: + +- fee updates +- model approval and rejection policy +- manifest update approval policy +- slasher authorization +- validator blacklisting and unblacklisting +- treasury withdrawals +- exchange-rate or token-policy changes if those remain governable +- future platform-contract upgrades + +## Why This Matters + +DIN is not just a set of task contracts. It is a protocol with shared rules, shared incentives, shared security assumptions, and shared state. + +If those rules are controlled by a single admin key or a small operator set forever, then: + +- governance becomes centralized even if execution is decentralized +- participants must trust operators to act fairly +- economic policy can be changed unilaterally +- blacklist and slashing authority can be abused +- upgrade authority becomes a centralization and security risk + +A DAO does not remove all governance risk, but it can make authority: + +- transparent +- rule-bound +- auditable +- contestable + +## Current State + +The current platform contracts already expose governance-sensitive actions. + +Examples include: + +### `DinCoordinator.sol` + +- withdraw ETH treasury balance +- add slasher contracts +- remove slasher contracts +- update validator stake contract reference +- update DIN-per-ETH rate + +### `DinValidatorStake.sol` + +- blacklist and unblacklist validators through direct owner authority +- control slasher authorization indirectly via the coordinator + +Important note: + +- there is currently a blacklist path +- there is now a matching unblacklist path in the contract +- both are currently centralized under the stake contract owner and should later move under DAO governance by ownership transfer or equivalent governance control + +If DIN wants governance around validator rehabilitation or appeal, that should be added deliberately rather than assumed. + +### `DINModelRegistry.sol` + +- approve model registration requests +- reject model registration requests +- approve manifest update requests +- reject manifest update requests +- disable models +- enable models +- set registration and manifest-update fees +- withdraw accumulated fees +- transfer DAO admin authority + +## Governance Scope + +The cleanest long-term direction is: + +- platform-level governance should be DAO-controlled +- task-level operations should remain more local and task-owner-driven unless there is a strong protocol reason to centralize them + +For this document, the scope is the platform layer. + +At minimum, decentralized governance should eventually cover everything currently done by: + +- `daoAdmin` in `DINModelRegistry` +- `owner()` of `DinCoordinator` +- any owner-like authority used for future platform contract upgrades + +## Governance Domains + +DIN governance should distinguish between different classes of decisions instead of putting everything through one identical process. + +### 1. Parameter Governance + +Examples: + +- open-source registration fee +- proprietary registration fee +- manifest update fees +- DIN-per-ETH rate if retained +- future staking thresholds +- future unbonding periods + +These are structured numeric or config changes. + +### 2. Policy Governance + +Examples: + +- approve or reject a change in slashing rules +- approve new registry rules +- define who can be a slasher +- define blacklist and appeal policy +- decide whether certain platform modules are upgradeable + +These are broader rule changes, not just parameter tweaks. + +### 3. Operational Governance + +Examples: + +- approve model requests in the current registry design +- approve manifest updates in the current registry design +- disable or re-enable a model +- blacklist or unblacklist a validator +- authorize or deauthorize a slasher contract + +These are case-by-case decisions about live protocol state. + +### 4. Treasury Governance + +Examples: + +- withdraw accumulated fees +- fund grants +- fund audits +- fund protocol development +- route treasury assets + +### 5. Upgrade Governance + +Examples: + +- approve platform contract upgrades +- migrate to new implementations +- update proxy admins or timelock executors + +This is the highest-risk governance domain and should usually have the strongest safeguards. + +## Should DIN Use Voting? + +Yes. Platform-level governance should ultimately go through voting rather than unilateral admin action. + +But not every action needs the same vote type, quorum, delay, or execution path. + +The important design question is not only "should there be voting?" It is: + +- who votes +- with what voting power +- for which proposal classes +- with what thresholds +- with what execution delay + +## Should DIN Use Quadratic Voting? + +Quadratic voting is attractive in theory because it reduces the dominance of very large holders relative to linear token voting. + +But DIN should be careful here. + +### Why quadratic voting is attractive + +- gives smaller holders more influence per token +- may better reflect intensity of preference +- can reduce simple whale dominance + +### Why raw on-chain quadratic voting is dangerous + +- transferable tokens make Sybil splitting easy +- one large holder can split balances across many wallets +- flash-loan and temporary-balance attacks become harder to reason about +- implementation complexity increases +- the economics can become unclear and gameable + +### Recommendation + +DIN should **not** start with raw quadratic voting on freely transferable DIN balances. + +A better near-term governance default is: + +- binding governance based on **snapshot voting power from staked or locked DIN** +- optional delegation +- quorum and proposal thresholds +- timelocked execution + +If the protocol wants quadratic behavior later, it should only be considered after DIN has: + +- clear identity or anti-Sybil assumptions +- locked or non-transferable voting power +- tested governance demand for that complexity + +A reasonable compromise is: + +- use token-weighted voting for binding governance +- optionally use quadratic or reputation-weighted **signaling** for non-binding sentiment + +## Basis Of Voting Power + +The most natural basis is DIN-based governance power, but not raw wallet balances. + +Recommended direction: + +- voting power should come from **staked DIN**, **locked DIN**, or **vote-escrowed DIN** +- voting power should be measured at a snapshot block +- delegated voting should be supported + +This is better than free-balance voting because it: + +- makes governance participation more intentional +- reduces short-term manipulation +- aligns governance with long-term protocol commitment + +Possible options: + +### Option 1. Staked DIN + +Pros: + +- aligned with network participation +- easier to justify economically + +Cons: + +- favors validators and active operators +- may underrepresent model owners or long-term token holders who do not stake + +### Option 2. Locked DIN + +Pros: + +- cleaner governance commitment signal +- easier to separate governance from validator economics + +Cons: + +- requires additional locking mechanics + +### Option 3. Hybrid + +Pros: + +- can combine staking and governance participation +- more flexible long-term + +Cons: + +- more complex to reason about + +### Recommendation + +Start with one of these two: + +- locked DIN governance power, or +- staked DIN governance power with explicit delegation + +Raw transferable-balance voting is likely too weak for production. + +## Who Can Make Proposals? + +DIN should not allow unrestricted zero-cost proposal spam. + +Proposal creation should be gated by at least one of: + +- minimum voting power threshold +- proposal bond or deposit +- delegated sponsorship +- reputation or role requirements for some proposal categories + +### Recommended baseline + +Allow proposals from addresses that satisfy one of: + +- hold at least a minimum threshold of governance power +- receive delegation above the proposal threshold +- are sponsored by another address that does + +For high-risk proposal categories, DIN may require stricter gating. + +Examples: + +- treasury withdrawals above a threshold +- contract upgrades +- blacklist or unblacklist actions + +## Who Is Allowed To Vote? + +Voting eligibility should be determined by governance power at a snapshot. + +Recommended rules: + +- snapshot taken when proposal becomes active +- only addresses with governance power at that snapshot can vote +- delegation fixed by snapshot rules + +This prevents votes from shifting mid-proposal in unstable ways. + +## Proposal Lifecycle + +DIN should define an explicit proposal lifecycle instead of ad hoc admin execution. + +One reasonable lifecycle is: + +1. Proposal draft +2. Discussion period +3. On-chain proposal creation +4. Voting delay +5. Voting period +6. Queueing if passed +7. Timelock delay +8. Execution +9. Finalization as accepted or rejected + +### 1. Draft + +The proposer defines: + +- proposal type +- rationale +- target contracts +- function calls +- parameter values +- risk level + +### 2. Discussion + +This can happen off-chain first to reduce noisy on-chain spam. + +Examples: + +- forum discussion +- GitHub issue or RFC +- Snapshot-style temperature check + +### 3. Creation + +The formal proposal is submitted on-chain with: + +- proposer identity +- proposal metadata URI +- executable actions or action hash +- snapshot block + +### 4. Voting + +Voters cast: + +- `for` +- `against` +- optionally `abstain` + +Proposal success should depend on: + +- quorum +- majority rule or supermajority rule depending on proposal type + +### 5. Queueing + +If a proposal passes, it should usually be queued into a timelock rather than executed immediately. + +### 6. Execution + +After the timelock delay: + +- accepted proposals can be executed +- rejected proposals expire without execution + +### 7. Finalization + +Proposal state becomes one of: + +- executed +- rejected +- expired +- canceled + +## Proposal Types And Thresholds + +Not every proposal should use the same thresholds. + +One reasonable model is: + +### Low-Risk Parameter Changes + +Examples: + +- fee changes within bounded ranges +- small exchange-rate updates if that policy remains active + +Possible governance settings: + +- normal quorum +- simple majority +- standard timelock + +### Medium-Risk Operational Actions + +Examples: + +- add or remove a slasher contract +- disable or re-enable a model +- blacklist or unblacklist a validator + +Possible governance settings: + +- higher quorum +- stronger review requirements +- maybe a shorter emergency path for protective actions but not for restorative ones + +### High-Risk Treasury Or Upgrade Actions + +Examples: + +- large treasury withdrawals +- platform contract upgrades +- governance-contract upgrades +- transfer of critical protocol authority + +Possible governance settings: + +- higher proposal threshold +- higher quorum +- supermajority +- longer timelock + +## Accepting, Rejecting, And Executing Proposals + +DIN should separate these clearly. + +### Accepted + +A proposal is accepted if it: + +- reaches quorum +- gets the required majority or supermajority +- passes category-specific checks + +### Rejected + +A proposal is rejected if it: + +- fails quorum +- loses the vote +- is canceled due to invalidity or proposer failure + +### Executed + +An accepted proposal is only finalized after execution succeeds. + +That matters because: + +- a passed vote can still fail at execution if the action is malformed +- contracts may have changed state during the timelock period +- execution should be explicit and observable + +## Example Governance Scope Mapping + +The current centralized actions can be mapped roughly as follows: + +### Registry Governance + +- approve or reject model requests +- approve or reject manifest updates +- disable or enable models +- update fees +- withdraw fees + +### Coordinator Governance + +- add or remove slasher contracts +- update validator stake contract reference +- update DIN-per-ETH rate +- withdraw ETH treasury balance + +### Stake Governance + +With future contract changes, governance may also cover: + +- blacklist validator +- unblacklist validator +- future staking parameter changes +- future jailing or appeal logic + +### Upgrade Governance + +- upgrade platform contracts +- replace governance executors +- change timelock parameters + +## Emergency Governance + +DIN likely needs an emergency path, but it should be narrow. + +Possible emergency-only actions: + +- disable a malicious model +- pause or freeze a dangerous slasher contract +- emergency blacklist subject to later review + +Emergency powers should not bypass governance permanently. + +Reasonable safeguards: + +- emergency action expires unless ratified +- emergency council scope is narrow +- emergency actions are transparent and reviewable +- restorative actions such as unblacklisting may require normal governance or appeal flow + +## Recommended Governance Architecture + +One practical direction is a layered architecture: + +### Layer 1. Token / Voting Power Layer + +- DIN locked or staked for governance power +- delegation supported +- snapshot-based accounting + +### Layer 2. Proposal Layer + +- proposal thresholds +- proposal metadata +- typed proposal categories + +### Layer 3. Voting Layer + +- quorum rules +- majority or supermajority rules +- per-category thresholds + +### Layer 4. Timelock / Execution Layer + +- queue successful proposals +- enforce execution delay +- execute approved contract calls + +### Layer 5. Emergency Layer + +- narrowly scoped emergency authority +- post-action ratification or review + +## Suggested Phased Rollout + +### Phase 1. DAO-Controlled Platform Admin + +Replace direct admin or owner actions with a DAO executor or timelock that controls: + +- `DINModelRegistry` admin functions +- `DinCoordinator` owner functions + +This is the smallest meaningful decentralization step. + +### Phase 2. Governance Process Standardization + +Introduce: + +- proposal thresholds +- quorum rules +- voting periods +- timelocks +- delegation + +### Phase 3. Expanded Governance Scope + +Add governance control over: + +- treasury routing +- blacklist and unblacklist logic +- platform upgrades +- staking parameter changes + +### Phase 4. Advanced Governance + +Only later, if justified: + +- quadratic signaling +- reputation overlays +- bicameral governance by role +- specialized committees for appeals or emergency review + +## Open Design Questions + +- Should model approval and manifest approval remain direct DAO votes, or should governance delegate those to a smaller elected committee? +- Should validator blacklisting and unblacklisting require the same threshold? +- Should treasury proposals and contract-upgrade proposals require supermajority? +- Should DIN governance power come from staked DIN, locked DIN, or a hybrid? +- Should quadratic voting be used anywhere beyond non-binding signaling? +- Should model owners, validators, and token holders all vote in one chamber, or should DIN eventually separate roles? + +## Good Contribution Directions + +- define the minimum DAO scope for platform-level admin replacement +- design governance contracts and timelock flow +- propose a voting-power model based on DIN commitment rather than raw balance +- design blacklist and unblacklist appeal mechanics +- compare token-weighted and quadratic voting for DIN specifically +- prototype proposal categories with different thresholds +- document off-chain discussion plus on-chain execution workflow + +## Conclusion + +DIN should move the authority currently held by `dinDAO` admin keys and platform-contract owners into a DAO-based governance system for production. + +The recommended default is not raw quadratic voting on transferable DIN. A stronger first step is: + +- snapshot-based governance power from staked or locked DIN +- proposal thresholds +- quorum rules +- category-specific voting requirements +- timelocked execution + +`DINModelRegistry` fee updates and validator blacklisting are good examples, but the broader scope is all platform-level authority that should eventually be governed by the DAO rather than a centralized admin. + +--- + +## Decisions Made (feat/din-dao) + +The following decisions were reached during the design phase and are recorded here as +the rationale record. The specification is in `Documentation/technical/din-dao/README.md`. + +### Voting power + +**Decision: locked DIN (stDIN).** +Implemented in `DinGovernanceStaking.sol`. Voting power comes from locking DIN +tokens into a non-transferable ERC-20 (stDIN) implementing OZ `IVotes`. Validator +stake in `DinValidatorStake` is not counted in v1 to avoid conflicts of interest when +governance votes on slashing conditions or blacklisting appeals. + +### Quadratic voting + +**Decision: no on-chain quadratic voting.** +Non-binding signaling off-chain only, if ever. Raw quadratic voting on freely +transferable DIN would be trivially gameable via address splitting. + +### Proxy pattern (from Discussion #17) + +**Decision: UUPS with timelock-gated `_authorizeUpgrade` once Stage B is live.** +Transparent proxy is retained for the current devnet deployment (PR #13). Migration +to UUPS is planned before mainnet deployment via a governance proposal through +`DinTimelockLong`. Upgrade validity is enforced by `upgrades.validateUpgrade()` as a +CI gate before any proposal reaches the timelock. + +### Own multisig vs. Gnosis Safe + +**Decision: build `DinMultisig` for devnet; plan Safe migration for mainnet.** +See `Documentation/technical/din-dao/README.md §6`. + +### Timelock delays + +**Decision: two instances — 24 h (short) and 48 h (long).** +Short for Parameter and Operational proposals; long for Treasury and Upgrade proposals. +See design doc §7 for rationale. + +### Blacklisting vs. unblacklisting threshold + +**Decision: different thresholds.** +Blacklisting is `Operational` category (lower threshold; also available as guardian +emergency action). Unblacklisting is `Operational` via full governance only — no +emergency path for restorative actions. + +### Treasury and upgrade supermajority + +**Decision: yes, ≥66% of participating votes with higher quorum (15–20%).** +See design doc §4.2. + +### One chamber vs. separated by role + +**Decision: one chamber for v1.** Bicameral governance (validators vs. token holders) +is deferred to Phase 4 in the suggested governance roadmap. + +### Stage activation schedule + +| Stage | Contracts | Target milestone | +|-------|--------------------------------------------|------------------| +| A | DinMultisig | Devnet 2.0 | +| B | DinTimelockShort, DinTimelockLong | Devnet 3.0 | +| C | DinGovernanceStaking, DinGovernor | Testnet 1.0 | +| D | DinGuardian | Testnet 1.0–2.0 | diff --git a/Documentation/dindao.md b/Documentation/dindao.md index aea035b..3f7bb4b 100644 --- a/Documentation/dindao.md +++ b/Documentation/dindao.md @@ -203,6 +203,130 @@ dincli dindao registry set-admin --- +## 5a. Ownership-Transfer Runbook — Stage B Activation (devnet 3.0) + +This runbook transfers `owner()` of each platform contract from the DIN-Representative +EOA to the appropriate `DinTimelock` instance. Execute steps in the exact order listed. +The entire sequence must be rehearsed on Optimism Sepolia devnet before any testnet +deployment. + +### Prerequisites + +- `DinMultisig` deployed with production signers and thresholds verified. +- `DinTimelockShort` (24 h delay) deployed; `DinMultisig` holds `PROPOSER_ROLE` and + `CANCELLER_ROLE`; `DEFAULT_ADMIN_ROLE` renounced. +- `DinTimelockLong` (48 h delay) deployed; same role wiring as above. +- Both timelocks verified on Optimism Sepolia Blockscout. +- All four platform contract proxies deployed (PR #13): `DinCoordinator`, + `DinToken`, `DinValidatorStake`, `DINModelRegistry`. + +### Step 1 — Transfer DINModelRegistry ownership + +`DINModelRegistry` uses OZ two-step `Ownable2Step`. Initiate from the DIN-Representative +EOA, then accept from the timelock side via a scheduled call. + +```bash +# 1a. Initiate transfer (DIN-Representative signs) +dincli dindao registry set-admin + +# 1b. Build the acceptance call and schedule it through DinMultisig +# Target: DINModelRegistry proxy address +# Calldata: acceptOwnership() +# Category: Upgrade +# Timelock: DinTimelockLong (48 h) +``` + +After the 48 h delay, execute the scheduled timelock operation. Verify: +```bash +# Confirm new owner +cast call "owner()(address)" --rpc-url $RPC +# Expected: DinTimelockLong address +``` + +> [!IMPORTANT] +> Do not proceed to Step 2 until Step 1 is confirmed on-chain. + +### Step 2 — Transfer DinValidatorStake ownership + +```bash +# DIN-Representative initiates +cast send "transferOwnership(address)" \ + \ + --private-key $DIN_REP_KEY --rpc-url $RPC + +# Schedule acceptance through DinMultisig (Upgrade category → DinTimelockLong) +# Calldata: acceptOwnership() on DinValidatorStake proxy +``` + +Verify after 48 h execution: +```bash +cast call "owner()(address)" --rpc-url $RPC +# Expected: DinTimelockLong address +``` + +### Step 3 — Transfer DinCoordinator ownership + +```bash +cast send "transferOwnership(address)" \ + \ + --private-key $DIN_REP_KEY --rpc-url $RPC + +# Schedule acceptance (Upgrade category → DinTimelockLong) +``` + +### Step 4 — Transfer DinToken ownership + +`DinToken` ownership controls the mint-authority wiring to `DinCoordinator`. +Transfer to `DinTimelockLong`. + +```bash +cast send "transferOwnership(address)" \ + \ + --private-key $DIN_REP_KEY --rpc-url $RPC +``` + +### Step 5 — Transfer ProxyAdmin ownership (upgrade governance) + +The `ProxyAdmin` created in PR #13 controls implementation upgrades for all four +proxy contracts. Transfer it to `DinTimelockLong`. + +```bash +cast send "transferOwnership(address)" \ + \ + --private-key $DIN_REP_KEY --rpc-url $RPC +``` + +> [!CAUTION] +> After this step, no platform contract upgrade can proceed without a successful +> governance proposal through `DinTimelockLong`. Ensure the multisig signers are +> active and the timelock role wiring is verified before completing this step. + +### Step 6 — Verify end state + +```bash +# All four contracts should report DinTimelockLong as owner +for PROXY in $COORDINATOR $VALIDATOR_STAKE $MODEL_REGISTRY $DIN_TOKEN; do + echo "Owner of $PROXY:" + cast call $PROXY "owner()(address)" --rpc-url $RPC +done + +# ProxyAdmin owner +cast call $PROXY_ADMIN "owner()(address)" --rpc-url $RPC +``` + +### Rollback + +If the ownership transfer must be reversed before `acceptOwnership()` is called: +1. The pending transfer can be cancelled by calling `transferOwnership(address(0))` on + the relevant contract from the still-active DIN-Representative EOA. +2. Once `acceptOwnership()` has been called by the timelock (Step 1b etc.), the transfer + is complete and cannot be rolled back via EOA. Recovery requires a governance proposal + through the timelock to call `transferOwnership` again. + +This is why the runbook must be rehearsed end-to-end on devnet before mainnet execution. + +--- + ## Workflow 1. **Deploy** — Coordinator → Validator Stake → Model Registry (in order). @@ -211,4 +335,5 @@ dincli dindao registry set-admin 4. **Process Manifest Update Requests** — Review pending `ManifestUpdateRequest` entries. 5. **Monitor** — Use registry commands to track network growth and model status. 6. **Emergency** — Use `disable-model` if a model needs to be stopped immediately. +7. **Stage B Activation** — See §5a for the ordered runbook to transfer ownership to the timelocks. From 7d4c77e55a94065027566053a520e17be7b568b3 Mon Sep 17 00:00:00 2001 From: robertocarlous Date: Wed, 8 Jul 2026 22:09:58 +0100 Subject: [PATCH 13/13] test(din-dao): add fuzz and invariant tests per P3-6.3b conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DaoInvariants.t.sol — four test suites: I. StakingInvariantTest (stateful invariant via StakingHandler) - invariant_TotalSupplyEqualsLockedDIN: stDIN.totalSupply() always equals DIN held by the staking contract — no mint or burn without a matching token transfer - invariant_GhostLockedMatchesContract: ghost variable tracking every lock/unlock matches both totalLocked() and totalSupply() - invariant_TotalVotesNeverExceedSupply: sum of votes across all actors never exceeds stDIN totalSupply — voting power cannot be conjured II. TimelockFuzzTest - testFuzz_CannotExecuteBeforeMinDelay: for any elapsed time in [0, MIN_DELAY), execution always reverts — covers the full sub-delay range - testFuzz_AlwaysExecutesAfterDelay: for any extra time >= 0 after MIN_DELAY, execution always succeeds III. MultisigFuzzTest - testFuzz_ConfirmCountBounded: random confirm/revoke sequences from 3 signers never produce a count outside [0, 3] - testFuzz_ExecutableRequiresThreshold: a proposal reaches Executable only when at least threshold (2) unrevoked confirms exist IV. GuardianFuzzTest - testFuzz_CannotExpireBeforeWindow: expireAction reverts for any elapsed time in [0, WINDOW) regardless of proximity to boundary - testFuzz_AlwaysExpiresAfterWindow: reversal is applied for any time >= WINDOW - testFuzz_CannotRatifyTwice: double-ratify always reverts with DG_ActionNotActive --- foundry/test/dao/DaoInvariants.t.sol | 375 +++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 foundry/test/dao/DaoInvariants.t.sol diff --git a/foundry/test/dao/DaoInvariants.t.sol b/foundry/test/dao/DaoInvariants.t.sol new file mode 100644 index 0000000..112b968 --- /dev/null +++ b/foundry/test/dao/DaoInvariants.t.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; +import "../../src/dao/DinGovernanceStaking.sol"; +import "../../src/dao/DinMultisig.sol"; +import "../../src/dao/DinTimelock.sol"; +import "../../src/dao/DinGuardian.sol"; +import "../../src/dao/interfaces/IDinMultisig.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// ───────────────────────────────────────────────────────────────────────────── +// Shared mock DIN token +// ───────────────────────────────────────────────────────────────────────────── + +contract InvMockDIN is ERC20 { + constructor() ERC20("DIN Token", "DIN") {} + function mint(address to, uint256 amount) external { _mint(to, amount); } +} + +// ───────────────────────────────────────────────────────────────────────────── +// I. Staking invariants +// Invariant A: totalSupply of stDIN always equals DIN held by the contract +// Invariant B: total delegated voting power never exceeds stDIN totalSupply +// ───────────────────────────────────────────────────────────────────────────── + +/// @dev Handler called by the Foundry fuzzer for staking invariant tests. +/// Maintains a list of up to 4 actors and ghost variables tracking the +/// expected locked amount so invariant assertions have a ground truth. +contract StakingHandler is Test { + InvMockDIN internal din; + DinGovernanceStaking internal stDIN; + + address[4] internal actors; + uint256 public ghostTotalLocked; + + constructor(InvMockDIN din_, DinGovernanceStaking stDIN_) { + din = din_; + stDIN = stDIN_; + + actors[0] = makeAddr("inv_alice"); + actors[1] = makeAddr("inv_bob"); + actors[2] = makeAddr("inv_carol"); + actors[3] = makeAddr("inv_dave"); + + // Pre-fund every actor with 10 000 DIN + for (uint256 i; i < 4; ++i) { + din_.mint(actors[i], 10_000e18); + vm.prank(actors[i]); + din_.approve(address(stDIN_), type(uint256).max); + } + } + + // ─── Actions ────────────────────────────────────────────────────────────── + + /// @dev Lock a bounded amount of DIN for a random actor. + function lock(uint256 actorSeed, uint256 amount) external { + address actor = actors[actorSeed % 4]; + uint256 cap = din.balanceOf(actor); + if (cap == 0) return; + amount = bound(amount, 1, cap); + + vm.prank(actor); + stDIN.lock(amount); + ghostTotalLocked += amount; + } + + /// @dev Unlock a bounded amount of stDIN for a random actor. + function unlock(uint256 actorSeed, uint256 amount) external { + address actor = actors[actorSeed % 4]; + uint256 cap = stDIN.balanceOf(actor); + if (cap == 0) return; + amount = bound(amount, 1, cap); + + vm.prank(actor); + stDIN.unlock(amount); + ghostTotalLocked -= amount; + } + + /// @dev Delegate voting power between random actors to exercise checkpoints. + function delegate(uint256 actorSeed, uint256 delegateeSeed) external { + address actor = actors[actorSeed % 4]; + address delegatee = actors[delegateeSeed % 4]; + vm.prank(actor); + stDIN.delegate(delegatee); + } +} + +contract StakingInvariantTest is Test { + InvMockDIN internal din; + DinGovernanceStaking internal stDIN; + StakingHandler internal handler; + + function setUp() public { + din = new InvMockDIN(); + stDIN = new DinGovernanceStaking(address(din)); + handler = new StakingHandler(din, stDIN); + + targetContract(address(handler)); + } + + /// @dev Invariant A: stDIN total supply equals DIN locked in the contract. + /// Any discrepancy indicates a mint or burn without a matching transfer, + /// which would violate 1:1 conservation. + function invariant_TotalSupplyEqualsLockedDIN() public view { + assertEq( + stDIN.totalSupply(), + din.balanceOf(address(stDIN)), + "stDIN supply != DIN balance" + ); + } + + /// @dev Invariant B: ghost variable tracks every lock/unlock; it must + /// match both the contract's totalLocked view and the stDIN supply. + function invariant_GhostLockedMatchesContract() public view { + assertEq( + handler.ghostTotalLocked(), + stDIN.totalLocked(), + "ghost != totalLocked" + ); + assertEq( + stDIN.totalLocked(), + stDIN.totalSupply(), + "totalLocked != totalSupply" + ); + } + + /// @dev Invariant C: total votes delegated to all actors never exceeds + /// the stDIN total supply. Voting power cannot be conjured. + function invariant_TotalVotesNeverExceedSupply() public view { + address[4] memory actors_ = [ + makeAddr("inv_alice"), + makeAddr("inv_bob"), + makeAddr("inv_carol"), + makeAddr("inv_dave") + ]; + uint256 totalVotes; + for (uint256 i; i < 4; ++i) { + totalVotes += stDIN.getVotes(actors_[i]); + } + assertLe(totalVotes, stDIN.totalSupply(), "total votes > supply"); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// II. Timelock invariant / fuzz +// Nothing can execute before minDelay has elapsed from scheduling. +// ───────────────────────────────────────────────────────────────────────────── + +/// @dev Minimal call target used by timelock tests. +contract InvTimelockTarget { + uint256 public value; + function setValue(uint256 v) external { value = v; } +} + +contract TimelockFuzzTest is Test { + DinTimelock internal tl; + InvTimelockTarget internal target; + + address internal proposer_ = makeAddr("tl_proposer"); + + uint256 internal constant MIN_DELAY = 1 days; + + function setUp() public { + address[] memory proposers = new address[](1); + proposers[0] = proposer_; + address[] memory executors = new address[](1); + executors[0] = address(0); + + tl = new DinTimelock(MIN_DELAY, proposers, executors, address(0)); + target = new InvTimelockTarget(); + } + + /// @dev Fuzz: for any elapsed time strictly less than MIN_DELAY, execution + /// must revert. The fuzzer explores the entire [0, MIN_DELAY) range. + function testFuzz_CannotExecuteBeforeMinDelay(uint256 elapsed) public { + elapsed = bound(elapsed, 0, MIN_DELAY - 1); + + bytes memory data = abi.encodeCall(InvTimelockTarget.setValue, (1)); + bytes32 salt = bytes32(uint256(elapsed)); // unique salt per run + + vm.prank(proposer_); + tl.schedule(address(target), 0, data, bytes32(0), salt, MIN_DELAY); + + vm.warp(block.timestamp + elapsed); + + vm.expectRevert(); + tl.execute(address(target), 0, data, bytes32(0), salt); + } + + /// @dev Fuzz: for any elapsed time >= MIN_DELAY, execution must succeed. + function testFuzz_AlwaysExecutesAfterDelay(uint256 extra) public { + extra = bound(extra, 0, 365 days); + + bytes memory data = abi.encodeCall(InvTimelockTarget.setValue, (42)); + bytes32 salt = bytes32(extra); + + vm.prank(proposer_); + tl.schedule(address(target), 0, data, bytes32(0), salt, MIN_DELAY); + + vm.warp(block.timestamp + MIN_DELAY + extra); + tl.execute(address(target), 0, data, bytes32(0), salt); + + assertEq(target.value(), 42); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// III. Multisig fuzz +// Confirm/revoke sequences never produce a count outside [0, signerCount]. +// A proposal cannot reach Executable with fewer confirms than its threshold. +// ───────────────────────────────────────────────────────────────────────────── + +/// @dev Minimal call target for multisig fuzz tests. +contract InvMultisigTarget { + uint256 public value; + function setValue(uint256 v) external { value = v; } +} + +contract MultisigFuzzTest is Test { + DinMultisig internal ms; + InvMultisigTarget internal msTarget; + + address internal s0 = makeAddr("ms_s0"); + address internal s1 = makeAddr("ms_s1"); + address internal s2 = makeAddr("ms_s2"); + + // Thresholds: Parameter=2, Operational=2, Treasury=3, Upgrade=3 + uint256[4] internal thresholds = [uint256(2), 2, 3, 3]; + + function setUp() public { + address[] memory signers_ = new address[](3); + signers_[0] = s0; signers_[1] = s1; signers_[2] = s2; + ms = new DinMultisig(signers_, thresholds); + msTarget = new InvMultisigTarget(); + } + + /// @dev Fuzz: confirm count after a random sequence of confirms and revokes + /// from distinct signers must always be in [0, 3]. + function testFuzz_ConfirmCountBounded( + bool confirmS0, + bool confirmS1, + bool confirmS2, + bool revokeS0, + bool revokeS1 + ) public { + bytes memory data = abi.encodeCall(InvMultisigTarget.setValue, (1)); + vm.prank(s0); + uint256 id = ms.propose(address(msTarget), data, 0, ProposalCategory.Parameter); + + if (confirmS0) { vm.prank(s0); ms.confirm(id); } + if (confirmS1) { vm.prank(s1); ms.confirm(id); } + if (confirmS2) { vm.prank(s2); ms.confirm(id); } + + // Revoke only if previously confirmed to avoid revert on NotConfirmed + if (revokeS0 && confirmS0) { + (, , , , ProposalState st, , ) = ms.getProposal(id); + if (st == ProposalState.Open || st == ProposalState.Executable) { + vm.prank(s0); ms.revoke(id); + } + } + if (revokeS1 && confirmS1) { + (, , , , ProposalState st, , ) = ms.getProposal(id); + if (st == ProposalState.Open || st == ProposalState.Executable) { + vm.prank(s1); ms.revoke(id); + } + } + + (, , , , , uint256 count, ) = ms.getProposal(id); + assertLe(count, 3, "confirm count exceeds signer count"); + } + + /// @dev Fuzz: a Parameter proposal (threshold=2) can only become Executable + /// once at least 2 signers have confirmed without revoking. + function testFuzz_ExecutableRequiresThreshold(bool s1Confirms, bool s0Revokes) public { + bytes memory data = abi.encodeCall(InvMultisigTarget.setValue, (7)); + vm.prank(s0); + uint256 id = ms.propose(address(msTarget), data, 0, ProposalCategory.Parameter); + + vm.prank(s0); ms.confirm(id); + + if (s0Revokes) { + vm.prank(s0); ms.revoke(id); + } + + if (s1Confirms) { + vm.prank(s1); ms.confirm(id); + } + + (, , , , ProposalState state, uint256 count, ) = ms.getProposal(id); + + if (state == ProposalState.Executable) { + assertGe(count, 2, "Executable with fewer than threshold confirms"); + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// IV. Guardian fuzz +// expireAction always reverts before the ratification window elapses. +// ratifyAction always reverts after the action has already been ratified. +// ───────────────────────────────────────────────────────────────────────────── + +contract GuardianFuzz_Target { + bool public flag; + function setFlag() external { flag = true; } + function clearFlag() external { flag = false; } +} + +contract GuardianFuzzTest is Test { + DinGuardian internal grd; + GuardianFuzz_Target internal gTarget; + + address internal multisig_ = makeAddr("grd_multisig"); + address internal governor_ = makeAddr("grd_governor"); + + uint256 internal constant WINDOW = 7 days; + + function setUp() public { + grd = new DinGuardian(multisig_, governor_, WINDOW); + gTarget = new GuardianFuzz_Target(); + } + + /// @dev Fuzz: expireAction always reverts for any elapsed time before the + /// ratification window closes, regardless of how close to the boundary. + function testFuzz_CannotExpireBeforeWindow(uint256 elapsed) public { + elapsed = bound(elapsed, 0, WINDOW - 1); + + bytes memory data = abi.encodeCall(GuardianFuzz_Target.setFlag, ()); + bytes memory reversal = abi.encodeCall(GuardianFuzz_Target.clearFlag, ()); + + vm.prank(multisig_); + uint256 id = grd.performAction(address(gTarget), data, reversal, "fuzz"); + + vm.warp(block.timestamp + elapsed); + vm.expectRevert(DG_WindowNotElapsed.selector); + grd.expireAction(id); + } + + /// @dev Fuzz: for any elapsed time >= WINDOW, expireAction dispatches the + /// reversal and the target state is restored. + function testFuzz_AlwaysExpiresAfterWindow(uint256 extra) public { + extra = bound(extra, 0, 365 days); + + bytes memory data = abi.encodeCall(GuardianFuzz_Target.setFlag, ()); + bytes memory reversal = abi.encodeCall(GuardianFuzz_Target.clearFlag, ()); + + vm.prank(multisig_); + uint256 id = grd.performAction(address(gTarget), data, reversal, "fuzz"); + + assertTrue(gTarget.flag()); // action applied + + vm.warp(block.timestamp + WINDOW + extra); + grd.expireAction(id); + + assertFalse(gTarget.flag()); // reversal applied + } + + /// @dev Fuzz: double-ratify always reverts — an action cannot be ratified twice. + function testFuzz_CannotRatifyTwice(uint256 elapsed) public { + elapsed = bound(elapsed, 0, WINDOW - 1); + + bytes memory data = abi.encodeCall(GuardianFuzz_Target.setFlag, ()); + + vm.prank(multisig_); + uint256 id = grd.performAction(address(gTarget), data, "", "fuzz"); + + vm.warp(block.timestamp + elapsed); + vm.prank(governor_); grd.ratifyAction(id); + + vm.prank(governor_); + vm.expectRevert(DG_ActionNotActive.selector); + grd.ratifyAction(id); + } +}