-
Notifications
You must be signed in to change notification settings - Fork 0
02 smart contracts
Coverage contracts are the policy and payout application layer over Catalysis Core. They are responsible for policy issuance semantics, premium collection/routing, and claim execution, while Core handles committee stake and SSP-level execution.
graph LR
CPF[CoverPoolFactory]
CP[CoverPool]
PM[PolicyManager]
CM[ClaimManager]
PRM[PremiumManager]
SR[SpecRegistry]
SW[Swapper]
UV3[UniswapV3Adapter]
CVW[CoveredVaultWrapper]
PF[ChainlinkPriceFeed]
CPF --> CP
PM --> CP
CP --> PM
CP --> SR
CVW --> PRM
CVW --> CM
PRM --> CP
CM --> SR
CM --> SW
CM --> PRM
SW --> UV3
PM --> PF
Role: Deploys and tracks CoverPool clones.
Pattern and state:
- UUPS upgradeable proxy.
- Deploys deterministic minimal proxy clones using
Clones.cloneDeterministic. - Maintains an internal pool set and pagination helpers.
Key functions:
-
createCoverPool(params)creates and initializes a pool clone, grants curator ownership. -
computeCoverPoolAddress(params)predicts deterministic clone address. The salt combines allCreateCoverPoolParamsfields with the factory's currentpolicyManageraddress, so predictions change ifsetPolicyManageris called. -
coverPoolExists(pool)validates whether a pool is factory-created. -
getCoverPools(offset, limit),totalCoverPools(),coverPoolAt(index)— pagination helpers over the internal pool set. -
setCoverPoolImplementation,setPolicyManager,setStakeManager,setSpecRegistry— dependency setters (CREATOR_ROLE). -
pause/unpause— emergency halt viaDEFAULT_ADMIN_ROLE(notCREATOR_ROLE).
Note: On EigenLayer, duration vaults act as operators and are auto-deployed during committee creation.
Key roles:
-
CREATOR_ROLEcontrols pool creation (createCoverPool) and major factory dependency setters. -
DEFAULT_ADMIN_ROLEcontrolspause/unpauseand upgrades.
Role: Curator-owned underwriting pool for quote validation and policy binding.
Pattern and state:
- Intended for minimal-clone deployment.
- Uses
AccessControlDefaultAdminRulesUpgradeablefor admin transfer safety. - Maintains bound policy commits and per-policy bound metadata.
Key functions:
-
bindPolicyForRequest(policyId, quote, spec, vaults, signature)performs:- draft checks via
PolicyManager, - EIP-712 quote signature verification (via
_verifyQuotewith thevalidateQuotemodifier enforcing expiry), - local pool-side bind state persisted immediately after signature verification (
_recordBoundPolicy), - spec registration in
SpecRegistry, - committee vault binding in Core via
StakeManager, - final policy bind callback into
PolicyManager.
- draft checks via
-
setPoolFeeBps(fee)proposes a new pool fee and starts a 48-hour timelock (POOL_FEE_TIMELOCK). The previous fee remains active until the timelock elapses.finalizePoolFeeBps()(callable by anyone) materialises the pending proposal once the timelock has passed. If a new proposal is submitted while a previous one is pending but not yet matured, the pending proposal is cancelled and replaced.setFeeRecipient(...)updates the fee recipient immediately. -
setPremiumDefaulted(policyId, defaulted)— forwarded toPolicyManager; callable only by the pool admin. -
getBoundPolicy(policyId)/hasBoundPolicy(policyId)— view helpers for bound policy state. -
quoteDomainSeparator()— returns the EIP-712 domain separator for off-chain quote construction. -
pause/unpause— emergency halt viaDEFAULT_ADMIN_ROLE.
Key role:
-
QUOTE_SIGNER_ROLEdefines trusted quote signers. -
DEFAULT_ADMIN_ROLE(curator) controls all pool configuration, role grants, and pausing.
Role: Policy draft and bound-policy authority.
Pattern and state:
- UUPS upgradeable proxy.
- Stores draft records (
PolicyDraft) and canonical bound metadata (PolicyMetadata). - Tracks next policy id and policy commit uniqueness.
Key functions:
-
requestCoverage(...):- called by the wrapper owner (EOA/multisig) as
msg.sender, recorded asbuyer; theCoveredVaultWrappercontract itself is not the buyer, - commonly sets
claimertoCoveredVaultWrapper(files claims atomically on withdrawal shortfall), - commonly sets
beneficiarytoCoveredVaultWrapperfor atomic shortfall top-ups, - validates pool, payout token whitelist, hook, and duration,
- creates policy draft,
- creates Core committee with
committeeId = policyIdviaStakeManager.createCommittee(policyId, pool, duration), - then explicitly calls
StakeManager.addOperatorToCommittee(curator, policyId)to register the pool curator as operator.
- called by the wrapper owner (EOA/multisig) as
-
bindPolicy(request, boundPolicy):- callable by pool only,
- validates request/draft/quote consistency,
- verifies stake and vault readiness through Core,
- stores canonical policy metadata,
- calls
IBindPolicyHook.onPolicyBound(policyId, coverageLimit).
-
cancelPolicyDraft(policyId)— buyer-only; cancels aCreateddraft and removes the curator from the committee viaStakeManager.removeOperatorFromCommittee. -
setPremiumDefaulted(policyId, defaulted)— callable by the bound policy's pool (not admin). -
setSupportedPayoutToken(token, supported)— admin-managed payout token whitelist. -
pause/unpause— emergency halt viaDEFAULT_ADMIN_ROLE.
Note: PolicyManager stores a claimManager address for reference but does not call ClaimManager at runtime.
Role: Premium split and restaker reward routing bridge into Core.
Pattern and state:
- UUPS upgradeable proxy.
- Holds platform configuration:
platformTreasury,platformFeeBps. - Maintains an admin-controlled set of approved premium tokens;
distributePremiumreverts for any unapproved token.
Key functions:
-
distributePremium(pool, policyId, premiumToken, amount):- permissionless entrypoint — any caller (typically
CoveredVaultWrapper.collectPremium()) may call it, - requires:
poolhas a bound policy forpolicyId,premiumTokenis in the approved token set,amount >= 1000, - pulls
amountfrom the caller viasafeTransferFrom, so the caller must have approvedPremiumManagerfirst, - computes split: platform / pool / restaker,
- transfers platform fee to
platformTreasury, - transfers pool fee to pool
feeRecipient, - for the restaker share: grants
RewardsManageran ephemeral per-call allowance viaforceApprove(rewardsManager, restakerSplit), callsRewardsManager.distributeRewards(policyId, curator, restakerSplit, token, taskId)(wheretaskIdis derived internally askeccak256(abi.encodePacked(policyId, ++nonce))), then zeroes the allowance withforceApprove(rewardsManager, 0).
- permissionless entrypoint — any caller (typically
-
approveSpender(token, spender, amount)grants an arbitrary spender a custom allowance fromPremiumManager; this is a general admin utility and is not a prerequisite fordistributePremiumordistributeSurplusAsRewards, both of which manage their own ephemeral allowances internally. -
addApprovedPremiumToken(token)/removeApprovedPremiumToken(token)admin management of allowed premium tokens. -
isApprovedPremiumToken(token)/approvedPremiumTokens()view helpers. -
getFeeSplits(...)deterministic split helper. -
distributeSurplusAsRewards(policyId, curator, amount, token, taskId)— routes excess collateral from claim settlements toRewardsManager; requiresCLAIM_MANAGER_ROLE(held byClaimManager). Uses the same ephemeralforceApprovepattern asdistributePremium. -
setRewardsManager,setPlatformTreasury,setPlatformFeeBps— admin setters. -
pause/unpause— emergency halt viaDEFAULT_ADMIN_ROLE.
Access control: DEFAULT_ADMIN_ROLE for configuration; distributePremium has no role requirement; distributeSurplusAsRewards requires CLAIM_MANAGER_ROLE.
Role: Claim execution engine from filing to payout.
Pattern and state:
- UUPS upgradeable proxy.
- Maintains per-policy claim counters, claim records, and cumulative paid-out amount.
Key functions:
-
fileClaim(policyId, requestedAmount, evidenceHash, additionalData):- file-and-resolve entrypoint (single transaction),
- requires
claimApprovalRequired[policyId] == false; curator must callapproveNextClaimbetween successive claims, - validates coverage window, remaining coverage, non-zero/unique evidence hash, and
!premiumDefaulted, - requires caller equals policy
claimer, - resolves spec and runs evaluation,
- if payable, executes slashing and collateral processing, then sets
claimApprovalRequired[policyId] = true, - transfers payout to beneficiary.
-
approveNextClaim(policyId)— curator clears the per-policy approval gate between successive claims. -
claimApprovalRequired(policyId)/remainingCoverage(policyId)/claimRecord(policyId, claimId)— views. -
recoverTokens(token, to, amount)— admin utility to recover tokens left by failed surplus distributions. -
setMaxSwapSlippageBps,setSpecRegistry,setSlashingManager,setSwapper,setPolicyManager,setPremiumManager— admin dependency setters. -
pause/unpause— emergency halt viaDEFAULT_ADMIN_ROLE. -
receive()— accepts native ETH for native-token payout flows.
Slashing and payout mechanics (token-native, no oracle):
-
SlashingManager.previewSlashing(committeeId, operator)returns per-vault collateral tokens and token-native stake amounts. -
_computeVaultSlashesquotes each cross-token stake viaSwapper.quoteSwapto determine payout-equivalent values, then computes proportional token-native slash amounts per vault; cross-token amounts are inflated by1 / (1 - maxSwapSlippageBps)to absorb worst-case swap slippage. IfquoteSwapreverts or returns zero, or the computed slash rounds to zero, aVaultExcludedFromSlashingevent is emitted and the vault is skipped. -
SlashingManager.executeSlashing(committeeId, operator, VaultSlash[], taskId)executes slashing and returns seized collateral. - For each collateral token:
- if it already equals
payoutToken: transfer directly to beneficiary; surplus forwarded viaPremiumManager.distributeSurplusAsRewards(skipped for native ETH surplus or ifpremiumManageris unset), - otherwise: swap through
SwapperusingquoteSwap-derivedamountOutMin; surplus forwarded similarly. - Surplus routing is wrapped in a
try/catch; failures emitSwapSurplusDistributionFailedand leave tokens in the contract for recovery viarecoverTokens.
- if it already equals
No USD conversion or ChainlinkPriceFeed is used in the claim execution path.
Role: Immutable-style mapping of (coverPool, specId) to ISpec, with admin-controlled spec approval.
Pattern and state:
- UUPS upgradeable proxy.
- Registration is idempotent for the same target and rejects remapping to a different spec.
- Enforces caller is a factory-created pool.
- Maintains an admin-approved whitelist of
ISpecimplementation addresses. A spec must be approved before any pool can register it.
Key functions:
-
approveSpec(spec)called by admin to whitelist a trustedISpecimplementation. -
revokeSpec(spec)called by admin to remove anISpecfrom the whitelist. -
isSpecApproved(spec)view helper to check whether a spec is currently approved. -
registerSpec(specId, spec)called by pool during bind; reverts withSpecNotApprovedif the spec is not on the whitelist. -
resolveSpec(pool, specId)called byClaimManager; resolution is gated by approval status — if a spec is revoked viarevokeSpec, both new registrations and claim resolution for any(pool, specId)pair using that spec address are blocked. Re-approving the spec viaapproveSpecrestores resolution. Use revocation as a targeted kill switch for compromised spec implementations.
Role: Controlled token conversion path for claim collateral.
Pattern and state:
- UUPS upgradeable proxy.
- Route registry keyed by
(tokenIn, tokenOut). - Supports whitelisted swap targets and native wrapper semantics.
Key functions:
-
setSwapRoute(...),setSwapTargetWhitelist(...)for configuration. -
executeSwap(params)for authorized executors (typicallyClaimManager).
Key roles:
-
DEFAULT_ADMIN_ROLEwhitelists swap targets viasetSwapTargetWhitelist. -
SWAP_MANAGER_ROLEconfigures swap routes viasetSwapRoute. -
SWAP_EXECUTOR_ROLEcallsexecuteSwap(held byClaimManager).
Role: Bridges the Swapper's static-calldata pattern to Uniswap V3 SwapRouter02.
Why it exists: The Swapper stores one static calldata blob per (tokenIn, tokenOut) route and
replays it verbatim on every swap. Uniswap V3's exactInputSingle requires the amountIn to be
encoded in each call, making a direct route impossible. The adapter resolves amountIn dynamically
from the allowance the Swapper grants it.
Integration pattern (called by Swapper._performSwap):
- Swapper approves adapter for
amountInoftokenIn. - Swapper calls
adapter.swap(tokenIn, tokenOut, fee, amountOutMinimum)via stored static calldata. - Adapter reads
allowance(Swapper, adapter)to obtainamountIn. - Adapter pulls
tokenInfrom Swapper viatransferFrom. - Adapter calls
SwapRouter02.exactInputSingleforwardingamountOutMinimumfor router-level slippage enforcement. - Router delivers
tokenOutdirectly to Swapper. - Adapter clears residual approval to the router.
Slippage protection: The amountOutMinimum parameter is forwarded directly to the Uniswap V3
router, enabling defense-in-depth slippage protection at the adapter level.
Access control: swap() is restricted to the SWAPPER address recorded at construction time.
Only the authorized Swapper contract may invoke the adapter, enforcing the principle of least
privilege. Any third-party call is rejected with UnauthorizedCaller().
Deployment: script/DeployUniswapV3Adapter.s.sol deploys the adapter (passing the SWAPPER
address as a constructor argument), whitelists it in Swapper, and configures the route in one
transaction. Since UniswapV3Adapter is an immutable contract, any change to the adapter requires
deploying a new instance and reconfiguring the Swapper route via setSwapTargetWhitelist +
setSwapRoute.
| Network | SwapRouter02 |
|---|---|
| Sepolia | 0x3bFA4769FB09eefC5a80d6E87c3B9C650f7Ae48E |
| Mainnet | 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 |
These components live in src/defi/ and are first-class architecture actors.
- UUPS-upgradeable ERC-4626 vault that wraps a Morpho V2 vault,
- shares are non-transferable between external accounts,
- accrues premiums continuously against depositor principal via a reward-per-token accumulator,
- exposes
collectPremium()(permissionless) which flushes accrued premium toPremiumManager.distributePremium()when a policy is bound; falls back to a directsafeTransfertopremiumRecipientwhenpolicyId == 0, - detects shortfall on withdrawal by comparing
insuredBasis(principal minus paid premium) against proceeds from the Morpho vault, - calls
ClaimManager.fileClaim(...)atomically during withdrawal when a shortfall exists, - receives claim payout and tops up the user's withdrawal to make them whole,
- exposes
emergencyRedeem/emergencyWithdrawfor users to exit without insurance by setting a transient skip-insurance flag.
All major contracts are UUPS upgradeable (except CoverPool clones, which are minimal proxies).
Typical control planes:
-
DEFAULT_ADMIN_ROLEfor contract-wide configuration and upgrades. - specialized roles for restricted entrypoints:
-
CREATOR_ROLEon factory, -
QUOTE_SIGNER_ROLEon pools, -
SWAP_MANAGER_ROLE(route config) andSWAP_EXECUTOR_ROLE(execute swap) on swapper; swap target whitelisting requiresDEFAULT_ADMIN_ROLEon swapper.
-
- On
SpecRegistry,DEFAULT_ADMIN_ROLEadditionally controls the spec approval whitelist (approveSpec/revokeSpec). Only admin-approvedISpecimplementations may be registered by cover pool curators.
Coverage contracts rely on Core interfaces:
-
IStakeManagerfor committee create/operator/vault and stake reads, -
IRewardsManagerfor reward fan-out trigger, -
ISlashingManagerfor committee slashing execution, -
IChainlinkPriceFeedfor stake-to-USD conversion at bind time (stake sufficiency check inPolicyManager).