Skip to content

Commit

Permalink
more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
haythemsellami committed Jan 9, 2025
1 parent e86dc37 commit 57b7e3d
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 52 deletions.
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
## Euler Earn
# Euler Earn

<!-- TOC FOLLOWS -->
<!-- START OF TOC -->
<!-- DO NOT EDIT! Auto-generated by md-toc: https://github.com/hoytech/md-toc -->

* [Introduction ](#introduction-)
* [Local Development](#local-development)
* [Build](#build)
* [Test](#test)
* [Enigma Dark Invariants](#enigma-dark-invariants)
* [Format](#format)
* [Smart Contracts Documentation](#smart-contracts-documentation)
* [Scripts](#scripts)
* [Security](#security)
* [Known limitations](#known-limitations)
* [License](#license)

<!-- END OF TOC -->

## Introduction

Euler Earn is an open source protocol for permissionless risk curation on top of [ERC4626 vaults](https://eips.ethereum.org/EIPS/eip-4626)(strategies). Although it is initially designed to be integrated with [Euler V2 vaults](https://github.com/euler-xyz/euler-vault-kit), technically it supports any other vault as long as it is ERC4626 compliant.

The Euler Earn in itself is an ERC4626 vault, and any risk curator can deploy one through the factory. Each vault has one loan asset and can allocate deposits to multiple strategies. Euler Earn vaults are noncustodial and immutable instances, and offer users an easy way to provide liquidity and passively earn yield.

For more details, please refer to the [whitepaper](/docs/whitepaper.md) and the [low-level spec](/docs/low-level-spec.md).

## Usage
## Local Development

Euler Earn comes with a comprehensive set of tests written in Solidity, which can be executed using Foundry.

Expand Down Expand Up @@ -98,9 +118,9 @@ forge fmt
forge doc --serve --port 4000
```

## Scripts
## Scripts

You can find `forge scripts` inside the `/script` dir to interact with Euler Earn protocol. For more details, please check the scripts usage document.
You can find `foundry scripts` inside the `/script` dir to interact with Euler Earn protocol. For more details, please check the [scripts usage document](/script/README.md).

## Security

Expand All @@ -112,7 +132,7 @@ Always include thorough tests when using Euler Earn protocol to ensure it intera

## Known limitations

Refer to the [add-link](/) for a list of known limitations and security considerations.
Refer to [this doc](/docs/known-limitations.md) for a list of known limitations and security considerations.

## License

Expand Down
84 changes: 84 additions & 0 deletions docs/known-limitations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Euler Earn known limitations and security considerations
description: Known limitations and security considerations when interacting or integrating with Euler Earn protocol
---

# Known limitations and security considerations

<!-- TOC FOLLOWS -->
<!-- START OF TOC -->
<!-- DO NOT EDIT! Auto-generated by md-toc: https://github.com/hoytech/md-toc -->

* [Earn vault shares should not be used as collateral & the share price should not be used as an oracle](#earn-vault-shares-should-not-be-used-as-collateral--the-share-price-should-not-be-used-as-an-oracle)
* [Frontrunning loss socialization](#frontrunning-loss-socialization)
* [Interest smearing mechanism create MEV-opportunity](#interest-smearing-mechanism-create-mev-opportunity)
* [maxWithdraw()/maxRedeem() does not always return optimal maximum amount](#maxwithdrawmaxredeem-does-not-always-return-optimal-maximum-amount)
* [Strategy's previewRedeem() might be manipulable](#strategys-previewredeem-might-be-manipulable)
* [Strategies with fees not properly supported](#strategies-with-fees-not-properly-supported)
* [Performance fee is instantly given to fee recipient](#performance-fee-is-instantly-given-to-fee-recipient)
* [Rebalance mechanism is impacted by the order of the giving strategies array](#rebalance-mechanism-is-impacted-by-the-order-of-the-giving-strategies-array)

<!-- END OF TOC -->

## Earn vault shares should not be used as collateral & the share price should not be used as an oracle

During the call to `harvest()` or to `toggleStrategyEmergencyStatus()`, the realized net negative yield amount or the loss suffered by that specific strategy is socialized across all the depositors after the interest removal.

If the vault's shares are deposited on another protocol, the sudden price drops could cause liquidations.

## Frontrunning loss socialization

Users can monitor the net yield amount that will be harvested prior to the execution of the `harvest()` function, and in the case of that net amount being negative, they can frontrun the harvest transaction to withdraw from Earn vault, as long as their withdrawal amount does not trigger an automatic harvest.

The same apply for toggling a faulty strategy to `Emergency` status.

## Interest smearing mechanism create MEV-opportunity

The harvested positive yield is smeared as interest over a certain smearing period, minimum 1 day.

Large interest events like turning switching a strategy back from the `Emergency` status to `Active` will spike the APR for this period and new depositors can come in to earn a share of the large interest, bringing down the APR as the fixed smeared interest is distributed over a larger set of depositors. This might feel unfair for depositors who suffered the loss when the strategy was emergency-removed.

The same apply after harvesting a big amount of positive yield.

## maxWithdraw()/maxRedeem() does not always return optimal maximum amount

When performing a withdrawal, a harvest is skipped if withdrawn only from the cash reserve. Otherwise, a harvest could be performed (depending on the harvest cooldown) which can result in a loss.

The current algorithm starts by estimating the max amount to withdraw as the user's entire amount `(share balance * share price)` and simulates the harvest conditions based on that.

However, it could happen that the user's entire amount results in withdrawing more than the cash reserve which results in a harvest ending up with a loss, reducing the share price and the amount the user receives. Whereas if the user only withdrew the cash reserve, they would have skipped the loss and withdrawn more.

Euler Earn uses the current implementation to not incentivize users to frontrun harvesting a negative yield and the execution of loss deduction.

## Strategy's previewRedeem() might be manipulable

The EulerEarn vault uses `.previewRedeem()` to measure the value allocated to a certain strategy. [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626#security-considerations) warns that this value might be manipulable and is not always safe to be used as an oracle:
```
The preview methods return values that are as close as possible to exact as possible. For that reason, they are manipulable by altering the on-chain conditions and are not always safe to be used as price oracles. This specification includes convert methods that are allowed to be inexact and therefore can be implemented as robust price oracles.
```

Before enabling a strategy, make sure to check that the `previewRedeem()` function cannot be manipulated, for example, through "read-only reentrancy". Otherwise, the value might be over-/underestimated which can be exploited to book a profit or loss.

## Strategies with fees not properly supported

When allocating to a strategy via rebalance(), the strategy's allocated amount is set to the deposited amount, instead of what the received shares are worth, through a `IERC4626(_strategy).previewRedeem()` call:
```
IERC4626(_strategy).deposit(amountToRebalance, address(this));
$.strategies[_strategy].allocated = (strategyData.allocated + amountToRebalance).toUint120();
```

There might be a discrepancy between these two values if the strategy takes fees. This loss is only booked once `harvest()` is called and the `previewRedeem()` value is fetched and compared to the allocated amount.

The loss will be socialized among all depositors. Even users that join after a large depositor will take that depositor's deposit/withdrawal fee loss once harvest is called which might be considered unfair.

As strategies with fees on the principal (management fees) will incur a loss on every rebalance, **they shouldn't be supported**. Ensure that **the following invariant holds for every enabled strategy:**

`depositing/withdrawing assets should increase/decrease previewRedeem(strategy.balanceOf(this)) by assets`.(in practice, most vaults will incur a tiny rounding loss as deposit mints fewer shares to the depositor and previewRedeem further underestimates the assets the depositor receivers for the shares).

## Performance fee is instantly given to fee recipient

When performance fee is activated, and in the case of a harvested positive yield, this yield is smeared and earned by the depositors over the smearing period. However, the performance fee taken on the yield and minted instantly to the fee recipient in the form of EarnVault shares. Therefore, fee recipient will also be earning future smeared interest on positive yield, as well as impacted by any future loss socialization.

## Rebalance mechanism is impacted by the order of the giving strategies array

The `rebalance()` function depends on the order of strategies. It should be called such that all the withdrawals happen first, and all the deposits afterwards. Otherwise, one could end up skipping a deposit when being temporarily short on cash, but it would become available after a withdraw from another strategy that had surplus in funds based on it's amount of allocation points.
60 changes: 34 additions & 26 deletions docs/low-level-spec.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,56 @@
# Euler Earn Low-Level Spec
# Euler Earn low-level spec

Haythem Sellami & Mick de Graaf.

<!-- TOC FOLLOWS -->
<!-- START OF TOC -->
<!-- DO NOT EDIT! Auto-generated by md-toc: https://github.com/hoytech/md-toc -->

* [Strategy](#strategy)
* [Cash Reserve](#cash-reserve)
* [Allocation Points](#allocation-points)
* [Strategy Cap](#strategy-cap)
* [Emergency Status as Circuit-Breaker](#emergency-status-as-circuit-breaker)
* [Withdrawal Queue](#withdrawal-queue)
* [Deposits & Withdraws](#deposits--withdraws)
* [Rebalance](#rebalance)
* [Harvest](#harvest)
* [Performance Fee](#performance-fee)
* [Loss Deduction & Socialisation](#loss-deduction--socialisation)
* [Gulping & Smearing Period](#gulping--smearing-period)
* [Specification](#specification)
* [Strategy](#strategy)
* [Cash reserve](#cash-reserve)
* [Allocation points](#allocation-points)
* [Strategy cap](#strategy-cap)
* [Circuit-breaker](#circuit-breaker)
* [Withdrawal queue](#withdrawal-queue)
* [Deposits & withdraws](#deposits--withdraws)
* [Rebalance](#rebalance)
* [Harvest](#harvest)
* [Performance fee](#performance-fee)
* [Loss deduction & socialisation](#loss-deduction--socialisation)
* [Gulping & smearing period](#gulping--smearing-period)
* [Known limitations and security considerations](#known-limitations-and-security-considerations)

<!-- END OF TOC -->

## Strategy
## Specification

### Strategy

The goal of implementing Euler Earn is to provide to provide a passive yield for users, and to manage the risk on their behalf. The source of yield are the strategies that the Euler Earn vault allocates funds into.
A strategy is any ERC4626 compliant contract, can be added and removed by the address that has the `STRATEGY_OPERATOR` role.

Removing a strategy that has an allocated amount greater than 0 is not possible, therefore an adjustment in its allocation points to 0, and then a rebalance trigger is required to remove such a strategy.

### Cash Reserve
#### Cash reserve

The strategy with `address(0)` is reserved for the `CASH_RESERVE` strategy, which acts as the first source of liquidity for withdrawals. The amount of funds set aside for the `cash reserve` is based on the number of allocation points set for it.

### Allocation Points
#### Allocation points

When adding a strategy, the `STRATEGY_OPERATOR` needs to set `allocation points` for the given strategy, which define the amount of assets to be allocated into that strategy via rebalancing.

For the `cash reserve` strategy, the allocation points amount is defined at the deployment time.

An address that has the `GUARDIAN` role can adjust the allocation points for an already added strategy. For the `cash reserve` strategy, and allocation points amount of 0 is not allowed.

### Strategy Cap
#### Strategy cap

Although the allocation points define the amount of funds to allocate in a strategy, the `GUARDIAN` can still set a cap on a strategy allocated amount. By default, the cap for all the strategies is set to 0.

It is not possible to set a cap on the amount reserved for the `cash reserve` strategy.

### Circuit-Breaker
#### Circuit-breaker

In the scenario where the euler earn vault can not interact with a strategy that has some allocated amount, the `GUARDIAN` can set the strategy status as `Emergency`. This will basically allow the `rebalance` and `harvest` functionalities to execute as expected without actually removing that strategy from the [withdrawal queue array](#withdrawal-queue).

Expand All @@ -56,22 +60,22 @@ The `GUARDIAN` can revert back the strategy to active anytime, once that's done,

The strategy allocated amount will be immediately available for [`gulping`](#gulping--smearing-period).

### Withdrawal Queue
#### Withdrawal queue

Strategies addresses are stored in the `Withdrawal Queue` array based on their order of insertion.
The withdrawal queue order is mainly used during the `withdraw` and `harvest` operations.

An address that has the `WITHDRAWAL_QUEUE_MANAGER` role can re-order the withdrawal queue array. It is expected to have the strategies with the most allocated amounts in the beginning of the array, for more gas-efficient withdraws.

## Deposits & Withdraws
### Deposits & withdraws

When the assets get deposited into the euler earn by the user, those funds do not get allocated immediately to the strategies, instead we just increase the `totalAssetsDeposited`. The funds in the euler earn get allocated into the strategies through the operation called [`Rebalancing`](#rebalance).

For withdraws, if the amount of asset to withdraw can not be covered by the funds in the `cash reserve`, the euler earn will loop through the `withdrawal queue array`, and withdraw from the strategies one by one till the requested amount to withdraw is filled. If for some reason the funds can not be taken from the strategies, the withdraw operation will fail with `NotEnoughAssets` error.

The withdraw operation does trigger a [`harvest operation`](#harvest) if the `HARVEST_COOLDOWN` period has passed.

## Rebalance
### Rebalance

A `rebalance` operation can be initiated by any address that has the `Rebalancer` role, by calling the `rebalance()` function, giving an array of strategies addresses to rebalance.

Expand All @@ -84,7 +88,7 @@ For both cases, the amount to rebalance is restricted by a few factors...For the
For the withdraw case:
- The max amount withdrawable from that specific strategy.

## Harvest
### Harvest

Once funds are allocated, the euler earn vault has no awareness for the yield generated from those strategies, that's why a harvesting operation is needed.

Expand All @@ -96,24 +100,28 @@ In the case of a negative net yield amount, the euler earn has a [`loss deductio

A `HARVEST_COOLDOWN` period is set to `1 day` which only allows the harvest execution during a `withdraw()` call if the last executed harvest was at least 1 day before. A direct call to `harvest()` ignores the cooldown period check.

### Performance Fee
#### Performance fee

An address that has the role `EULER_EARN_MANAGER` can set the fee recipient address and the performance fee. The `MAX_PERFORMANCE_FEE` is set to 50%.

During the `harvest` operation, if the net yield amount is positive and the performance fee is set, then that fee is accrued from the net yield amount, gets converted to `shares` amount and minted to the `feeRecipient`.

## Loss Deduction & Socialisation
### Loss deduction & socialisation

The euler earn vault can suffer losses in two scenarios:
- During a `harvest` operation, if the harvested net yield amount is negative.
- If a circuit-breaker for a strategy is activated by marking its status as `Emergency`, using the `toggleStrategyEmergencyStatus()` function.

For those scenarios, the loss deduction mechanism is implemented. The euler earn vault will first deduct the loss amount from any past-harvest-yield that remains to be distributed as interest. If the loss is bigger than that, the remaining amount will be socialised amongst all current depositors in the Euler Earn vault. Although opportunistic depositors could frontrun loss socialisation by withdrawing earlier, that would only work if `harvest` is not triggered during the withdrawal because the `HARVEST_COOLDOWN` period hasn't pass yet.

## Gulping & Smearing Period
### Gulping & smearing period

As mentioned in the harvest section, the harvested positive yield is not immediately distributed to depositors, but it does get added to the `totalAllocated` amount, therefore it becomes available to `gulp()`. Gulping is the mechanism which distributes it to share holders in the vault over the smearing period. This period is set by the Earn vault deployer, at deployment time. Accrued interest is added to the `totalAssetsDeposited` of the Euler Earn vault, adjusting the exchange rate accordingly. On deposit and redeem accrued interest is added to the totalDeposited variable which tracks all deposits in the vault in a donation attack resistant manner.

On gulp, any interest which has not been distributed, is smeared for an additional two weeks. In theory this means that interest could be smeared indefinitely by continuously calling gulp, in practice it is expected that the interest will keep accruing, negating any negative side effects which may come from the smearing mechanism.

The gulping mechanism implemented here is the same one that was implemented in the [`EulerSavingsRate` contract](https://docs.euler.finance/euler-vault-kit-white-paper/#eulersavingsrate).
The gulping mechanism implemented here is the same one that was implemented in the [`EulerSavingsRate` contract](https://docs.euler.finance/euler-vault-kit-white-paper/#eulersavingsrate).

## Known limitations and security considerations

Please check [the following doc](./known-limitations.md).
Loading

0 comments on commit 57b7e3d

Please sign in to comment.