Euler Vault Integration

This guide explains how to integrate Cozy Safety Module into a Euler Vault as a bad debt backstop. Use it as a reference when deploying, wiring, or operating the integration. Euler vaults can atomically repay bad debt post-liquidation using eTokens tapped from a Safety Module. This provides an alternative mechanism to managing bad debt with bad debt socialization (the default option).

contract EulerTrancheRaiseStrategy is IRaiseStrategy {
  /// @notice Converts asset needs to safety module specific raises using a tranching strategy which prioritizes
  /// raising the fee share reserve pool prior to the lender share reserve pool.
  /// @param originSafetyModule_ The safety module that triggered the raise
  /// @param assetNeeds_ The asset needs to be converted to raises
  /// @param data_ Encoded reserve pool ids: abi.encode(uint8 feeShareReservePoolId, uint8 lenderShareReservePoolId)
  function calculateRaise(ISafetyModule originSafetyModule_, AssetNeed[] memory assetNeeds_, bytes calldata data_)
    external
    returns (SafetyModuleRaise[] memory);
}
contract CozyLiquidatorManager is Ownable, ICozyLiquidatorManager {
  /// @notice Deploys a new CozyLiquidator with the provided parameters.
  /// @param safetyModule_ The associated SafetyModule.
  /// @param eVault_ The associated Euler vault.
  /// @param raiseStrategy_ The associated raise strategy.
  /// @param feeShareReservePoolId_ The reserve pool ID to use for the fee share.
  /// @param lenderShareReservePoolId_ The reserve pool ID to use for the lender share.
  /// @param salt_ Used to compute the resulting address of the CozyLiquidator along with `msg.sender`.
  /// @return cozyLiquidator_ The newly created CozyLiquidator.
  function createCozyLiquidator(
    ISafetyModule safetyModule_,
    IEVault eVault_,
    EulerTrancheRaiseStrategy raiseStrategy_,
    uint8 feeShareReservePoolId_,
    uint8 lenderShareReservePoolId_,
    bytes32 salt_
  ) external returns (ICozyLiquidator cozyLiquidator_)
}
contract FeeReceiver is Ownable {
  /**
   * @notice Constructor that sets all parameters for the fee receiver
   * @param asset_ Euler vault token that this fee receiver manages
   * @param safetyModule_ The SafetyModule to which fees are redirected
   * @param safetyModuleReservePoolId_ The reserve pool ID in the safety module for fee shares
   * @param safetyModuleShare_ Percentage of fees that should be redirected to the safety module represented as a ZOC
   * (e.g. 5000 = 50%)
   * @param owner_ Address of the owner who can claim any undirected fees
   */
  constructor(
    IERC20 asset_,
    ISafetyModule safetyModule_,
    uint8 safetyModuleReservePoolId_,
    uint256 safetyModuleShare_,
    address owner_
  )
}

Components

  • CozyLiquidatorManager.createCozyLiquidator deploys minimal proxy liquidators that are parameterized for a specific safety module + vault.

  • CozyLiquidator is the controller that Euler vaults call into.

  • CozyLiquidationHandler.handleCozyLiquidation is delegate called by the liquidator to execute the liquidation, and trigger + raise the Safety Module.

  • EulerTrancheRaiseStrategy is a raise strategy which specifies that eTokens deposited by the vault governor (via the FeeReceiver) get tapped prior to eTokens deposited by lenders.

  • FeeReceiver redirects a configurable portion of vault fees into the safety module’s fee share reserve pool.

Deployment & Configuration

1

Configure the safety module

  • Define reserve pools for the Euler vault eToken. Pool 0 for fee shares and pool 1 for lender shares (both backed by the same eToken).

  • Include ControllerConfig({controller: ISafetyModuleController(cozyLiquidatorAddress), exists: true}) inside the ConfigUpdateCalldataParams supplied to a queued config update. This registers the liquidator as a controller via CozySafetyModuleManager.registerSafetyModuleController.

2

Deploy the liquidator for the target vault

Call CozyLiquidatorManager.createCozyLiquidator with:

  • The configured safety module.

  • The Euler vault address (eToken).

  • The EulerTrancheRaiseStrategy instance.

  • Fee-share and lender-share reserve pool IDs.

  • A deploy salt (for deterministic addresses if desired).

