From 57b7e3d3fd0dcc3e407dcc71ed6d8bc7f3497f0f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:59:02 +0700 Subject: [PATCH] more docs --- README.md | 30 +++++++++++--- docs/known-limitations.md | 84 +++++++++++++++++++++++++++++++++++++++ docs/low-level-spec.md | 60 ++++++++++++++++------------ docs/whitepaper.md | 41 +++++++++---------- script/README.md | 2 +- 5 files changed, 165 insertions(+), 52 deletions(-) create mode 100644 docs/known-limitations.md diff --git a/README.md b/README.md index cfcae98c..4828ca4c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,24 @@ -## Euler Earn +# Euler Earn + + + + + +* [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) + + + +## 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. @@ -6,7 +26,7 @@ The Euler Earn in itself is an ERC4626 vault, and any risk curator can deploy on 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. @@ -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 @@ -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 diff --git a/docs/known-limitations.md b/docs/known-limitations.md new file mode 100644 index 00000000..bb3a2280 --- /dev/null +++ b/docs/known-limitations.md @@ -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 + + + + + +* [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) + + + +## 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. \ No newline at end of file diff --git a/docs/low-level-spec.md b/docs/low-level-spec.md index 9a793eb3..b52bda24 100644 --- a/docs/low-level-spec.md +++ b/docs/low-level-spec.md @@ -1,4 +1,4 @@ -# Euler Earn Low-Level Spec +# Euler Earn low-level spec Haythem Sellami & Mick de Graaf. @@ -6,33 +6,37 @@ Haythem Sellami & Mick de Graaf. -* [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) -## 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. @@ -40,13 +44,13 @@ For the `cash reserve` strategy, the allocation points amount is defined at the 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). @@ -56,14 +60,14 @@ 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). @@ -71,7 +75,7 @@ For withdraws, if the amount of asset to withdraw can not be covered by the fund 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. @@ -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. @@ -96,13 +100,13 @@ 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. @@ -110,10 +114,14 @@ The euler earn vault can suffer losses in two scenarios: 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). \ No newline at end of file +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). \ No newline at end of file diff --git a/docs/whitepaper.md b/docs/whitepaper.md index 2e66bb2c..f0d190e9 100644 --- a/docs/whitepaper.md +++ b/docs/whitepaper.md @@ -3,7 +3,7 @@ title: Euler Earn description: An open source protocol for permissionless risk curation on top of ERC4626 vaults --- -# Euler Earn Whitepaper +# Euler Earn whitepaper Mick de Graaf & Haythem Sellami. @@ -13,21 +13,22 @@ Mick de Graaf & Haythem Sellami. * [Introduction](#introduction) * [Motivation](#motivation) -* [Permissionless Yield Aggregation & Risk Management](#permissionless-yield-aggregation--risk-management) +* [Permissionless yield aggregation & risk management](#permissionless-yield-aggregation--risk-management) * [Core concepts](#core-concepts) * [Strategy](#strategy) * [Cash reserve](#cash-reserve) - * [Allocation Points](#allocation-points) + * [Allocation points](#allocation-points) * [Rebalance](#rebalance) + * [Strategy allocation cap](#strategy-allocation-cap) * [Harvest](#harvest) - * [Performance Fee](#performance-fee) - * [Loss Deduction](#loss-deduction) - * [Yield Gulping & Smearing ](#yield-gulping--smearing-) - * [Withdrawal Queue](#withdrawal-queue) + * [Performance fee](#performance-fee) + * [Loss deduction](#loss-deduction) + * [Yield gulping & interest accruing](#yield-gulping--interest-accruing) + * [Withdrawal queue](#withdrawal-queue) * [Roles](#roles) - * [Strategy Emergency Status](#strategy-emergency-status) - * [Native ERC20 Votes](#native-erc20-votes) -* [Immutability, Management and Fees](#immutability-management-and-fees) + * [Strategy emergency status as circuit-breaker](#strategy-emergency-status-as-circuit-breaker) + * [Native ERC20 votes](#native-erc20-votes) +* [Immutability, management and fees](#immutability-management-and-fees) @@ -43,7 +44,7 @@ Euler Earn vaults are noncustodial, immutable, and offer users an easy way to pr Euler V2 is a lending and borrowing protocol built on top of the [EVC](https://github.com/euler-xyz/ethereum-vault-connector) primitive and using the [Euler Vault Kit](https://github.com/euler-xyz/euler-vault-kit), prioritising modularity, efficiency and flexibility. On Euler V2, lenders must consider many technical factors, including the types of collateral accepted, loan-to-value ratio, the oracles used, supply and borrow caps, the type of vault (borrowable, escrow), and so on. For that reason, interacting with lending vaults directly is more suited to sophisticated and knowledgeable lenders than to passive ones. Euler Earn helps solve this challenge by providing an easier way for lenders to gain access to passive yield, by externalising risk-management to trusted vault governors who manage their deposits on their behalf. -## Permissionless Yield Aggregation & Risk Management +## Permissionless yield aggregation & risk management Any risk curator can use the factory to create a Euler Earn vault, including DAOs, protocols, risk experts, funds or curious developers. Anyone can leverage the permissionless infrastructure to provide users with a simple passive yield earning experience. @@ -65,7 +66,7 @@ The strategy with address zero is used as a cash reserve. An amount of the total deposited assets not allocated to any strategy, and used as the first source of liquidity during withdrawal. The amount to set as cash reserve is decided based on the allocation points set for the cash reserve strategy. -### Allocation Points +### Allocation points Each strategy gets assigned allocation points, including the cash reserve strategy. During strategy rebalance, the amount of asset to allocate is calculated based on its allocation points. @@ -75,7 +76,7 @@ The user's deposited assets are allocated across the Euler Earn vault’s strate Only an address that has the `Rebalancer` [role](#roles) can execute a rebalance operation. -### Strategy Allocation Cap +### Strategy allocation cap An address with the `Guardian` [role](#roles) can set a cap on the amount of asset that can be allocated in a certain strategy. @@ -89,19 +90,19 @@ During harvesting, Earn vault loop through all the strategies to execute the har In case of net positive yield, a [performance fee](#performance-fee) is accrued, if applicable. In case of negative net yield, a [loss deduction mechanism](#loss-deduction) is applied. -### Performance Fee +### Performance fee An Earn vault's manager can set a performance fee that will be accrued on every harvested net positive yield. The fee amount will be converted to vault shares and minted to the fee recipient address. By default, the performance fee is set to 0%, and is capped to 50%. -### Loss Deduction +### Loss deduction A loss deduction mechanism is applied in the case of harvesting a net negative yield amount. The net negative yield amount will be first deducted from the interest left to accrue, if that's not enough to cover the loss, the rest will be socialised across depositors. -### Yield Gulping & Interest Accruing +### Yield gulping & interest accruing Harvested positive yield is not instantly added to the Earn vault total deposits, instead, it gets gulped as an interest to be distributed (smeared) along the smearing period (2 weeks), and that prevents sudden jumps in the euler earn vault’s exchange rate. -### Withdrawal Queue +### Withdrawal queue A queue of the added strategy addresses, mainly used during yield harvesting and executing withdrawal requests from the Earn vault. Strategies are pushed into the withdrawal queue and removed from it when the add or removing strategy operation is called. @@ -136,7 +137,7 @@ Each role has their own specific `Admin role`, the holder of the Admin role can ![euler-earn-roles](./euler-earn-roles.png) -### Strategy Emergency Status as Circuit-Breaker +### Strategy emergency status as circuit-breaker In case of a faulty strategy that has already been allocated funds, the Guardian can set that strategy status as `Emergency`, therefore the Earn vault will be functioning as expected, without taking into account that specific strategy when executing a harvest or rebalance. @@ -144,11 +145,11 @@ When a strategy is set as `Emergency`, the strategy allocated amount is no longe The Guardian can toggle the strategy status back to active anytime. At the time of changing a strategy status from `Emergency` to `Active`, the Earn vault balance in that strategy will be stored as the `allocated` amount, and will added to the total allocated amount. Therefore, that amount will be instantly available to gulp and smeared as interest to depositors. -### Native ERC20 Votes +### Native ERC20 votes Euler Earn vault natively integrates with ERC20Votes contract to support Compound-like voting and delegation, therefore the shareholders of an Earn vault can use their shares as voting power in the vault governance. -## Immutability, Management and Fees +## Immutability, management and fees Euler Earn is a robust and flexible protocol, providing an immutable set of contracts and a set of parameters for vault creators to configure. diff --git a/script/README.md b/script/README.md index 279ac3ea..1841d16e 100644 --- a/script/README.md +++ b/script/README.md @@ -1,4 +1,4 @@ -# Scripts +# Forge scripts to interact with Euler Earn In this directory, you can find helper `forge` scripts to interact with Euler Earn protocol. The scripts allows to: