Joel's dev blog

Euler V2 Primer

September 23, 2025

6 min read

Euler V2 system deployment

To get a better overview of any sophisticated system of smart contracts, it’s always good to start from how the deployment works.

Deployment in EVK Test setup

How Euler v2 is set up can be found out by looking at the test setup file:

First, protocol core is deployed by _deployProtocolCore, and then vaults are deployed by _deployVaults. We’re going to talk about EthereumVaultConnector a bit later. Following that are protocol config and mock oracle and assets.

Now, in _deployVaults is where the real magic happens. First, the deployed EthereumVaultConnector, ProtocolConfig, SequenceRegistry, and BalanceTracker are packaged into Base.Integrations struct. We call them integrations.

Then, each module such as Vault, Borrowing, Liquidation are deployed and stored in Dispatch.DeployedModules struct.

These two structs - Base.Integrations and Dispatch.DeployedModules are again used to initialize EvaultExtended contract. This serves as the implementation for the vault factory contract.

Using the factory contract, multiple copies of EvaultExtended are created for each asset concerned.

Deployment in euler-devland

Another example of the deployment script is at euler-devland. It contains scripts to spin up an instance of Euler contracts for local development and testing purposes.

We can see that this is exactly the same pattern of deployment that we saw in EVK Test setup.

Deployment in euler-xyz/evk-periphery

The real deployment script that is used for the mainnet lives under euler-xyz/evk-periphery/script. It really does not matter which repository has the deployment script, because the complete Euler system includes multiple Github repositories anyways.

Pivotal to the system are probably only the first few deployment scripts:

deployment_scripts.png

01_Integrations.s.sol deploys the ‘protocol core’ that will form the Base.Integrations struct:

02_PeripheryFactories.s.sol deploys periphery contracts - not too important in understanding the core Euler contracts:

03_OracleAdapters.s.sol and 04_IRM.s.sol are just self-explanatory: they deploy oracle adapters and interest rate models.

05_EvaultImplementation.s.sol deploys the vault modules and stores them in Dispatch.DeployedModules struct.

One difference between this and the Setup.t.sol we saw before is that EVault implementation is represented by address evaultImpl = address(new EVaultExtended(integrations, modules)); in the setup contract, while here in production deployment, we use EVault contract:

And in fact, EVaultExtended is just an extension of EVault with a few more view functions, so for the sake of understanding, we can just pretend it’s an EVault contract itself:

06_EVaultFactory.s.sol deploys a GenericFactory that can deploy copies of EVault for different assets later:

Now, 07_EVault.s.sol calls createProxy of GenericFactory that was deployed in the previous step to create a vault for each asset:

Vaults deployment

To systematically manage and deploy vaults, Euler came up with something called clusters.

A cluster is a collection of vaults that accept each other as collateral and have a common governor. A governor acts as an admin, controlling upgrades, parameter changes, and access to sensitive functions.

To deploy a cluster, one can just inherit ManageClusterBase.s.sol contract to write custom deployment script for a cluster.

When a cluster is deployed, each pair that can be used as a collateral or debt against each other becomes visible on Euler. For example:

From this script, we can find that the LLTV for borrowing WBTC against WETH collateral is 73% (19th column, 1st row), and for that for borrowing WETH against WBTC collateral is 82% (1st column, 19th row).

This is directly reflected on Euler frontend, which means you can find the details of pair on the website:

WBTC (collateral)/WETH (debt): wbtc_weth

WETH (collateral)/WBTC (debt): weth_wbtc

So we know cluster.ltvs actually means liquidation LTV, and max LTV (max borrowable ratio against collateral) is 2% less as per cluster.spreadLTV = 0.02e4;.

Euler Vault Kit (EVK)

So far we’ve been talking about vaults a lot under the deployment section, but what really is a vault? Each vault is represented by an EVK. EVK is an ERC-4626 vault with added borrowing functionality. The original spec of ERC-4626 vaults only allow deposits and withdrawals, but EVK allows users to borrow assets against their deposited collateral.

So there are as many vaults as different assets at the very least. Each asset can have multiple vaults too. Each vault tracks basically any information that relates to the credit vault of an asset like interest rate, deposits, borrows.

Each row that you can see on the ‘Lend’ page of Euler is a vault:

lend_page

For example, these are two different vaults for USDC:

Frontier Falcon USDC:

frontier_falcon_usdc

Euler Yield USDC:

euler_yield_usdc

And again, these vaults belong to different clusters. For example, Frontier Falcon USDC belongs to a cluster called Falcon:

The code matches exactly what we can see on the frontend of Frontier Falcon USDC:

falcon_cluster_can_be_borrowed.png

We look at the first column of cluster.ltvs. All of the assets can be used as collateral for USDC debt.

falcon_cluster_can_be_collateral.png

On the other hand, the first row of cluster.ltvs only has USDT and USDf with an actual value of LTV, other assets being all LTV_ZERO. This means only these two assets can be borrowed with USDC being used as a collateral, which is shown on ‘Can be used as collateral’ tab.

Ethereum Vault Connector (EVC)

The EVC primarily mediates between vaults, contracts that implement the ERC-4626 interface and contain additional logic for interfacing with other vaults.

When a user wishes to borrow, they must link their accounts and collateral vaults to the borrowed-from vault via the EVC. The liability vault, also known as the “controller”, is then consulted whenever a user wants to perform an action potentially impacting account’s solvency, such as withdrawing collateral. The EVC is responsible for calling the controller which determines whether the action is allowed or if it should be blocked to prevent account insolvency.

References


Written by Joel Mun. Joel likes Rust, GoLang, Typescript, Wasm and more. He also loves to enlarge the boundaries of his knowledge, mainly by reading books and watching lectures on Youtube. Guitar and piano are necessities at his home.

© Joel Mun 2025