Ensure the security of your smart contracts

Modern DeFi Lending Protocols, how it's made: Euler V2

Author: Sergey Boogerwooger
Security researcher at MixBytes
There are many new lending protocols in DeFi. They enable users to hedge risks, operate with multiple assets simultaneously, take short or long positions in the market, and make their funds work effectively. Lendings serve as the financial cornerstone, like banking loans and deposits in the traditional world. But why are there so many different solutions? How do they differ from the existing ones? How do they plan to compete?

Today, there are many new ideas and approaches in the desgin of lendings: decentralization and permissionless money markets, "soft" liquidations, leverage and incentivisation strategies and simply more effective technical design.

The plan for this series of articles is to review some modern lending protocols and dive into their core algorithms and important pieces of code.

Let's start with Euler V2.
Euler V2 higher-level design
The main idea behind Euler V2 technical design is a "modular lending ecosystem". Essentially, it comprises a vast array of "vaults", with each vault designated for storing a single collateral or borrowing a single asset. Euler V2 offers developers the ability to create their own vaults, providing only the base authentication layer, vaults access logic, and operations with multiple vaults.

Each vault is a ERC-4626-based contract, working with a single asset. The vault accepts assets from users and mints/burns shares. The vaults are set up using the Euler Vault Kit (WP).

