[Audit L-03] Reject zero admin in AccessControl initializers (deployment footgun) #120

Closed
opened 2026-05-04 04:17:47 +00:00 by PlasticDigits · 9 comments
PlasticDigits commented 2026-05-04 04:17:47 +00:00 (Migrated from gitlab.com)

Source

Tracked from internal smart contract audit: audits/audit_smartcontract_1777813071.md (L-03: Several AccessControl deployments do not reject zero admin addresses).

Problem statement

Contracts that use OpenZeppelin AccessControl (or enumerable upgradeable variants) assign DEFAULT_ADMIN_ROLE via _grantRole in constructors / initializer flows. Unlike Ownable-style contracts that commonly reject address(0) in their initializers, these paths currently allow admin == address(0).

Granting DEFAULT_ADMIN_ROLE to address(0) does not create a usable admin: OpenZeppelin’s role model expects a living account or contract to hold that role so it can grant/revoke other roles and (for UUPS targets) authorize upgrades. A mistaken deployment wiring zero as admin yields a governance-dead deployment: withdrawals, governor updates, NFT admin actions, treasury parameter roles, and upgrade authorization may become permanently unreachable except via an emergency upgrade path that itself requires admin—i.e., a brick risk.

This is low severity from an attacker’s perspective: it is an operator/deployer footgun, not an unprivileged fund-stealing path. Impact is misconfiguration leading to irreversible inability to administer the system.

In-scope deployments (today)

Component Mechanism
Fee sinks (shared base) FeeSink.__FeeSink_init(admin) grants DEFAULT_ADMIN_ROLE + WITHDRAWER_ROLE to admin
Fee sink implementations CL8YProtocolTreasury.initialize, DoubLPIncentives.initialize, EcosystemTreasury.initialize delegate to __FeeSink_init
PodiumPool initialize(admin) grants DEFAULT_ADMIN_ROLE
FeeRouter initialize(admin, ...) grants DEFAULT_ADMIN_ROLE + GOVERNOR_ROLE
RabbitTreasury initialize(..., admin) grants DEFAULT_ADMIN_ROLE, PARAMS_ROLE, PAUSER_ROLE
Doubloon constructor(admin) grants DEFAULT_ADMIN_ROLE only
LeprechaunNFT constructor(..., admin) grants DEFAULT_ADMIN_ROLE + MINTER_ROLE
  1. Add an explicit invariant at the earliest common point:

    • In contracts/src/sinks/FeeSink.sol, inside __FeeSink_init, add
      require(admin != address(0), "<consistent suffix>");
      immediately before any _grantRole calls (covers CL8YProtocolTreasury, DoubLPIncentives, EcosystemTreasury in one place).
  2. Add the same guard to each non-inherited initializer/constructor that grants AccessControl roles:

    • contracts/src/sinks/PodiumPool.sol → initialize(address admin)
    • contracts/src/FeeRouter.sol → initialize(address admin, ...)
    • contracts/src/RabbitTreasury.sol → initialize(..., address admin) (alongside existing require validations)
    • contracts/src/tokens/Doubloon.sol → constructor(address admin)
    • contracts/src/LeprechaunNFT.sol → constructor(..., address admin)
  3. Revert string policy: Prefer a short, grep-friendly prefix per contract (e.g. FeeSink: zero admin, PodiumPool: zero admin, …) to match existing patterns like FeeSink: zero address on withdraw’s to.

  4. No behavior change for valid admins: Existing tests and deployments with non-zero admin must remain green.

Optional hardening (out of narrow L-03 scope but aligned): audit other initialize/constructor flows that _grantRole with a caller-supplied address for the same class of mistake.

Files expected to change (implementation)

Solidity (required)

  • contracts/src/sinks/FeeSink.sol
  • contracts/src/sinks/PodiumPool.sol
  • contracts/src/FeeRouter.sol
  • contracts/src/RabbitTreasury.sol
  • contracts/src/tokens/Doubloon.sol
  • contracts/src/LeprechaunNFT.sol

