Skip to content

feat: add liquidation considerations architecture and white paper #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 29 additions & 25 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@ EulerSwap is an automated market maker (AMM) that integrates with Euler credit v

Each EulerSwap instance is a lightweight smart contract that functions as an [EVC operator](https://evc.wtf/docs/whitepaper/#operators) while implementing a highly customizable AMM curve to determine swap output amounts.

When a user initiates a swap, the EulerSwap operator borrows the required output token using the input token as collateral. The operator’s internal AMM curve governs the exchange rate, ensuring deep liquidity over short timeframes while maintaining a balance between collateral and debt over the long term.
When a user initiates a swap, the EulerSwap operator can borrow the required output token using the input token as collateral. The operator’s internal AMM curve governs the exchange rate, ensuring deep liquidity over short timeframes while maintaining a balance between collateral and debt over the long term.

Swapping can be performed by invoking the EulerSwap instance, either through a Uniswap2-compatible `swap()` function or as a [Uniswap4 hook](https://docs.uniswap.org/contracts/v4/concepts/hooks).

## Code structure

EulerSwap is split into the following main contracts:

* `EulerSwap`: Contract that is installed as an EVC operator by liquidity providers, and is also invoked by swappers in order to execute a swap.
* `UniswapHook`: The functions required so that the EulerSwap instance can function as a Uniswap4 hook.
* `EulerSwapFactory`: Factory contract for creating `EulerSwap` instances and for querying existing instances.
* `EulerSwapPeriphery`: This is a wrapper contract for quoting and performing swaps, while handling approvals, slippage, etc.
- `EulerSwap`: Contract that is installed as an EVC operator by liquidity providers, and is also invoked by swappers in order to execute a swap.
- `UniswapHook`: The functions required so that the EulerSwap instance can function as a Uniswap4 hook.
- `EulerSwapFactory`: Factory contract for creating `EulerSwap` instances and for querying existing instances.
- `EulerSwapPeriphery`: This is a wrapper contract for quoting and performing swaps, while handling approvals, slippage, etc.

The above contracts depend on libraries:

* `CtxLib`: Allows access to the `EulerSwap` context: Structured storage and the instance parameters
* `FundsLib`: Moving tokens: approvals and transfers in/out
* `CurveLib`: Mathematical routines for calculating the EulerSwap curve
* `QuoteLib`: Computing quotes. This involves invoking the logic from `CurveLib`, as well as taking into account other limitations such as vault utilisation, supply caps, etc.
- `CtxLib`: Allows access to the `EulerSwap` context: Structured storage and the instance parameters
- `FundsLib`: Moving tokens: approvals and transfers in/out
- `CurveLib`: Mathematical routines for calculating the EulerSwap curve
- `QuoteLib`: Computing quotes. This involves invoking the logic from `CurveLib`, as well as taking into account other limitations such as vault utilisation, supply caps, etc.

And some utilities:

* `MetaProxyDeployer`: Deploys EIP-3448-style proxies.
* `ProtocolFee`: The factory stores protocol fee parameters that will affect subsequently created `EulerSwap` instances. These can be changed by an owner.
- `MetaProxyDeployer`: Deploys EIP-3448-style proxies.
- `ProtocolFee`: The factory stores protocol fee parameters that will affect subsequently created `EulerSwap` instances. These can be changed by an owner.

## Operational flow

Expand All @@ -53,7 +53,7 @@ Traditional AMMs hold dedicated reserves of each of the supported tokens, which

Since EulerSwap does not have dedicated reserves, its swapping limits must be defined in another way. This is accomplished by having the EulerSwap operator define an abstract curve. The domain of this curve defines the swap limits, which can be considered the virtual reserves.

The abstract curve is centred on an *equilibrium point*. This is parameterised by two equilibrium reserves values. These specify the magnitude of the virtual reserves, and are effectively hard limits on the supported swap sizes. They are often equal, but do not necessarily have to be (for instance, if the two vaults have asymmetric LTVs).
The abstract curve is centred on an _equilibrium point_. This is parameterised by two equilibrium reserves values. These specify the magnitude of the virtual reserves, and are effectively hard limits on the supported swap sizes. They are often equal, but do not necessarily have to be (for instance, if the two vaults have asymmetric LTVs).

At the equilibrium point, the marginal swap price is defined by the ratio of two parameters `priceX` and `priceY`. Generally operators will choose the price ratio at equilibrium to be the asset's pegged price, or the wider market price. The prices should also compensate for a difference in token decimals, if any.

Expand All @@ -73,44 +73,48 @@ If the current state of the account is where you wish the equilbrium point to be

Note that there may be a race condition when removing one swap operator and installing another. In between when you've calculated the current reserves and when you've actually created and installed the new operator, a swap may occur that modifies the account state. To avoid this, a wrapper contract should be used that calculates the current reserves. Or, more simply, just verifies that the account state was as observed by the operator and otherwise reverts.


## Fees

Swapping fees are charged by requiring the swapper to pay slightly more of the input token than is required by the curve parameters. This extra amount is simply directly deposited into the vaults on behalf of the EulerSwap account. This means that it has the effect of increasing the account's NAV, but does not change the shape of the curve itself. The curve is always static, per EulerSwap instance.

When an EulerSwap instance is created, a **protocol fee** parameter may be installed by the factory. This portion of the collected fees are routed to a protocol fee recipient. An administrator of the factory can change the the protocol fee and recipient for future created EulerSwap instances, although previously created instances will not be updated retroactively.


## Reserve desynchronisation

The EulerSwap contract tracks the current reserves in storage. After a swap, the amount of received tokens is added to the current reserves, and the amount of sent tokens subtracted. Since the reserves are not allowed to go negative, this implies a hard limit on the swap sizes.

While these reserves track the state of the world as influenced by swaps, they can get out-of-sync with the actual account for various reasons:

* Interest can be accrued, either increasing or decreasing the account's NAV.
* Swap fees are not tracked, and instead increase the account's NAV.
* The account could be liquidated.
* The account owner could manually add or remove funds, repay loans, etc.
- Interest can be accrued, either increasing or decreasing the account's NAV.
- Swap fees are not tracked, and instead increase the account's NAV.
- The account could be liquidated.
- The account owner could manually add or remove funds, repay loans, etc.

In order to correct any desynchronisation, the EulerSwap operator should be uninstalled and a new, updated one installed instead.


## getLimits

Although the virtual reserves specify a hard limit for swaps, there may be other implicit limits that are even lower:

* The vaults have high utilisation and cannot service large borrows or withdrawals
* The vaults have supply and/or borrow caps
* The operator may have been uninstalled
- The vaults have high utilisation and cannot service large borrows or withdrawals
- The vaults have supply and/or borrow caps
- The operator may have been uninstalled

There is a function `getLimits` that can take these into account. This function itself is an upper-bound and the values it returns may not be swappable either, in particular if the curve shape does not allow it. However, it makes a best effort and this function can be used to rapidly exclude pools that are definitely unable to service a given size swap.


## Swapper Security

When swapping with an EulerSwap instance, users should always make sure that they received the desired amount of output tokens in one of two ways:

* Actually checking your output token balances before and after and making sure they increased by an amount to satisfy slippage.
* Ensure that the EulerSwap code is a trusted instance that will send the specified output amount or revert if not possible. This can be done by making sure an instance was created by a trusted factory.
- Actually checking your output token balances before and after and making sure they increased by an amount to satisfy slippage.
- Ensure that the EulerSwap code is a trusted instance that will send the specified output amount or revert if not possible. This can be done by making sure an instance was created by a trusted factory.

In particular, note that the periphery does not perform either of these checks, so if you use the periphery for swapping, you should ensure that you only interact with EulerSwap instances created by a known-good factory.

## Liquidation Considerations

It is important for market makers running EulerSwap instances to parameterise their curve in such a way as to ensure that swaps cannot drive their Euler account towards liquidation. EulerSwap account owners are also responsible for monitoring the health of their vaults and should take proactive steps if their collateral accrues bad debt or drops in value—since this can happen independently of swap activity.

This is important, because if another position in the same collateral vault is liquidated and leaves behind bad debt, the value
of the shared collateral used for liquidity could drop. This would affect the effective LTV of all positions using that
vault, including those managed by the EulerSwap AMM. An attacker could exploit this situation by then initiating a swap that pushes the AMM's position right up to the liquidation threshold, leveraging the degraded collateral value caused by unrelated bad debt.
Binary file modified docs/whitepaper/EulerSwap_White_Paper.pdf
Binary file not shown.
Loading