Impairment state machine
Impairment state machine — CreatorOVault
Onchain side-pocket for a failed strategy. Facade ABI is on CreatorOVault; transitions run in CreatorOVaultCoreModule via delegatecall. Companion: Impairment v1 disclosures.
States
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). |
Constructor defaults: Normal, impairmentChallengeWindow = 1 days, maxImpairmentTripDuration = 14 days (bounds 3–30 days), maxImpairmentChallengesPerEpoch = 3. impairmentChallengeBond = 0 disables the ETH bond.
Who may 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
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
#157050: a false-alarm or stale clear starts a re-trip cooldown of maxImpairmentTripDuration so the authority cannot clear+re-trip to refresh the stale deadline forever.
ODA-497-2: clearStaleImpairmentTrip is refused while snapshotRoot != 0.
Errors (transition-critical)
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
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.