Pick one coherent approach:

  • Single focused test module such as contracts/test/AccessControlZeroAdmin.t.sol that deploys minimal instances (direct new where non-proxy, proxy + initialize via Foundry patterns matching existing tests) and asserts vm.expectRevert for each contract/surface; or
  • Extend the closest existing files: contracts/test/FeeSinks.t.sol, contracts/test/FeeRouter.t.sol, contracts/test/RabbitTreasury.t.sol, contracts/test/LeprechaunNFT.t.sol, plus one assertion that a FeeSink derivative wired through UUPSDeployLib rejects zero admin.

Regression coverage matrix (each row must revert with zero admin):

  1. EcosystemTreasury / DoubLPIncentives / CL8YProtocolTreasury proxy initialize(address(0))
  2. PodiumPool proxy initialize(address(0))
  3. FeeRouter proxy initialize(address(0), sinks, weights) (minimal valid sinks/weights or expect revert before sink validation depending on ordering—assert ordering documents which require fires first)
  4. RabbitTreasury proxy full initialize with admin = address(0) but otherwise valid params
  5. new Doubloon(address(0))
  6. new LeprechaunNFT(..., address(0))

Deployment scripts (verification only)

Production paths like contracts/script/DeployDev.s.sol already use a non-zero deployer; no functional change strictly required unless you add defensive require there for clarity. Optionally add assert/comments in contracts/script/UUPSDeployLib.sol mirroring onchain checks for faster failure during dry-run.

Update or add bullets so operators know zero-admin is forbidden at the EVM boundary:

  • docs/onchain/fee-routing-and-governance.md (FeeRouter admin / governor semantics)
  • docs/testing/strategy.md or docs/testing/invariants-and-business-logic.md — add a named invariant / test-map row for “AccessControl deployments reject admin == address(0)”
  • Optionally cross-reference in contracts/PARAMETERS.md or ops deployment checklist if admin wiring is documented there

Acceptance criteria

  • Every listed contract rejects admin == address(0) at constructor or initializer, before any _grantRole that assigns DEFAULT_ADMIN_ROLE (and before co-granted roles in the same block: WITHDRAWER_ROLE, GOVERNOR_ROLE, PARAMS_ROLE, PAUSER_ROLE, MINTER_ROLE as applicable).
  • FOUNDRY_PROFILE=ci (or repo-default CI recipe) forge test passes with new regression tests included.
  • Existing integration tests (DevStackIntegration, TimeCurve*, FeeRouter*, etc.) remain unchanged except where they intentionally tested zero-admin (unlikely).
  • Audit recommendation L-03 can be marked addressed in audits/audit_smartcontract_1777813071.md or a follow-up changelog note references this issue (maintainer preference).