The core of Euler V2 is Ethereum Vault Connector (EVC) (WP), acting as an access controller. It determines if the user is allowed to perform a particular action with the vault, manages user accounts, operators (accounts authorized to manage users' assets) and facilitates multicalls. Users can enable/disable different vaults, using some of them as collateral storage, others for borrowing or taking flashloans, and these combinations of operations can be very flexible. In addition, each vault can "attach" its own reward mechanics, enabling developers to implement various forms of incentivisation within their lending protocol. Here's an example scheme:
Here, three users utilize different sets of vaults, providing collateral in "collateral only" vaults and borrowing from connected vaults.

Euler V2 doesn't fully control the internal logic of vaults, allowing developers to implement their own logic of determining the health of users' debt positions and estimating collateral value. For example, developers may opt to use NFTs or other types of assets for specialized vaults. They can create upgradeable, governed vaults or make them totally immutable. The combinations of vaults can vary significantly, it's good to read about different product lines in the WP here.

It's possible to deploy malicious vaults that can steal users' funds. Therefore, users should carefully check and trust the vaults they're using.

[NOTE] The last Dencun hardfork of Ethereum has deprecated SELFDESTRUCT usage in most cases. This has made it impossible to deploy mutable malicious vaults, and this hardfork is an excellent addition to the security of Euler V2 and similar projects

Another important feature of EVC is its ability to perform multicalls, allowing operations that temporarily violate the limits but lead to a successful consistent result (such as flashloans). For example, a user can borrow more funds than allowed, but, in later calls, they can return the remaining debt and complete the whole pack of operations successfully.

An additional feature of Euler is the ability to assign an "operator" (contract or EOA) for an account, which can perform actions on behalf of the user, allowing to build extra services around Euler vaults.
The core of Euler V2 is the EthereumVaultConnector.sol (EVC) is the main contract, regulating access to vaults.

Euler allows ONE ETH address to have up to 256 different accounts, where the last 8 bits are used as the selector for the current account:
This design enables various functionalities when working with multiple vaults at a time and granting permissions to other addresses ("operators") to operate with different user accounts, controllable from single ETH address. For example, it allows setting a bitmask indicating which accounts of the current user are "controllable" by a particular operator.

The next crucial aspect of EVC involves enabling/disabling vaults for the account. When a user wants to add/remove collateral, they must use enableCollateral or disableCollateral. However, the latter, of course, can be denied if there is an unpaid debt. This debt arises when a user borrows the assets from vaults, and the first step for borrowing is enableController which grants access to the user's collaterals to the "borrowing" vault. The external logic governing the user's ability to disableController and regain access to their collaterals in other vaults resides in the controller but internally uses the checkAccountStatus() function, implemented by vault creator.

All the important operations in the vaults like borrow, repay, flashloan and many others are performed strictly via EVC (modifier callThroughEVC is here).
The vault in Euler V2 holds exactly one underlying token, which is a serious difference from other lending protocols where contracts typically hold two or or more underlying assets (at least one for borrowing and one for lending). The main entry point for each vault of Euler is the EVault.sol contract (we've already discussed some of its functions), having many functions related to tokens, operations with shares, borrowing, liquidations, and governance. The internal function for creating a new vault is here.

In Euler V2, cross-vault operations operate by shares rather than underlying tokens. For example, during liquidation, the liquidator receives shares in the vault containing the collateral asset, not the asset itself. This design allows cross-vault operations to work with standardized ERC20 shares with the same precision and implement the unified algorithms to safeguard shares against exchange rate manipulations (well described in this article). For the same purpose (i.e. defending against external donations of tokens) Euler V2 vaults autonomously track token balances by maintaining a copy of the underlying token balance, increasing and decreasing it when tokens come in or out. This makes it impossible to use rebasing or "fee-on-transfer" tokens, but they anyway have a lot of problems with lending protocols.

Tracking user balance logic is implemented in BalanceUtils.sol where we can find functions increaseBalance() and decreaseBalance() (along with transfer and setting allowances). They are used in finaliseDeposit() and finalzeWithdraw() in Vault.sol.

The collection of protocol fees and vault governor fees is well described in the WP and can be found in Governance.sol here.
The price oracle is the most important component of any lending protocol, as manipulations of the oracle are among the most common attack vectors faced by such protocols.

Euler V2 vaults can use any external oracle by implementing the IPriceOracle interface.

The functions used in vaults:

/// @notice One-sided price: How many quote tokens you would get for the inAmount of base tokens, assuming no price spread
function getQuote(uint inAmount, address base, address quote) external view returns (uint outAmount);

/// @notice Two-sided price: How many quote tokens you would get/spend for selling/buying the inAmount of base tokens
function getQuotes(uint inAmount, address base, address quote) external view returns (uint bidOutAmount, uint askOutAmount);
The primary function of the oracle price is to compute the user's liability and collateral values. Each vault has an address called unitOfAccount, serving as the "base" asset for measurement of liability and collateral values (like here).

Euler V2 doesn't rely on static prices between two tokens; instead, it prefers to quote the price, which depends on token amounts. It's well described here. In addition, Euler V2 oracles expose bid/ask prices (selling or buying prices) because, in one case, we need a mid-point price (i.e. for liquidation), or a bid price for collateral estimation (as shown in the example code above).
Risk management
The most interesting part of any lending protocol is, of course, the risk management part: accounts' health, liquidations and bad debt.

The account health logic for Euler is described here. If a user has n different assets ci as collaterals, and each asset has an LTV (Loan-To-Value) factor LTVi, then Euler calculates the risk-adjusted value as

and, if user's liability exceeds this value, the account can be liquidated.

Overall collateral and liability values are calculated here in LiquidityUtils.sol, where each collateral of the user is summarized. All values are measured in the current vault's unitOfaccount. Here is the calculation of collateral value (multiplied by the LTV factor at the end).

The possibility of liquidation is calculated in Liquidation.sol here. The profitability of liquidation (discount factor) depends on how deeply the position is in violation (the relation between total collateral and debt is here). So, while the liability increases with time, the discount factor decreases, making liquidation more and more attractive to the liquidator.

Liquidations in Euler V2 are not restricted, and the liquidator can repay any amount of the user's debt.

Next stop - "forgiveness" and "acceptance" :) Euler v2 prohibits operations that lead to an unhealthy status of the user, and after almost every operation, a health check is performed. However, in the case of liquidation, when the liquidator can repay only part of the debt, leaving the violator's account in an unhealthy status, the special forgiveAccountStatusCheck() function is used to temporarily disable the health check. This part of code can be found here. Health checks are restored in later operations, but, anyway, this part of code remains risky, and all operations within the liquidation should be performed carefully.

Bad debt. The worst-case scenario for any lending. In Euler V2 bad debt (indicating there is debt but insufficient collateral available for liquidation) is handled using socialization. It means that bad debt simply disappears, decreasing the total amount of borrowing and "socialising" the losses to all vault depositors. This procedure is performed within the liquidation process (if it's not fully finished) here.
Additional implementation details
Euler V2 uses a special ERC20 token (DToken.sol) to track users' debt. This token is not directly involved in the liability calculations, but operations with this token (i.e. transferring debt during repayment) are very handy for off-chain software tasks such as analyzing debts, repayment amounts, liquidation possibilities, etc. The address of the DToken belonging to the current vault is deterministically computed from the vault address.

Another interesting aspect is the *BalanceForwarder flags, which enable hooks on increase/decrease balances in the vault. It's made according to the WP to provide developers with the capability to process external rewarding logic. Examples of such logic are in the reward-streams repo, where we can find a hook on balance change here.
Euler V2 uses a straightforward and reliable codebase, offering developers the core layer, security features, and base lending/borrowing mechanics. It delegates the design of product lines to developers, empowering them to combine various types of vaults and different interest rate models. This flexibility enables the creation of lending platforms ranging from strict, immutable, permissionless and secure "landing areas" to multi-level, nested and upgradeable experimental projects.

This modular structure, along with minimalistic "single-asset" vaults and controlled multicalls, is very promising. Let's see what will be built on top of this interesting codebase.

In the next article, we will review another intriguing lending protocol. Don't miss it. Bye!
Who is MixBytes?
MixBytes is a team of expert blockchain auditors and security researchers specializing in providing comprehensive smart contract audits and technical advisory services for EVM-compatible and Substrate-based projects. Join us on Twitter to stay up-to-date with the latest industry trends and insights.
The information contained in this Website is for educational and informational purposes only and shall not be understood or construed as financial or investment advice.
Other posts