From 2f179ae56f6745e194fd91a274ee4e5bc193d643 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 05:44:16 +0000 Subject: [PATCH] docs: add mainnet deploy & role rotation runbook - Create docs/mainnet-deploy.md with production deployment sequence - Cover Deploy.s.sol vs DeployMainnet.s.sol script split - Document pre-flight checklist, role rotation phases, risk register - Add link to README Documentation table (audience: operators) - Add pointer in docs/robinhood-chain.md Production section Docs-only change; no Solidity/script implementation. --- README.md | 1 + docs/mainnet-deploy.md | 267 ++++++++++++++++++++++++++++++++++++++++ docs/robinhood-chain.md | 4 + 3 files changed, 272 insertions(+) create mode 100644 docs/mainnet-deploy.md diff --git a/README.md b/README.md index 33d34cd..4ac356b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ If you're looking for MCP server support or React trust-badge components, those | [Ecosystem Overview](docs/ecosystem.md) | Everyone — start here to understand the full picture | | [Quickstart](docs/quickstart.md) | Developers — register your first agent in 10 minutes | | [Robinhood Chain](docs/robinhood-chain.md) | Developers — deploy / test on Robinhood Chain (46630 / 4663) | +| [Mainnet Deploy & Role Rotation](docs/mainnet-deploy.md) | Operators — production deployment runbook for `4663` | | [CounterAudit Integration](docs/counteraudit-integration.md) | Enterprise — embed agent identity in your audit trail | | [AI Framework Integration](docs/ai-frameworks.md) | Developers — LangChain, AutoGen, CrewAI, Node.js | | [Reputation Model](docs/reputation-model.md) | Everyone — how the 6-factor score works and grows | diff --git a/docs/mainnet-deploy.md b/docs/mainnet-deploy.md new file mode 100644 index 0000000..07506e7 --- /dev/null +++ b/docs/mainnet-deploy.md @@ -0,0 +1,267 @@ +# Mainnet Deploy & Role Rotation + +> **Status:** Draft runbook — not executed. +> **Target:** Robinhood Chain mainnet `4663` + +This guide covers the end-to-end deployment and role-hardening sequence for +bringing Countersig protocol contracts to mainnet. It is intended for operators +responsible for deploying, verifying, and securing the production registry. + +--- + +## Critical Script Split + +Two deployment scripts exist with distinct purposes — do not confuse them: + +| Script | What It Deploys | When to Use | +|---|---|---| +| `script/Deploy.s.sol` | Protocol: Identity + Reputation + Staking + **faucet** `CSIGToken` | Testnet today. Requires a mainnet variant (no faucet; bond = WETH/USDC or fixed `CSIG`) | +| `script/DeployMainnet.s.sol` | **TGE only:** Timelock + Treasury/Team vesting + fixed-supply `CSIG` | Token launch. Does **not** deploy Identity/Reputation/Staking | + +The [oracle-first strategy](oracle-first.md) means you can ship **protocol livenet** without TGE. Bond token should be an established asset (WETH/USDC) or fixed-supply CSIG — never `CSIGToken.faucet()`. + +--- + +## 1. Pre-flight Checklist + +Complete these items before any broadcast on `4663`. + +### People & Keys + +- [ ] **Deployer EOA** funded with mainnet gas (Alchemy RPC preferred; public RPC is rate-limited) +- [ ] **Gov Safe** — 3-of-5 (proposers/cancellers for Timelock) +- [ ] **Slashing committee Safe** — separate 3-of-5 (or same Safe only if you explicitly accept concentration) +- [ ] **Oracle key** — HSM/MPC wallet that will hold `ORACLE_ROLE` (not the deployer long-term) +- [ ] Cold backup of all Safe recovery paths + +### Decisions to Lock Before Deploy + +- [ ] Bond token address on `4663` (WETH / USDC / CSIG) +- [ ] `MINIMUM_STAKE`, `CHALLENGE_PERIOD` (7d), `SCORE_CHALLENGE_WINDOW` (6h recommended for mainnet; testnet uses shorter), `UNBONDING_PERIOD` (21d) +- [ ] Whether Identity is still legacy `CountersigIdentity` or ERC-8004-only at cutover (see [ADR 0001](adr/0001-erc8004-as-identity-layer.md)) +- [ ] Timelock delay **7 days** ([tokenomics §9](tokenomics.md) — locked at init) +- [ ] Audit complete / findings remediated + +### Code Gaps to Close First + +- [ ] Add `script/DeployProtocolMainnet.s.sol` (or extend Deploy) that: + 1. Accepts `BOND_TOKEN` address (no faucet mint) + 2. Deploys Identity / Reputation / Staking proxies + 3. Optionally deploys or accepts an existing Timelock + 4. Grants roles to oracle + committee + 5. Transfers `DEFAULT_ADMIN_ROLE` + `UPGRADER_ROLE` to Timelock + 6. Renounces deployer admin/upgrader + 7. Writes `deployments/4663.json` +- [ ] Add `script/RotateRoles.s.sol` for post-deploy or testnet rehearsal +- [ ] Dry-run both on a fork of `4663` / rehearse role rotation on `46630` + +--- + +## 2. Recommended Sequence + +Protocol livenet (oracle-first): + +``` +A. Rehearse on testnet 46630 (role rotation only) +B. Deploy Timelock on 4663 (gov Safe = proposer/canceller) +C. Deploy protocol proxies with BOND_TOKEN + initial roles +D. Verify on Blockscout +E. Rotate admin/upgrader → Timelock; committee → Safe; oracle → HSM key +F. Renounce deployer privileges; assert with cast calls +G. Point hardened oracle + SDK at deployments/4663.json +H. Smoke: register → stake → epoch → meetsThreshold (small real stake) +``` + +TGE (`DeployMainnet.s.sol`) is **optional later** and independent of C–H if bonds are WETH/USDC. + +--- + +## 3. Phase A — Testnet Rehearsal + +On `46630`, deployer currently holds admin/oracle/committee (confirm against `deployments/46630.json`). Practice: + +1. Deploy a **test** Timelock (48h delay OK for rehearsal; mainnet must be 7d). +2. Create/use a Safe as `COMMITTEE_ADDRESS`. +3. Run rotation script / cast sequence: + - `grantRole(DEFAULT_ADMIN_ROLE, timelock)` on Identity, Reputation, Staking + - `grantRole(UPGRADER_ROLE, timelock)` on all three + - `grantRole(ORACLE_ROLE, oracleHsm)` on Reputation + - `grantRole(SLASHING_COMMITTEE_ROLE, committeeSafe)` on Reputation + Staking + - `renounceRole` for deployer on admin/upgrader/oracle/committee as appropriate +4. Confirm deployer **cannot** `upgradeTo` or `setMinimumStake` anymore. +5. Confirm Timelock → schedule → wait → execute can still change a harmless param. +6. Confirm committee Safe can `initiateSlash` (on a throwaway agent). + +Do **not** renounce testnet deployer until rehearsal is documented and reversible plan exists. + +--- + +## 4. Phase B — Deploy Timelock on Mainnet + +Either via a thin script or adapted from `DeployMainnet.s.sol` step 1: + +```solidity +TimelockController( + minDelay = 7 days, + proposers = [GOV_MULTISIG], + executors = [address(0)], // permissionless execute after delay + admin = address(0) // timelock self-admins +) +``` + +Record: `timelock`, `govMultisig`. + +--- + +## 5. Phase C — Deploy Protocol on `4663` + +Intended env (for the future `DeployProtocolMainnet` script): + +```bash +export DEPLOYER_PRIVATE_KEY=0x... +export BOND_TOKEN=0x... # WETH or USDC on 4663 +export ORACLE_ADDRESS=0x... # HSM/MPC (or deployer temporarily) +export COMMITTEE_ADDRESS=0x... # Safe (or deployer temporarily) +export MINIMUM_STAKE=... # in bond-token wei +export CHALLENGE_PERIOD=604800 # 7 days +export SCORE_CHALLENGE_WINDOW=21600 # 6 hours +export UNBONDING_PERIOD=1814400 # 21 days + +# dry-run +forge script script/DeployProtocolMainnet.s.sol --rpc-url robinhood_mainnet -vvvv + +# broadcast + verify +forge script script/DeployProtocolMainnet.s.sol --rpc-url $ALCHEMY_ROBINHOOD_MAINNET \ + --broadcast --verify --verifier blockscout \ + --verifier-url https://robinhoodchain.blockscout.com/api/ -vvvv +``` + +Wiring (same as today's `Deploy.s.sol`): + +1. Identity proxy (`admin = deployer` initially, `stakingCore = 0` then grant) +2. Reputation proxy +3. Staking proxy with `BOND_TOKEN` +4. `identity.grantRole(STAKING_CORE_ROLE, staking)` +5. `reputation.grantRole(STAKING_CORE_ROLE, staking)` +6. `reputation.grantRole(ORACLE_ROLE, oracle)` +7. `reputation` + `staking` `grantRole(SLASHING_COMMITTEE_ROLE, committee)` +8. Write `deployments/4663.json` + +**Do not** deploy `CSIGToken` (faucet) on mainnet. + +--- + +## 6. Phase D — Role Rotation + +Assume temporary deploy left admin on deployer. Final state: + +| Role | Identity | Reputation | Staking | +|---|---|---|---| +| `DEFAULT_ADMIN_ROLE` | Timelock only | Timelock only | Timelock only | +| `UPGRADER_ROLE` | Timelock only | Timelock only | Timelock only | +| `STAKING_CORE_ROLE` | Staking proxy | Staking proxy | n/a | +| `ORACLE_ROLE` | n/a | Oracle HSM (+ future operators) | n/a | +| `SLASHING_COMMITTEE_ROLE` | n/a | Committee Safe | Committee Safe | + +### Cast-Style Sequence (per contract) + +```bash +# Grant first, then renounce — never renounce admin before Timelock has it +cast send $REPUTATION "grantRole(bytes32,address)" $DEFAULT_ADMIN_ROLE $TIMELOCK \ + --rpc-url ... --private-key $DEPLOYER +cast send $REPUTATION "grantRole(bytes32,address)" $UPGRADER_ROLE $TIMELOCK \ + --rpc-url ... --private-key $DEPLOYER +# ... Identity + Staking same for admin/upgrader + +cast send $REPUTATION "grantRole(bytes32,address)" $ORACLE_ROLE $ORACLE_HSM ... +cast send $REPUTATION "grantRole(bytes32,address)" $SLASHING_COMMITTEE_ROLE $COMMITTEE_SAFE ... +cast send $STAKING "grantRole(bytes32,address)" $SLASHING_COMMITTEE_ROLE $COMMITTEE_SAFE ... + +# If deployer was temporary oracle/committee: +cast send $REPUTATION "renounceRole(bytes32,address)" $ORACLE_ROLE $DEPLOYER ... +cast send $REPUTATION "renounceRole(bytes32,address)" $SLASHING_COMMITTEE_ROLE $DEPLOYER ... +cast send $STAKING "renounceRole(bytes32,address)" $SLASHING_COMMITTEE_ROLE $DEPLOYER ... + +# Last: renounce deployer admin + upgrader on all three +cast send $REPUTATION "renounceRole(bytes32,address)" $DEFAULT_ADMIN_ROLE $DEPLOYER ... +cast send $REPUTATION "renounceRole(bytes32,address)" $UPGRADER_ROLE $DEPLOYER ... +# Identity + Staking likewise +``` + +Prefer a single Foundry `RotateRoles.s.sol` that asserts `hasRole` after each step and reverts if Timelock/Safe/oracle missing roles. + +### Post-Rotation Assertions + +- [ ] `hasRole(DEFAULT_ADMIN_ROLE, deployer) == false` on all three +- [ ] `hasRole(UPGRADER_ROLE, deployer) == false` on all three +- [ ] `hasRole(DEFAULT_ADMIN_ROLE, timelock) == true` on all three +- [ ] Oracle HSM can `proposeReputation` (or is granted) +- [ ] Committee Safe can call `initiateSlash` / `rejectReputation` +- [ ] Deployer upgrade attempt reverts +- [ ] Timelock schedule → 7d → execute succeeds for a no-op/param change on a staging rehearsal + +--- + +## 7. Phase E — Oracle & Ops Cutover + +- [ ] `oracle/.env` → `4663` addresses, Alchemy RPC, `ORACLE_PRIVATE_KEY` = HSM key +- [ ] `ATTEST_COOLDOWN_MS`, `ORACLE_STATE_PATH` volume mounted +- [ ] Scrape `/metrics`; alert on `/health` 503 (stale epoch / unwritable store) +- [ ] `ORACLE_ADMIN_TOKEN` set (never expose unauthenticated `/attest`/`/epoch`) +- [ ] SDK / CounterAudit configs point at `deployments/4663.json` +- [ ] Run fork tests: + +```bash +FOUNDRY_FORK=1 forge test --match-contract ForkTest --fork-url $MAINNET_RPC +``` + +Extend `Fork.t.sol` for `4663` once addresses committed. + +--- + +## 8. Optional Later — TGE (`DeployMainnet.s.sol`) + +Only when token launch is intentional (align with [oracle-first](oracle-first.md) / no public sale messaging until legal/product greenlight): + +```bash +export GOV_MULTISIG=0x... +export TEAM_BENEFICIARY=0x... +export PUBLIC_SALE_RECIPIENT=0x... # must != LIQUIDITY_RECIPIENT +export LIQUIDITY_RECIPIENT=0x... +export TGE_START=... + +forge script script/DeployMainnet.s.sol --rpc-url $RPC --broadcast -vvvv +``` + +If protocol already live on WETH/USDC, TGE does not automatically retarget Staking's bond token — that would be a separate governance upgrade / new deployment decision. + +--- + +## 9. Risk Register + +| Risk | Mitigation | +|---|---| +| Deployer retains admin after "mainnet" | Automated assertions + public role dump in `deployments/4663.json` | +| `challengeWindow = 0` after upgrade | Always `upgradeToAndCall` with V2 init (see `script/Upgrade.s.sol` lesson) | +| Faucet token on mainnet | Script must not deploy `CSIGToken` | +| Single oracle key | HSM + documented acceptance; roadmap multi-operator | +| Secrets in CI job-level conditions | Keep secret checks at step/env level, not job-level `if: secrets.*` | +| Public RPC flakiness | Alchemy for oracle + deploy | + +--- + +## 10. Suggested Engineering Work + +1. **Rehearse role rotation on `46630`** (practice path with a new Timelock + Safe). +2. **Implement `DeployProtocolMainnet.s.sol` + `RotateRoles.s.sol`** with fork tests. +3. **Extend `Fork.t.sol`** template for `4663` once deployed. +4. Keep TGE script parked until product/legal says go. + +--- + +## Related + +- [Robinhood Chain Migration](robinhood-chain.md) +- [Direction: Oracle-First](oracle-first.md) +- [Tokenomics §9 — Governance Timelock](tokenomics.md) +- [ADR 0001 — ERC-8004 as Identity Layer](adr/0001-erc8004-as-identity-layer.md) diff --git a/docs/robinhood-chain.md b/docs/robinhood-chain.md index 135b85d..cb9cba6 100644 --- a/docs/robinhood-chain.md +++ b/docs/robinhood-chain.md @@ -141,6 +141,10 @@ Optional later: add a fork test suite against `robinhood_testnet` for post-deplo ## 6. Production (Robinhood mainnet) +For the full mainnet deployment sequence — including role rotation, Timelock +setup, and post-deploy hardening — see the [Mainnet Deploy & Role Rotation +runbook](mainnet-deploy.md). + Use `script/DeployMainnet.s.sol` + fixed-supply `CSIG` (not the testnet `CSIGToken` faucet) when you are ready for TGE on chain `4663`. Uniswap is available on Robinhood Chain for liquidity; revisit any Ethereum-only assumptions in