Verification checklist — PR review (code)

  1. Open each touched .sol file and confirm require(admin != address(0), ...) (or equivalent custom error if the repo adopts Solidity custom errors elsewhere for these modules) appears above _grantRole for the admin parameter used in initialization.
  2. Confirm FeeSink.__FeeSink_init gates all three sink derivatives without duplicating checks in subclasses (unless a subclass introduces a separate admin-like parameter—none today).
  3. Confirm FeeRouter.initialize order: sink validation versus admin check documented; reject tests match actual revert (admin vs sinks).
  4. Grep repo for _grantRole(DEFAULT_ADMIN_ROLE in contracts/src and confirm no initializer path skips the guard.
  5. Run locally:
    cd contracts && FOUNDRY_PROFILE=ci forge test -vv
    (or the exact CI command from audits/audit_smartcontract_1777813071.md § Executive Summary).

Verification checklist — post-merge / release

  1. No production deployment script should pass address(0) as admin; scan environment-driven deploy configs.
  2. If storage-layout or upgrades tooling runs in CI (openzeppelin-upgrades / unsafeAllow manifests), confirm no unintentional bytecode metadata-only churn beyond the initializer guard.
  3. Update internal tracking: close this issue when MR merges and audits doc references the MR / commit.

Rationale summary

Owning OpenZeppelin contracts often embed zero-address checks by convention; AccessControl._grantRole does not treat address(0) as special for admin assignment. Explicit validation aligns deployment safety with the rest of the codebase’s initializer style (many functions already require non-zero economic addresses).

Shell

## Source Tracked from internal smart contract audit: [`audits/audit_smartcontract_1777813071.md`](/audits/audit_smartcontract_1777813071.md) (**L-03: Several AccessControl deployments do not reject zero admin addresses**). ## Problem statement Contracts that use OpenZeppelin **AccessControl** (or enumerable upgradeable variants) assign `DEFAULT_ADMIN_ROLE` via `_grantRole` in constructors / `initializer` flows. Unlike **Ownable**-style contracts that commonly reject `address(0)` in their initializers, these paths currently allow `admin == address(0)`. Granting **`DEFAULT_ADMIN_ROLE` to `address(0)`** does **not** create a usable admin: OpenZeppelin’s role model expects a living account or contract to hold that role so it can grant/revoke other roles and (for UUPS targets) authorize upgrades. A mistaken deployment wiring **zero as admin** yields a governance-dead deployment: withdrawals, governor updates, NFT admin actions, treasury parameter roles, and upgrade authorization may become permanently unreachable except via an emergency upgrade path that itself requires admin—i.e., a brick risk. This is **low severity from an attacker’s perspective**: it is an **operator/deployer footgun**, not an unprivileged fund-stealing path. Impact is misconfiguration leading to irreversible inability to administer the system. ### In-scope deployments (today) | Component | Mechanism | |-----------|-----------| | **Fee sinks (shared base)** | `FeeSink.__FeeSink_init(admin)` grants `DEFAULT_ADMIN_ROLE` + `WITHDRAWER_ROLE` to `admin` | | **Fee sink implementations** | `CL8YProtocolTreasury.initialize`, `DoubLPIncentives.initialize`, `EcosystemTreasury.initialize` delegate to `__FeeSink_init` | | **PodiumPool** | `initialize(admin)` grants `DEFAULT_ADMIN_ROLE` | | **FeeRouter** | `initialize(admin, ...)` grants `DEFAULT_ADMIN_ROLE` + `GOVERNOR_ROLE` | | **RabbitTreasury** | `initialize(..., admin)` grants `DEFAULT_ADMIN_ROLE`, `PARAMS_ROLE`, `PAUSER_ROLE` | | **Doubloon** | `constructor(admin)` grants `DEFAULT_ADMIN_ROLE` only | | **LeprechaunNFT** | `constructor(..., admin)` grants `DEFAULT_ADMIN_ROLE` + `MINTER_ROLE` | ## Recommended fix 1. **Add an explicit invariant at the earliest common point:** - In `contracts/src/sinks/FeeSink.sol`, inside `__FeeSink_init`, add `require(admin != address(0), "<consistent suffix>");` immediately **before** any `_grantRole` calls (covers **`CL8YProtocolTreasury`**, **`DoubLPIncentives`**, **`EcosystemTreasury`** in one place). 2. **Add the same guard to each non-inherited initializer/constructor that grants AccessControl roles:** - `contracts/src/sinks/PodiumPool.sol` → `initialize(address admin)` - `contracts/src/FeeRouter.sol` → `initialize(address admin, ...)` - `contracts/src/RabbitTreasury.sol` → `initialize(..., address admin)` (alongside existing `require` validations) - `contracts/src/tokens/Doubloon.sol` → `constructor(address admin)` - `contracts/src/LeprechaunNFT.sol` → `constructor(..., address admin)` 3. **Revert string policy:** Prefer a short, grep-friendly prefix per contract (e.g. `FeeSink: zero admin`, `PodiumPool: zero admin`, …) to match existing patterns like `FeeSink: zero address` on `withdraw`’s `to`. 4. **No behavior change for valid admins:** Existing tests and deployments with non-zero admin must remain green. Optional hardening (out of narrow L-03 scope but aligned): audit other `initialize`/`constructor` flows that `_grantRole` with a caller-supplied address for the same class of mistake. ## Files expected to change (implementation) ### Solidity (required) - `contracts/src/sinks/FeeSink.sol` - `contracts/src/sinks/PodiumPool.sol` - `contracts/src/FeeRouter.sol` - `contracts/src/RabbitTreasury.sol` - `contracts/src/tokens/Doubloon.sol` - `contracts/src/LeprechaunNFT.sol` ### Tests (recommended) Pick one coherent approach: - **Single focused test module** such as `contracts/test/AccessControlZeroAdmin.t.sol` that deploys minimal instances (direct `new` where non-proxy, proxy + `initialize` via Foundry patterns matching existing tests) and asserts **`vm.expectRevert`** for each contract/surface; **or** - Extend the closest existing files: `contracts/test/FeeSinks.t.sol`, `contracts/test/FeeRouter.t.sol`, `contracts/test/RabbitTreasury.t.sol`, `contracts/test/LeprechaunNFT.t.sol`, plus one assertion that a FeeSink derivative wired through **`UUPSDeployLib`** rejects zero admin. **Regression coverage matrix (each row must revert with zero admin):** 1. `EcosystemTreasury` / `DoubLPIncentives` / `CL8YProtocolTreasury` proxy `initialize(address(0))` 2. `PodiumPool` proxy `initialize(address(0))` 3. `FeeRouter` proxy `initialize(address(0), sinks, weights)` (minimal valid sinks/weights or expect revert before sink validation depending on ordering—**assert ordering** documents which require fires first) 4. `RabbitTreasury` proxy full `initialize` with `admin = address(0)` but otherwise valid params 5. `new Doubloon(address(0))` 6. `new LeprechaunNFT(..., address(0))` ### Deployment scripts (verification only) Production paths like `contracts/script/DeployDev.s.sol` already use a non-zero `deployer`; no functional change strictly required unless you add defensive `require` there for clarity. Optionally add **`assert`**/comments in **`contracts/script/UUPSDeployLib.sol`** mirroring onchain checks for faster failure during dry-run. ### Documentation (recommended alignment) Update or add bullets so operators know zero-admin is forbidden at the **EVM boundary**: - `docs/onchain/fee-routing-and-governance.md` (FeeRouter admin / governor semantics) - `docs/testing/strategy.md` or `docs/testing/invariants-and-business-logic.md` — add a named invariant / test-map row for **“AccessControl deployments reject `admin == address(0)`”** - Optionally cross-reference in `contracts/PARAMETERS.md` or ops deployment checklist if admin wiring is documented there ## Acceptance criteria - [ ] Every listed contract rejects `admin == address(0)` at constructor or `initializer`, **before** any `_grantRole` that assigns `DEFAULT_ADMIN_ROLE` (and before co-granted roles in the same block: `WITHDRAWER_ROLE`, `GOVERNOR_ROLE`, `PARAMS_ROLE`, `PAUSER_ROLE`, `MINTER_ROLE` as applicable). - [ ] **`FOUNDRY_PROFILE=ci`** (or repo-default CI recipe) **`forge test`** passes with new regression tests included. - [ ] Existing integration tests (`DevStackIntegration`, `TimeCurve*`, `FeeRouter*`, etc.) remain unchanged except where they intentionally tested zero-admin (unlikely). - [ ] Audit recommendation L-03 can be marked addressed in `audits/audit_smartcontract_1777813071.md` **or** a follow-up changelog note references this issue (maintainer preference). ## Verification checklist — PR review (code) 1. Open each touched `.sol` file and confirm `require(admin != address(0), ...)` (or equivalent custom error if the repo adopts Solidity custom errors elsewhere for these modules) appears **above** `_grantRole` for the admin parameter used in initialization. 2. Confirm **`FeeSink.__FeeSink_init`** gates all three sink derivatives without duplicating checks in subclasses (unless a subclass introduces a separate admin-like parameter—none today). 3. Confirm **`FeeRouter.initialize`** order: sink validation versus admin check documented; reject tests match actual revert (admin vs sinks). 4. Grep repo for **`_grantRole(DEFAULT_ADMIN_ROLE`** in `contracts/src` and confirm no initializer path skips the guard. 5. Run locally: `cd contracts && FOUNDRY_PROFILE=ci forge test -vv` (or the exact CI command from `audits/audit_smartcontract_1777813071.md` § Executive Summary). ## Verification checklist — post-merge / release 1. No production deployment script should pass **`address(0)`** as admin; scan environment-driven deploy configs. 2. If storage-layout or upgrades tooling runs in CI (`openzeppelin-upgrades` / `unsafeAllow` manifests), confirm no unintentional bytecode metadata-only churn beyond the initializer guard. 3. Update internal tracking: close this issue when MR merges and audits doc references the MR / commit. ## Rationale summary Owning OpenZeppelin contracts often embed zero-address checks by convention; **`AccessControl._grantRole` does not** treat `address(0)` as special for admin assignment. Explicit validation aligns deployment safety with the rest of the codebase’s initializer style (many functions already **`require`** non-zero economic addresses). Shell
PlasticDigits commented 2026-05-04 05:27:04 +00:00 (Migrated from gitlab.com)

mentioned in commit deeb851d20

mentioned in commit deeb851d20e0259cf2691af3f5c022c00f6ac65e
PlasticDigits commented 2026-05-04 05:27:04 +00:00 (Migrated from gitlab.com)

mentioned in commit 717c7fd6a1

mentioned in commit 717c7fd6a1e34b66ec0fa8850b5250ddb6a37164
PlasticDigits commented 2026-05-04 05:38:31 +00:00 (Migrated from gitlab.com)

mentioned in commit ebbf58ed93

mentioned in commit ebbf58ed9379559e94cc4855ca2e2b343ae18c70
Brouie commented 2026-05-04 06:18:14 +00:00 (Migrated from gitlab.com)

verified on commit deeb851 (merged via 717c7fd). new test file contracts/test/AccessControlZeroAdmin.t.sol covers all 8 affected contracts from your L-03 list:

  • test_RabbitTreasury_zeroAdmin_reverts
  • test_FeeRouter_zeroAdmin_reverts_before_sink_validation — bonus: confirms zero-admin rejection fires before sink validation, so deployer doesn't waste gas / get a misleading sink revert
  • test_PodiumPool_zeroAdmin_reverts
  • test_Doubloon_zeroAdmin_reverts
  • test_LeprechaunNFT_zeroAdmin_reverts
  • test_CL8YProtocolTreasury_zeroAdmin_reverts
  • test_DoubLPIncentives_zeroAdmin_reverts
  • test_EcosystemTreasury_zeroAdmin_reverts (FeeSink derivative)

deployment footgun closed — accidentally passing address(0) as initial admin now reverts at constructor/initializer time on every AccessControl contract in the system. all 8 pass clean.

verified on commit `deeb851` (merged via `717c7fd`). new test file `contracts/test/AccessControlZeroAdmin.t.sol` covers all 8 affected contracts from your L-03 list: - `test_RabbitTreasury_zeroAdmin_reverts` - `test_FeeRouter_zeroAdmin_reverts_before_sink_validation` — bonus: confirms zero-admin rejection fires before sink validation, so deployer doesn't waste gas / get a misleading sink revert - `test_PodiumPool_zeroAdmin_reverts` - `test_Doubloon_zeroAdmin_reverts` - `test_LeprechaunNFT_zeroAdmin_reverts` - `test_CL8YProtocolTreasury_zeroAdmin_reverts` - `test_DoubLPIncentives_zeroAdmin_reverts` - `test_EcosystemTreasury_zeroAdmin_reverts` (FeeSink derivative) deployment footgun closed — accidentally passing `address(0)` as initial admin now reverts at constructor/initializer time on every AccessControl contract in the system. all 8 pass clean.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-04 08:18:21 +00:00
PlasticDigits commented 2026-05-05 05:53:15 +00:00 (Migrated from gitlab.com)

mentioned in commit a073b2c6a1

mentioned in commit a073b2c6a182bb64462ee618b804637ded631888
PlasticDigits (Migrated from gitlab.com) reopened this issue 2026-05-05 05:57:42 +00:00
PlasticDigits commented 2026-05-05 05:57:46 +00:00 (Migrated from gitlab.com)

Follow-up (merged to main): derived-layer docs for GitLab #120

On-chain INV-AC-ZERO-ADMIN-120 was already verified elsewhere; this pass documents indexer + SPA boundaries so third-party agents do not expect logs or UI signals for a reverted zero-admin deploy.

What changed

  • Named invariants: INV-INDEXER-120-DEPLOY (no Postgres/API row for deploy-time revert) and INV-FRONTEND-120-DEPLOY (no bootstrap DEFAULT_ADMIN_ROLE probe).
  • Cross-links: docs/testing/invariants-and-business-logic.md §120, indexer/README.md, docs/indexer/design.md, docs/frontend/wallet-connection.md, docs/onchain/fee-routing-and-governance.md (anchor #deployer-evm-boundary-gitlab-120), docs/testing/manual-qa-checklists.md (#120), docs/testing/strategy.md, skills/README.md, .cursor/skills/yieldomega-guardrails/SKILL.md.

Verification checklist for @brouie

  • Invariants doc: indexer + frontend table rows cite #120 + link to INV-*-120-DEPLOY.
  • Indexer README + design: § explains successful tx only — failed initializer emits nothing to decode.
  • Wallet-connection: § states SPA assumes sane deploy + Forge coverage; indexer cannot infer zero admin from emptiness alone.
  • Manual QA #120 row runs: cd contracts && FOUNDRY_PROFILE=ci forge test --match-path test/AccessControlZeroAdmin.t.sol -vv.
  • Participant index skills/README.md still points contributors at the fuller anchor graph.

Issue left open for your sign-off. Commit on main: a073b2c.

## Follow-up (merged to `main`): derived-layer docs for GitLab #120 On-chain **`INV-AC-ZERO-ADMIN-120`** was already verified elsewhere; this pass **documents indexer + SPA boundaries** so third-party agents do not expect logs or UI signals for a reverted zero-admin deploy. ### What changed - **Named invariants:** `INV-INDEXER-120-DEPLOY` (no Postgres/API row for deploy-time revert) and `INV-FRONTEND-120-DEPLOY` (no bootstrap `DEFAULT_ADMIN_ROLE` probe). - **Cross-links:** `docs/testing/invariants-and-business-logic.md` §120, `indexer/README.md`, `docs/indexer/design.md`, `docs/frontend/wallet-connection.md`, `docs/onchain/fee-routing-and-governance.md` (anchor `#deployer-evm-boundary-gitlab-120`), `docs/testing/manual-qa-checklists.md` (#120), `docs/testing/strategy.md`, `skills/README.md`, `.cursor/skills/yieldomega-guardrails/SKILL.md`. ### Verification checklist for @brouie - [ ] Invariants doc: indexer + frontend table rows cite #120 + link to `INV-*-120-DEPLOY`. - [ ] Indexer README + design: § explains **successful tx only** — failed `initializer` emits nothing to decode. - [ ] Wallet-connection: § states SPA assumes sane deploy + Forge coverage; indexer cannot infer zero admin from emptiness alone. - [ ] Manual QA #120 row runs: `cd contracts && FOUNDRY_PROFILE=ci forge test --match-path test/AccessControlZeroAdmin.t.sol -vv`. - [ ] Participant index `skills/README.md` still points contributors at the fuller anchor graph. Issue left **open** for your sign-off. Commit on `main`: a073b2c.
Brouie commented 2026-05-05 07:11:38 +00:00 (Migrated from gitlab.com)

@PlasticDigits — verification PASS on a073b2c derived-layer docs at HEAD 435a446. walked your checklist:

  • invariants doc rows (docs/testing/invariants-and-business-logic.md L117 indexer row + L118 frontend row): both cite #120 and link to INV-INDEXER-120-DEPLOY / INV-FRONTEND-120-DEPLOY respectively. full INV-INDEXER-120-DEPLOY definition at L796 with cross-links to AccessControlZeroAdmin.t.sol, indexer README, and indexer design doc. INV-FRONTEND-120-DEPLOY definition at L800 with cross-links to wallet-connection.md and the manual QA checklist.
  • indexer README + design (indexer/README.md L46, docs/indexer/design.md L11): both anchor #accesscontrol-zero-admin-gitlab-120. README correctly frames the operational safety as Forge coverage + successful bytecode ("not missing rows in Postgres"). design doc explicitly states the indexer's job starts at successful bytecode at ADDRESS_REGISTRY addresses — no decode of failed initializer reverts.
  • wallet-connection (docs/frontend/wallet-connection.md L19 + L21): correctly states the SPA does NOT add a runtime probe for DEFAULT_ADMIN_ROLE and trusts VITE_* registry addresses against already-live bytecode. miswired env surfaces as RPC reverts / empty reads, not a dedicated badge.
  • fee-routing (docs/onchain/fee-routing-and-governance.md L65 + L67): #deployer-evm-boundary-gitlab-120 anchor present, lists all 8 contracts (FeeRouter, PodiumPool, FeeSink + 3 derivatives, RabbitTreasury, Doubloon, LeprechaunNFT) and cross-references both derived invariants.
  • manual QA #120 row (docs/testing/manual-qa-checklists.md L595-604): both INV-INDEXER-120-DEPLOY and INV-FRONTEND-120-DEPLOY rows present, doc-map links resolve.

Forge regression confirmed:

  • forge test --match-path test/AccessControlZeroAdmin.t.sol: 8/8 PASS — test_RabbitTreasury_zeroAdmin_reverts, test_FeeRouter_zeroAdmin_reverts_before_sink_validation (sink-validation ordering check still green), test_PodiumPool_zeroAdmin_reverts, test_Doubloon_zeroAdmin_reverts, test_LeprechaunNFT_zeroAdmin_reverts, test_CL8YProtocolTreasury_zeroAdmin_reverts, test_DoubLPIncentives_zeroAdmin_reverts, test_EcosystemTreasury_zeroAdmin_reverts.

derived-layer docs read as a clean boundary statement — third-party agents will not expect Postgres rows or UI signals for a reverted zero-admin deploy. matches the actual semantics: the EVM boundary catches it, observability layers stay quiet by design.

ready for close on your end.

@PlasticDigits — verification PASS on `a073b2c` derived-layer docs at HEAD `435a446`. walked your checklist: - **invariants doc rows** (`docs/testing/invariants-and-business-logic.md` L117 indexer row + L118 frontend row): both cite #120 and link to `INV-INDEXER-120-DEPLOY` / `INV-FRONTEND-120-DEPLOY` respectively. full `INV-INDEXER-120-DEPLOY` definition at L796 with cross-links to `AccessControlZeroAdmin.t.sol`, indexer README, and indexer design doc. `INV-FRONTEND-120-DEPLOY` definition at L800 with cross-links to `wallet-connection.md` and the manual QA checklist. - **indexer README + design** (`indexer/README.md` L46, `docs/indexer/design.md` L11): both anchor `#accesscontrol-zero-admin-gitlab-120`. README correctly frames the operational safety as Forge coverage + successful bytecode ("not missing rows in Postgres"). design doc explicitly states the indexer's job starts at successful bytecode at `ADDRESS_REGISTRY` addresses — no decode of failed initializer reverts. - **wallet-connection** (`docs/frontend/wallet-connection.md` L19 + L21): correctly states the SPA does NOT add a runtime probe for `DEFAULT_ADMIN_ROLE` and trusts `VITE_*` registry addresses against already-live bytecode. miswired env surfaces as RPC reverts / empty reads, not a dedicated badge. - **fee-routing** (`docs/onchain/fee-routing-and-governance.md` L65 + L67): `#deployer-evm-boundary-gitlab-120` anchor present, lists all 8 contracts (FeeRouter, PodiumPool, FeeSink + 3 derivatives, RabbitTreasury, Doubloon, LeprechaunNFT) and cross-references both derived invariants. - **manual QA #120 row** (`docs/testing/manual-qa-checklists.md` L595-604): both `INV-INDEXER-120-DEPLOY` and `INV-FRONTEND-120-DEPLOY` rows present, doc-map links resolve. Forge regression confirmed: - `forge test --match-path test/AccessControlZeroAdmin.t.sol`: 8/8 PASS — `test_RabbitTreasury_zeroAdmin_reverts`, `test_FeeRouter_zeroAdmin_reverts_before_sink_validation` (sink-validation ordering check still green), `test_PodiumPool_zeroAdmin_reverts`, `test_Doubloon_zeroAdmin_reverts`, `test_LeprechaunNFT_zeroAdmin_reverts`, `test_CL8YProtocolTreasury_zeroAdmin_reverts`, `test_DoubLPIncentives_zeroAdmin_reverts`, `test_EcosystemTreasury_zeroAdmin_reverts`. derived-layer docs read as a clean boundary statement — third-party agents will not expect Postgres rows or UI signals for a reverted zero-admin deploy. matches the actual semantics: the EVM boundary catches it, observability layers stay quiet by design. ready for close on your end.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-05 07:15:17 +00:00
PlasticDigits commented 2026-05-13 15:13:32 +00:00 (Migrated from gitlab.com)

mentioned in commit 617a90de47

mentioned in commit 617a90de47184a2e8bd9fb599dce2e2bc81f28ca
PlasticDigits commented 2026-05-13 16:07:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #207

mentioned in issue #207
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#120
No description provided.