SpecificationsImpairment state machine
Impairment state machine
The states, roles, transitions, and errors behind CreatorOVault impairment, for integrators, auditors, and contract developers.
This is the onchain side-pocket for a failed strategy in CreatorOVault. It is written for developers and auditors; holders should start with Impairment v1 disclosures.
The facade ABI is on CreatorOVault; transitions run in CreatorOVaultCoreModule via delegatecall. This machine ships with impairment v1 for new vault launches, and stays in Normal unless a strategy is tripped.
The lifecycle in words
- A guardian or the owner trips impairment on a strategy. The vault moves to
Suspectand freezes deposits and withdrawals. - Management proposes a Merkle root of who gets claims, which opens a challenge window.
- Anyone may challenge the root during that window; management rejects the challenge or clears the root.
- After the window, management finalizes. Claims can then be minted, keepers notify recoveries, and holders claim them.
- Once recovery accounting closes, the epoch is resolved and the vault returns toward
Normal.
A false alarm, or a trip left too long without a root, can be cleared back to Normal instead.
States
The vault has a mode, and each impairment epoch has its own status.
Vault mode
| Mode | Meaning |
|---|---|
Normal | Clean book. ERC-4626 ingress/egress live (subject to pause/shutdown/whitelist). |
Suspect | Active tripped epoch. deposit / mint / withdraw / redeem revert. maxDeposit / maxMint / maxWithdraw / maxRedeem return 0. |
Epoch status (ImpairmentEpoch.status)
| Status | Meaning |
|---|---|
None | Unused id. |
Tripped | Guardian/owner froze a strategy. Optional Merkle root + challenge window. |
Finalized | Root locked. Claims can mint. Recovery can notify/claim. |
Resolved | Recovery accounting closed. Root remains claimable (false-alarm clear does not). |
Defaults
Constructor defaults: Normal, impairmentChallengeWindow = 1 days, maxImpairmentTripDuration = 14 days (bounds 3–30 days), maxImpairmentChallengesPerEpoch = 3. impairmentChallengeBond = 0 disables the ETH bond.
Who may call
Each transition is restricted to one role, except the liveness and claim paths, which anyone can call.
| Actor | Transitions |
|---|---|
owner ∪ impairmentGuardian | tripImpairment, clearImpairmentTrip |
management | proposeImpairmentRoot, rejectImpairmentChallenge, clearImpairmentRootAfterChallenge, finalizeImpairment, reinstateImpairedStrategy |
keeper | notifyImpairmentRecovery |
| Anyone | clearStaleImpairmentTrip, challengeImpairmentRoot, mintImpairmentClaim, claimImpairmentRecovery |
Config (setImpairment*) is admin/owner on the facade → admin module. Not part of the epoch graph.
Transitions
The full graph, with the guard and revert reasons for each call.
Normal
│ tripImpairment(strategy, reasonCode)
│ onlyImpairmentAuthorized
│ revert: VaultNotNormal, ImpairmentAlreadyActive,
│ StrategyAlreadyImpaired, ImpairmentRetripCooldown,
│ InvalidImpairmentReason
▼
Suspect + epoch.Tripped
│
├─ clearImpairmentTrip(epochId) authorized
│ zeroes root / claim supply / recovery asset
│ starts re-trip cooldown = maxImpairmentTripDuration
│ → Normal
│
├─ clearStaleImpairmentTrip(epochId) permissionless
│ after trippedAt + maxImpairmentTripDuration
│ revert if snapshotRoot != 0 (use challenge / clear-root)
│ also starts re-trip cooldown
│ → Normal
│
└─ proposeImpairmentRoot(epochId, root, totalClaimSupply, recoveryAsset)
onlyManagement
challenge unlock must be < stale deadline
│
├─ challengeImpairmentRoot(epochId, reason) payable, anyone
│ during challenge window; bond + per-epoch cap
│ ├─ rejectImpairmentChallenge management (slash / keep bond)
│ └─ clearImpairmentRootAfterChallenge management → root cleared
│ then re-propose or clear trip
│
└─ finalizeImpairment(epochId) management
after window, no live challenge
▼
epoch.Finalized
├─ mintImpairmentClaim(epochId, account, amount, proof)
├─ notifyImpairmentRecovery(epochId, amount) keepers
└─ claimImpairmentRecovery(epochId, receiver, units)
→ epoch.Resolved (vault returns toward Normal)
reinstateImpairedStrategy once book is clean
Liveness guards
#157050: a false-alarm or stale clear starts a re-trip cooldown ofmaxImpairmentTripDurationso the authority cannot clear+re-trip to refresh the stale deadline forever.- ODA-497-2:
clearStaleImpairmentTripis refused whilesnapshotRoot != 0.
Errors (transition-critical)
Custom errors the transitions can revert with.
VaultNotNormal, VaultNotSuspect, NoActiveImpairment, ImpairmentAlreadyActive, InvalidImpairmentEpoch, InvalidImpairmentTransition, StrategyAlreadyImpaired, StrategyNotImpaired, InvalidImpairmentReason, ImpairmentRootNotReady, ImpairmentChallengeWindowClosed, ImpairmentRootRequired, ImpairmentRootAlreadyFinalized, ImpairmentRootWouldExceedStaleDeadline, ImpairmentRootBlocksStaleClear, ImpairmentRootChallengedErr, ChallengeWindowNotConfigured, ClaimAlreadyMinted, InvalidClaimProof, NothingToClaim, RecoveryEscrowNotConfigured, ClaimSupplyExceeded, ClaimSupplyExceedsTripShares, InvalidImpairmentTripDuration, InvalidImpairmentChallengeWindow, ImpairmentTripNotStale, ImpairmentRetripCooldown, ImpairmentChallengeCapExceeded, ImpairmentRootAlreadyChallenged, InsufficientChallengeBond, ImpairmentChallengeBondTransferFailed, NoActiveImpairmentChallenge, InvalidMaxImpairmentChallenges.
Events
Events to index for tracking an epoch.
ImpairmentTripped, ImpairmentTripCleared, ImpairmentTripClearedByTimeout, ImpairmentRootProposed, ImpairmentRootChallenged, ImpairmentRootCleared, ImpairmentRootFinalized, ImpairmentFinalized, ImpairmentRecoveryNotified, ImpairmentRecoveryClaimed, ImpairmentResolved, ImpairmentChallengeBondSlashed, ImpairmentChallengeBondRefunded, ImpairmentChallengeBondRefundFailed, plus config updates (ImpairmentChallengeWindowUpdated, …).
Test checklist
Existing coverage lives in test/CreatorOVault.ImpairmentV1.t.sol. Any change to this machine should keep these properties green:
- Happy path. trip → propose root → wait window → finalize → mint claim → notify recovery → claim → resolved. Vault returns to
Normal. Impaired strategy excluded fromtotalAssetsuntil reinstated. - False alarm.
clearImpairmentTripreturns toNormal, zeroes root/claim surface (ImpairmentRootRequiredafter clear). Re-trip before cooldown revertsImpairmentRetripCooldown. - Stale liveness. After
maxImpairmentTripDuration, anyone canclearStaleImpairmentTripifsnapshotRoot == 0. While a root is proposed, stale-clear reverts (ImpairmentRootBlocksStaleClear). - Challenge game. Bond too low / window closed / cap exceeded / already challenged revert. Management can reject or clear-root after challenge. Finalize blocked while challenged.
- Finalization gates. Cannot finalize before unlock. Cannot mint before finalize.
totalClaimSupplyis a hard mint cap (ClaimSupplyExceeded). - Recovery isolation. Escrow rejects claims past recovered-for-that-epoch. Creator-coin recovery uses tracked-balance so clean-book
totalAssetsdoes not double-count. - Suspect freeze. While
Suspect,max*are 0 and sync ERC-4626 in/out revert. - Auth. Non-guardian cannot trip. Non-management cannot propose/finalize.
clearStaleImpairmentTripstays permissionless.
Do not add product methods for new impairment features on CreatorOVault — extend the core module and keep the facade as a _delegate ABI.