-
Notifications
You must be signed in to change notification settings - Fork 0
04 restaker setup
Before a policy can be bound, the restaker must complete a deposit setup that makes their stake slashable by the policy's committee. This process involves depositing directly into an EigenLayer duration vault (and optionally a Symbiotic vault).
The restaker deposit is performed directly against EigenLayer contracts (no Coverage script needed).
Stake sufficiency is enforced at bind time by SSPRouter._checkDelegation inside addCommitteeVaults
and does not require a separate verification step.
On EigenLayer, duration vaults (DurationVaultStrategy) act as the operator — the curator
does NOT register as an EigenLayer operator, set an allocation delay, allocate magnitude, or
register to an operator set. The vault handles all of this internally:
- When
PolicyManager.requestCoverageis called, it triggersStakeManager.createCommittee, which callsSSPRouter.createCommittee. -
SSPRouter.createCommitteeautomatically deploys aDurationVaultStrategycontract (viaEigenAdapter.deployDurationVaultStrategy) for each acceptable collateral token. - Each
DurationVaultStrategyregisters itself as an EigenLayer operator and operator set upon deployment. - Restakers deposit collateral directly into the duration vault through EigenLayer's
StrategyManager.depositIntoStrategy(durationVault, token, amount). - No separate magnitude allocation or operator set registration is needed from the restaker.
flowchart TD
DEPOSIT["1. Deposit into duration vault\nStrategyManager.depositIntoStrategy(durationVault, token, amount)"]
VAULTS["2. Add vaults to committee\n(via CoverPool.bindPolicyForRequest)"]
BIND["3. Bind policy\n(PolicyManager.bindPolicy)"]
SLASH["Slashable on claim\n(SlashingManager.executeSlashing)"]
DEPOSIT --> VAULTS
VAULTS --> BIND
BIND --> SLASH
The restaker deposits collateral into the DurationVaultStrategy auto-deployed by
EigenAdapter.deployDurationVaultStrategy when the committee was created. Deposits go through
EigenLayer's standard StrategyManager:
IERC20(token).approve(eigenStrategyManager, amount);
IStrategyManager(eigenStrategyManager).depositIntoStrategy(durationVault, token, amount);The durationVault address is the DurationVaultStrategy contract deployed for this committee.
It acts as both an EigenLayer strategy (accepts deposits) and an EigenLayer operator (is
slashable). Its address is stored in StakeManager.getCommitteeVaults(policyId) after committee
creation.
The deposited amount must satisfy the coverage USD requirement. The USD value is computed by
EigenAdapter.getStrategiesStakeUSD(vaults) at bind time:
stake_USD = totalShares * sharesToUnderlyingView() * ChainlinkPriceFeed.getUSDValue(token, 1e18) / 1e18
Example: to cover coverageAmount = 100_000_000_000 (1000 USD at 8 decimals), at least
$1000 / ETH_USD_PRICE ETH worth of WETH must be deposited.
These steps are performed by the curator via CoverPool.bindPolicyForRequest(...), which:
- calls
StakeManager.addCommitteeVaults(policyId, [durationVaultStrategy])— registers the vault as a committee vault, callsEigenAdapter.addStrategiesToOperatorSetto configure it, and runsSSPRouter._checkDelegationwhich reverts withInsufficientDelegationAmountifEigenAdapter.getStrategiesStakeUSD(vaults)<committeeMaxStake, - calls
PolicyManager.bindPolicy(...)— verifies committee has at least one vault and activates the policy.
The _checkDelegation call inside SSPRouter.addCommitteeVaults computes
EigenAdapter.getStrategiesStakeUSD(vaults) and reverts with
InsufficientDelegationAmount if the stake is insufficient.
Policy binding (BindPolicy.s.sol) and restaker deposit are deliberately separated because:
- Different actors and keys: deposit uses the restaker key; binding uses the curator key and quote signer key.
-
Asynchronous stake propagation: after deposit, the USD value reported by
EigenAdaptermay take one or more blocks to reflect on-chain price feed data. - One-time vs per-policy: stake deposit is per-restaker/committee setup; binding is per-policy.
- Protocol boundary: the deposit phase crosses EigenLayer/Symbiotic APIs directly; the bind phase only touches Coverage and Core contracts.
After policy binding and premium collection, premiums are distributed as follows:
-
PremiumCollectorredeems vault shares and transfers underlying toPremiumManager. -
PremiumManager.distributePremiumsplits the amount into platform fee, pool fee, and restaker share. Platform and pool fees are transferred immediately. The restaker share stays inPremiumManager— it is NOT transferred toRewardsManager. -
PremiumManagercallsRewardsManager.distributeRewards(policyId, curator, amount, token, taskId). -
RewardsManagercallsSSPRouter.distributeRewards(..., tokenSource=premiumManager). -
SSPRouterpulls the tokens directly fromPremiumManagerviatoken.safeTransferFrom(premiumManager, adapter, amount).
Prerequisite: admin must call PremiumManager.approveSpender(token, sspRouter, type(uint256).max)
once during initial setup so that SSPRouter has an unlimited allowance to pull tokens.
Before any vault can be bound to a committee, the core admin must register it in SSPRouter:
cast send $SSPROUTER "registerVaultModule(address,uint8)" $VAULT_ADDRESS 2 \
--private-key $CORE_ADMIN_PRIVATE_KEY --rpc-url $RPC_URL
# 2 = EIGENLAYER; 1 = SYMBIOTICFor duration vaults auto-deployed by createCommittee, this is handled automatically.
Verify:
cast call $SSPROUTER "getVaultModule(address)(uint8)" $VAULT_ADDRESS --rpc-url $RPC_URL
# Expected: 2 (EIGENLAYER) or 1 (SYMBIOTIC)The delegation check inside SSPRouter.addCommitteeVaults verifies:
EigenAdapter.getStrategiesStakeUSD(eigenVaults)
+ SymbioticAdapter.getOperatorStakeUSD(operator, symbioticVaults, committeeId)
>= SSPRouter.committeeMaxStake[committeeId]
Where committeeMaxStake[committeeId] was set during SSPRouter.createCommittee (called from
StakeManager.createCommittee, which was called from PolicyManager.requestCoverage).
committeeMaxStake equals the coverageAmount from the policy request, denominated in USD
(8 decimal places). For example, a coverageAmount of 100_000_000_000 means $1,000 USD.
The restaker deposit is a single direct EigenLayer call — no Coverage script is needed.
Get the durationVault address from StakeManager.getCommitteeVaults(policyId)[0] after committee creation.
# 1. Approve collateral token to EigenLayer StrategyManager
cast send $COLLATERAL_TOKEN "approve(address,uint256)" $EIGEN_STRATEGY_MANAGER $DEPOSIT_AMOUNT \
--private-key $RESTAKER_PRIVATE_KEY --rpc-url $RPC_URL
# 2. Deposit into the duration vault
cast send $EIGEN_STRATEGY_MANAGER \
"depositIntoStrategy(address,address,uint256)" \
$EIGEN_DURATION_VAULT $COLLATERAL_TOKEN $DEPOSIT_AMOUNT \
--private-key $RESTAKER_PRIVATE_KEY --rpc-url $RPC_URL| Variable | Description |
|---|---|
RESTAKER_PRIVATE_KEY |
Restaker private key |
EIGEN_STRATEGY_MANAGER |
EigenLayer StrategyManager address |
EIGEN_DURATION_VAULT |
DurationVaultStrategy for this committee (StakeManager.getCommitteeVaults(policyId)[0]) |
COLLATERAL_TOKEN |
Collateral token accepted by the duration vault |
DEPOSIT_AMOUNT |
Amount to deposit (wei) |
| Component | Address |
|---|---|
| EigenLayer DelegationManager | 0xD4A7E1Bd8015057293f0D0A557088c286942e84b |
| EigenLayer StrategyManager | 0x2E3D6c0744b10eb0A4e6F679F71554a39Ec47a5D |
| EigenAdapter (AVS) | 0xA28871e253C972A96003D3f432CA4170f4ae2A78 |
| SSPRouter | 0xc2F3906Bb57c8Db7DF2DddF0f2CEc521fE6834f8 |
Duration vault addresses are looked up at runtime via StakeManager.getCommitteeVaults(policyId).
| Symptom | Root Cause | Fix |
|---|---|---|
InsufficientDelegationAmount at bind |
getStrategiesStakeUSD < committeeMaxStake
|
Deposit more collateral or reduce COVERAGE_AMOUNT
|
VaultModuleNotSet at addCommitteeVaults
|
Duration vault not registered in SSPRouter
|
Core admin: SSPRouter.registerVaultModule(vault, 2)
|
PriceFeedNotFound at fileClaim
|
WETH not registered in ChainlinkPriceFeed
|
Core admin: registerPriceFeed(WETH, ETH/USD_aggregator)
|
InvalidCommitteeId |
policyId = 0 (not set from output of RequestCoverage) |
Set POLICY_ID env var from RequestCoverage.s.sol output |
| Stake reads 0 before bind | Vaults not yet added to committee (expected) | Stake = 0 until bindPolicyForRequest runs addCommitteeVaults
|
SSPRouter safeTransferFrom reverts on distribution |
PremiumManager has not approved SSPRouter | Admin: PremiumManager.approveSpender(token, sspRouter, type(uint256).max)
|