You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ab51167
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/access-control.md
+9-5Lines changed: 9 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -83,7 +83,7 @@ bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
83
83
bytes32 public constant PARAM_ROLE = keccak256("PARAM_ROLE");
84
84
```
85
85
86
-
This contract uses a `governor` address + OpenZeppelin `AccessControl`-style `bytes32` role constants. It is **NOT** imported by `AdminModule`, `QueueModule`, `ERC4626Module`, or any active module on branch `reorg/runbook-docs-consolidate-01a.3`. The production role system is the `roleOf[selector]` mapping described above.
86
+
This contract uses a `governor` address + OpenZeppelin `AccessControl`-style `bytes32` role constants. It is **NOT** imported by `AdminModule`, `EpochedQueueModule`, `ERC4626Module`, or any active module. The production role system is the `roleOf[selector]` mapping described above.
|`acceptOwnership`| AdminModule | Must be `pendingOwner` (checked internally) |
191
195
|`markMatured`| FixedMaturityModule | Any address, once maturityTs reached |
192
196
|`markFundingFailed`| FixedMaturityModule | Any address, once deadline + net < min |
@@ -245,7 +249,7 @@ struct Layout {
245
249
246
250
These functions bypass normal ERC-20 transfer logic and directly adjust share balances. They are used by:
247
251
248
-
-**QueueModule** — burns user shares during queue settlement, mints fee shares to `feeCollector`
252
+
-**EpochedQueueModule** — burns escrowed shares as each claim is paid, transfers fee shares to `feeCollector` in one batch at epoch close, mints perf-fee shares on crystallization
249
253
-**FixedMaturityModule** — mints/burns during lifecycle transitions (e.g., `_applyFinalPerformanceFee`)
|`EpochQueueStorage`|`src/core/modules/EpochedQueueModule.sol:29`|Epoch/claim state, escrowedShares, outstandingClaimCount (retired `QueueStorage.sol` kept only as a reserved EIP-7201 slot, no longer in scope for live logic)|
@@ -172,8 +172,8 @@ Total: 51 `.sol` files in `src/core/`.
172
172
| L1 |**Owner key is single point of control**| Design choice | No on-chain DAO. Mitigated by vetoer + timelock. Deployment to multi-sig (Safe) is recommended. |
173
173
| L2 |**`FLAG_SYSTEM_SEALED` does not freeze `roleOf[selector]`**| Known gap (AC8) | Owner can change per-function roles post-seal. Timelock provides recourse window. |
174
174
| L3 |**`forceWithdrawAll` is best-effort (F-03: resolved)**| Design choice, mitigated | Delivers `min(hot, targetAssets)` — still no guarantee of full liquidity. Previously this could silently deliver an arbitrarily small fill (up to ~90%+ of value if strategies were frozen/illiquid); now a mandatory `minAssetsOut` parameter reverts the whole call (`SlippageExceeded`, no state change) if the fill falls short. See `test/sprint-test/ForceWithdrawAll_SlippagePOC.t.sol`. |
175
-
| L4 |**Settlement loop is partial**| Design choice | Gas safety exit at `gasleft() > 150_000`. Queue resumes in next call. Settlement is not atomic for large queues. |
176
-
| L5 |**INSTANT fallback stores `immediate=false`**| Design choice |INSTANT requests that fall back to queue are re-classified as standard queue entries (no epoch cap). Fixed in shadow report (BUG 6). |
175
+
| L4 |**Settlement is epoch-wide, pull-based**| Design choice (superseded L4) |`EpochedQueueModule` replaced the retired per-claim FIFO settle loop (gas-bounded `_settleLoop`) with `closeCurrentEpoch()`/`fundEpoch()` (epoch-wide, O(1) in claim count) and `claimEpochAssets()` (pull-based, per user). No gas-safety partial-exit is needed since no single call iterates over claims. |
176
+
| L5 |**INSTANT fallback always becomes a standard epoch claim**| Design choice |`requestInstantWithdrawal` requests that fail `_canInstant()`fall back to the exact same code path as `requestEpochWithdrawal` — there is no `immediate` flag on `EpochClaim` to mis-set (the BUG 6 class in the retired `QueueModule` is eliminated by construction, not by a fix). |
177
177
| L6 |**`preMaturityForceExitPenaltyBps` max 50%**| Design constraint | Hard cap at 5000 bps validated in `configureFixedMaturity`. |
178
178
| L7 |**`BatchGuardrails.sol` is peripheral**| Design choice | Not part of CoreVault module dispatch. Separate validation layer, not enforced at core level. |
179
179
| L8 |**`Roles.sol` and `Ownable2StepMixin.sol` are legacy**| Legacy artifact | pragma 0.8.24, NOT imported by active modules. Present in codebase but not deployed. |
@@ -184,6 +184,11 @@ Total: 51 `.sol` files in `src/core/`.
184
184
185
185
The Final Shadow Readiness Report (2026-04-10) found 7 bugs during a 1950-test, 10K-operation fork replay. All 7 were fixed before the first audit. Regression tests for each are in `test/unit/core/AuditFix_Regression.t.sol`.
186
186
187
+
> **Note**: BUG 4, 6, and 7 below refer to `QueueModule.sol`, which has since been deleted and
188
+
> replaced by `EpochedQueueModule.sol` (epoch-batched settlement) — see §9 for why the new
189
+
> module's settlement path should be treated as fresh audit surface rather than a pure
190
+
> regression check against these historical findings.
191
+
187
192
| Bug | Severity | File fixed | What was wrong |
188
193
|-----|---------|------------|---------------|
189
194
| BUG 1 | P0 |`ERC4626Module.sol`|`previewDeposit` returned gross instead of net (OZ non-compliance) |
@@ -213,7 +218,7 @@ These are known and accepted before the first audit:
| T1 |**Owner key compromise**| HIGH | Timelock delay + vetoer revoke window. `freezeParams` makes fees immutable post-hardening. | GI-3,4; governance unit tests |
264
269
| T2 |**Share price manipulation via donation**| HIGH | Fixed in shadow report (BUG 3: non-dilutive fee transfer). Dead deposit seeds baseline. | SI-2; `AuditFix_Regression`; `SharePriceCollapse_Security.t.sol`|
265
-
| T3 |**Queue exhaustion / gas DoS**| MEDIUM |Gas safety exit at 150K remaining. Partial settlement resumes in next call. `compactQueue` callable by anyone. | SI-5; `ExitEngine_StressTest`; e2e|
270
+
| T3 |**Queue exhaustion / gas DoS**| MEDIUM |`fundEpoch()` liquidity pull is O(1) regardless of claim count (single call per epoch, not a per-claim scan). Settlement is pull-based (`claimEpochAssets`) so an individual user's claim cost never depends on other users' claims. | SI-5; `ExitEngine_StressTest`; `Hardening_GasAndChaos` gas characterization|
266
271
| T4 |**Reentrancy via strategy callback**| MEDIUM |`FLAG_REENTRANCY_LOCKED` guards `deployToStrategies`, `rebalanceStrategies`. W2 rule: external calls are try/catch. |`CoreVault_DiamondLite_Reentrancy`; `ForceWithdraw_Reentrancy`|
267
272
| T5 |**EIP-7201 storage slot collision**| HIGH (mitigated) | 4 namespaces verified via `EIP7201Compliance.t.sol` (5 tests). Fixed post-FINDING-OOS-03. |`test/security/EIP7201Compliance.t.sol`|
268
-
| T6 |**Unauthorized processorMint/Burn**| CRITICAL |`isAuthorizedModule[addr]` gate — only QueueModule and FixedMaturityModule authorized at deploy. |`CoreVault_DiamondLite_AccessControl`|
273
+
| T6 |**Unauthorized processorMint/Burn**| CRITICAL |`isAuthorizedModule[addr]` gate — only EpochedQueueModule and FixedMaturityModule authorized at deploy. |`CoreVault_DiamondLite_AccessControl`|
269
274
| T7 |**FixedMaturity capital lock**| MEDIUM |`markMatured()` is permissionless; `preMaturityForceExitPenaltyBps ≤ 50%` hard cap. `forceWithdrawAll` available, now with a `minAssetsOut` floor (F-03). | FM invariant tests; `ForceWithdraw_*`|
270
275
| T8 |**Fee parameter ratchet via short timelock**| MEDIUM | H3: post-seal min delay floor 1 day. Guardian can pause while vetoer revokes. |`CoreVault_ParamTimelock`; `Governance_Seal_Invariants`|
271
276
@@ -275,7 +280,7 @@ Cross-referenced to test files for auditor traceability:
275
280
276
281
Priority ranking based on value at risk and complexity:
277
282
278
-
1.**Settlement loop and share accounting** (CRITICAL): `QueueModule._settleLoop`, `_convertToAssetsCached`, `_crystallize`. Four shadow-report HIGH bugs were found here. The PPS must remain exact across all settlement sequences.
283
+
1.**Settlement and share accounting** (CRITICAL): `EpochedQueueModule.closeCurrentEpoch`, `fundEpoch`, `claimEpochAssets`, `_crystallize`. This is a substantially rewritten implementation (epoch-batched, not per-claim FIFO) that replaced the retired `QueueModule` post-first-audit — it has NOT yet been through the same shadow-report fork-replay process that found the four HIGH bugs listed in §5.1 for the old module. `_crystallize` was ported verbatim and inherits BUG-4-equivalent regression coverage; the epoch close/fund/claim path is new and should be treated as fresh audit surface, not a regression check. PPS must remain exact — locked once at `closeCurrentEpoch()`, applied identically to every claim in that epoch.
279
284
280
285
2.**ERC-4626 compliance at boundaries** (HIGH): `previewDeposit`, `previewWithdraw`, `mint` fee symmetry with `deposit`. Boundary conditions (totalAssets=0, totalSupply=0) and mint/deposit equivalence.
281
286
@@ -293,13 +298,15 @@ Priority ranking based on value at risk and complexity:
293
298
294
299
The following specific checks are recommended based on the internal shadow report and architecture review:
-[ ] Verify `_convertToAssetsCached` uses snapshot BEFORE `_burn`(BUG 4 regression)
298
-
-[ ] Verify `immediate=false` for all queued claims (BUG 6 regression)
301
+
**Settlement: close / fund / claim** (`src/core/modules/EpochedQueueModule.sol:327-513`):
302
+
-[ ] Verify `ppsAtClose` is computed BEFORE any shares are burned, and never recomputed after (BUG 4-equivalent regression, new code path)
303
+
-[ ] Verify every `EpochClaim` is treated identically regardless of how it was created (`requestEpochWithdrawal` vs. `requestInstantWithdrawal` fallback) — there is no `immediate` flag to mis-set (BUG 6 class eliminated by construction, verify no reintroduction)
299
304
-[ ] Verify `feeShares` rounded UP (rounding in favour of protocol)
300
-
-[ ] Verify `cachedTA / cachedTS` snapshot is set once per batch (deterministic PPS)
-[ ] Verify INSTANT cap consumption: only for INSTANT-mode, never STANDARD/FORCE
305
+
-[ ] Verify `ppsAtClose` is locked once per epoch at `closeCurrentEpoch()` and used identically by every claim in that epoch regardless of claim order (deterministic PPS)
306
+
-[ ] Verify `fundEpoch()` only transitions to `Funded` when `hot >= totalNetAssets` — no partial-funding state can be marked `Funded`
307
+
-[ ] Verify `outstandingClaimCount` persists across `closeCurrentEpoch()` (does not reset like per-epoch `claimCount`) — dynamic-cap bypass class, already fixed pre-cutover, verify no regression
308
+
-[ ] Verify INSTANT cap consumption: only for the settled-immediately path in `requestInstantWithdrawal`, never for STANDARD claims at any point in their lifecycle
309
+
-[ ] Verify `_notifyIncentivesExit` gas cost per claim stays well under keeper gas limits (BUG 7 was a QueueModule/IncentivesEngine issue — confirm the same call pattern in `EpochedQueueModule` doesn't reintroduce it)
303
310
304
311
**Deposit and mint** (`src/core/modules/ERC4626Module.sol:166`):
0 commit comments