Skip to main content

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

ModeMeaning
NormalClean book. ERC-4626 ingress/egress live (subject to pause/shutdown/whitelist).
SuspectActive tripped epoch. deposit / mint / withdraw / redeem revert. maxDeposit / maxMint / maxWithdraw / maxRedeem return 0.

Epoch status (ImpairmentEpoch.status)

StatusMeaning
NoneUnused id.
TrippedGuardian/owner froze a strategy. Optional Merkle root + challenge window.
FinalizedRoot locked. Claims can mint. Recovery can notify/claim.
ResolvedRecovery 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

ActorTransitions
ownerimpairmentGuardiantripImpairment, clearImpairmentTrip
managementproposeImpairmentRoot, rejectImpairmentChallenge, clearImpairmentRootAfterChallenge, finalizeImpairment, reinstateImpairedStrategy
keepernotifyImpairmentRecovery
AnyoneclearStaleImpairmentTrip, 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:

  1. Happy path. trip → propose root → wait window → finalize → mint claim → notify recovery → claim → resolved. Vault returns to Normal. Impaired strategy excluded from totalAssets until reinstated.
  2. False alarm. clearImpairmentTrip returns to Normal, zeroes root/claim surface (ImpairmentRootRequired after clear). Re-trip before cooldown reverts ImpairmentRetripCooldown.
  3. Stale liveness. After maxImpairmentTripDuration, anyone can clearStaleImpairmentTrip if snapshotRoot == 0. While a root is proposed, stale-clear reverts (ImpairmentRootBlocksStaleClear).
  4. Challenge game. Bond too low / window closed / cap exceeded / already challenged revert. Management can reject or clear-root after challenge. Finalize blocked while challenged.
  5. Finalization gates. Cannot finalize before unlock. Cannot mint before finalize. totalClaimSupply is a hard mint cap (ClaimSupplyExceeded).
  6. Recovery isolation. Escrow rejects claims past recovered-for-that-epoch. Creator-coin recovery uses tracked-balance so clean-book totalAssets does not double-count.
  7. Suspect freeze. While Suspect, max* are 0 and sync ERC-4626 in/out revert.
  8. Auth. Non-guardian cannot trip. Non-management cannot propose/finalize. clearStaleImpairmentTrip stays permissionless.

Do not add product methods for new impairment features on CreatorOVault — extend the core module and keep the facade as a _delegate ABI.