3

Wire the Euler vault

  • Vault governance must set the liquidator as the liquidate hook target (IEVault.setHookConfig) and flip CFG_DONT_SOCIALIZE_DEBT so Euler does not spread bad debt across depositors.

  • eVault.feeReceiver() should be set to the FeeReceiver contract so FeeReceiver receives a portion of the governor's fees.

4

(Optional) Divert vault fees to the safety module

  • Deploy a FeeReceiver pointing at the eToken, safety module, and fee reserve pool ID. Set it as the vault’s fee receiver and choose a safetyModuleShare (ZOC).

  • Anyone can call redirectFees() to push accumulated eTokens into the fee reserve pool.

Liquidation & Raise Lifecycle

1

Trigger liquidation through the liquidator

CozyLiquidator.liquidate(violator, collateral, repayAssets, minYield) is called instead of eVault.liquidate.

Note: You must use the CozyLiquidator as the entrypoint, otherwise CozyLiquidator will revert with LiquidationNotRoutedThroughCozyLiquidator()

2

Liquidator delegates to handler

CozyLiquidator locks re-entrancy (only the vault can re-enter) and delegate-calls CozyLiquidationHandler.handleCozyLiquidation.

3

Handler executes Euler liquidation

The handler executes the real Euler liquidation via the EVC, emitting LiquidationExecuted. Since CozyLiquidator is set as the pre-hook on the vault, the eVault immediately calls back into CozyLiquidator.liquidate(), which returns a no-op and continues on to eVault.liquidate()

4

If residual debt remains, trigger the safety module

  • If collateral is insufficient and residual debt remains:

    • A deterministic triggerEventId is computed from the violator, remaining debt, timestamp, and triggerEventIdNonce.

    • The handler calls SafetyModule.trigger(triggerEventId, validityDuration) which moves the module into TRIGGERED state and increments numPendingRaises.

    • An AssetNeed for the Euler eToken shares is created by converting debt assets into eVault share amount.

    • SafetyModule.requestRaise is invoked with the EulerTrancheRaiseStrategy and pool IDs encoded in data.

5

Safety module processes the raise

  • Uses the raise strategy to split the need into concrete raise instructions.

  • Transfers the tapped eTokens to the liquidator, and decrements numPendingRaises

6

Handler repays the vault

Back in the handler, any eTokens received are sent to EVault.repayWithShares, cancelling the bad debt. BadDebtRepaid captures the repayment amount.

7

Liquidator stores snapshot and increments nonce

Control returns to CozyLiquidator, which stores and emits a TriggerEventStateSnapshot (violator, bad debt amount, amount repaid, timestamp) and increments triggerEventIdNonce.

Observability

/// SafetyModule

/// @dev Emitted when the SafetyModule is triggered.
event Triggered(ISafetyModuleController indexed controller_, bytes32 indexed triggerEventId_, uint256 expiresAt_);

event ReservePoolTapped(
    ISafetyModuleController indexed safetyModuleController_,
    bytes32 triggerEventId_,
    address indexed receiver_,
    uint8 indexed reservePoolId_,
    uint256 assetAmount_
);

/// @dev Emitted when a safety module is tapped.
event SafetyModuleTapped(
    ISafetyModuleController indexed safetyModuleController_, bytes32 indexed triggerEventId_, address indexed receiver_
);

/// @dev Emitted when the SafetyModule is requested to raise.
event RaiseRequested(
    ISafetyModuleController indexed controller_,
    bytes32 indexed triggerEventId_,
    address indexed receiver_,
    AssetNeed[] assetNeeds_,
    IRaiseStrategy raiseStrategy_,
    bytes data_
);
/// CozyLiquidator 

/// @notice Emitted on trigger event state snapshot.
event TriggerEventStateSnapshot(bytes32 indexed triggerEventId_, bytes triggerEventStateSnapshot_);
/// CozyLiquidationHandler 

/// @notice Emitted when a liquidation is executed.
event LiquidationExecuted(
  address liquidator_, address violator_, address collateral_, uint256 repayAssets_, uint256 minYieldBalance_
);

/// @notice Emitted when bad debt is repaid.
event BadDebtRepaid(bytes32 indexed triggerEventId_, uint256 badDebtAmount_, uint256 badDebtRepaid_);

Last updated