Exchange Rate Manipulation in ERC4626 Vaults

Attack Vectors, Manipulation Mechanisms, and Mitigations

Introduction

The ERC4626 Tokenized Vault standard has exploded in popularity. Many protocols are racing to implement compatible contracts, hoping to become integrated with yield aggregators and to capture significant shares of trading volume and user share.

However, as with any new technology, there are dangers that are not immediately apparent. Shortly after its release, it was identified that naive ERC4626 vaults are vulnerable to first deposit attacks, just as many other contracts like AMM pools are.

A number of mechanisms were developed to protect Tokenized Vaults against this attack vector. But the recent Wise Finance attack, and the realization that most vaults don't explicitly implement safe oracle functions, led us to research how to build ERC4626 vaults that would be safe against exchange rate manipulation.

In this article, we explore the attack methods that can be used to manipulate the exchange rate in a Tokenized Vault, the scenarios in which these attacks could become a problem, and the mitigations that can be implemented to help make vaults safe. As with so many things in smart contract development, there is no single solution that fits all purposes. In many cases more than one mitigation will be needed, but each case needs to be considered on its own merits. 

Using artificial exchange rates for fun and profit

Vaults are exposed to exchange rate manipulation attacks. So far two attack types have been identified in the wild: First deposit frontrunning and share price manipulation.

First deposit frontrunning
The attack and some possible defense mechanisms are described in detail in the OpenZeppelin docs. It goes like this: The attacker sandwiches the first deposit into the vault with an exchange rate manipulation such that all or most of the first depositor’s assets are lost to rounding, in favour of the vault. At the same time, the only other depositor in the vault, the attacker, is allowed to withdraw the entirety of the victim’s assets.

Share price manipulation
Vault shares are themselves an ERC20 token which can be used as any other asset in DeFi. They can act as collateral in a lending protocol, supporting debt, or can be borrowed themselves. As such they need to be priced somehow. For example, if the share tokens are used as collateral, and the share price is artificially raised, an attacker might borrow out more value than they really are allowed to. An example of the attack using this vector was the CREAM attack.

How to manipulate an exchange rate

Vaults are built specifically to enable the exchange rate between the shares and assets to fluctuate. Depositors add assets in the vault, receiving vault shares in exchange. It is expected that the vault will have some mechanism to multiply the assets it holds, which in return means that each vault share held by depositors increases its value in asset terms.

A straightforward approach to calculate the vault share price, or exchange rate, is to divide the amount of underlying asset held by the vault by the number of shares issued, which will price the vault shares in terms of the underlying asset.

With this straightforward approach to vault share prices, manipulating only one of the amount of shares issued or the amount of assets held by the vault will manipulate the vault share price in the same measure. This is what an attacker will try to do.


Direct donation
In common ERC4626 implementations, such as Solmate and OpenZeppelin, the conversion between assets and shares relies on the number of shares issued by the vault, and the assets it holds.

The amount of shares issued is recorded in the vault contract, but the assets it holds are not. These are recorded as the balance of the vault contract in the asset contract. This balance can be altered by anyone who transfers additional tokens to the vault's address, an action known as a donation. When a vault unexpectedly receives a donation, the value of each existing share increases. The CREAM attack is an example where this type of direct donation attack occurred.

Stealth donation
Another way to unbalance the shares and assets ratios is through abuse of the rounding logic in deposits and withdrawals. When users deposit assets into a vault, the assets are converted to shares to be added to the user's balances. The conversion rounds down, so that if the amount of assets added is not an exact multiple of the amount of assets needed to issue one share, the extra assets are accounted to the vault rather than to the user.

During withdrawal, shares are converted back to assets, rounding down again, again leaving the rounding remainder of assets in the vault. Effectively the rounding remainders are extra assets that don’t have a representation in shares. They are ‘stealth donated’ to the vault. 

Applied iteratively, stealth donations can be very effective at manipulating the exchange rates with exponential growth on every step. It does require, however, that the exchange rate starts out relatively high in order for the rounding remainders to compound. The recent Wise lending attack is an example of a stealth donation attack.

It is important to note that any other features that are added to a tokenized vault where there is a conversion between share and asset amounts are vulnerable to stealth donations. For example, in lending vaults another way to force unexpected asset amounts into the vault might be through debt repayments.

When asked for debt balance, the vault will most probably answer by rounding the liability with accrued interest up, in favor of the vault. This opens yet another stealth donation vector. It could be potentially devastating, since it provides an initial exchange rate bump from which a regular stealth donation through deposit/withdraws can be looped with exponential effectiveness. In our simulations, with no mitigation mechanisms in place, this would allow the attacker to manipulate the exchange rate almost arbitrarily within two blocks. This goes to show how important it is to also consider the logic each particular vault implements.

Unaccounted Flash Loans
Vaults are typically well-protected against the unauthorized removal of assets, which would be the inverse of a donation.

However, it can be the case that the vault is also a flash loan lender. While serving a flash loan, the vault will lend the assets it holds. In this scenario, the vault balance in the asset contract will decrease, affecting the share value.

How not to get rekt

There are many available defensive mechanisms, none of which has been demonstrated to be a silver bullet. They each protect only against certain types of manipulations and each has their own trade-offs. 

Initial deposit (dead shares) 
The dead shares method was first introduced in Uniswap V2. During the vault deployment an initial deposit is made and the resulting shares are burned. The method makes both direct and stealth donations more difficult. The amount of assets required by direct donation must be multiplied by the initial deposit size in order to have the same effect. The initial deposit also sets a minimum amount of shares and assets in the conversion equations, which means the subsequent rounding errors have a much smaller impact than if the vault started out empty.

The drawback of this method is that the vault creator needs to acquire and forfeit a small amount of assets, which are effectively taken out of circulation. The profits earned by dead shares are lost. There are also issues with this approach at a technical level, like approving a contract which is being created. 

Virtual deposit
The virtual deposit solution is described in the OpenZeppelin docs and applied in their ERC4626 reference implementation. A single virtual asset (1 wei)  and a corresponding 1 share are included in the conversions, with the share optionally scaled up with a decimal offset. The method is easy to implement and works very well against first deposit attacks. 

The only negative effect is that the virtual share would also be accruing interest, which should be accounted to the real depositors, but instead is irrevocably lost, since the share is not redeemable. The value of a single share’s earnings is presumably negligible.

The method offers little protection in terms of share pricing though, as it does little to prevent exchange rate inflation. A virtual deposit of a larger amount of assets, however, does protect against exchange rate manipulation in the same way as an initial deposit, without most of its hussles.

Internal asset balance tracking
The amount of assets the vault holds can be tracked internally on every user operation, instead of relying on the vault’s balance in the token contract. This method completely eliminates a direct donation attack, but offers no protection against stealth donations. Among the trade-offs there is incompatibility with rebasing tokens and potential loss of inadvertently donated assets, which may require a form of assets rescue mechanism.

Increased shares precision
In the OpenZeppelin method mentioned previously, shares are optionally tracked with increased precision compared to assets. Effectively, the exchange rate starts out lower than 1, which makes stealth donation infeasible in practice with appropriate precision increase factor. The increased shares precision needs to be handled somehow though. In OpenZeppelin’s implementation, ERC20 shares token has more decimals than the asset. This can be problematic as tokens with more than 18 decimals could be difficult to integrate with the rest of DeFI. 

Alternatively, share decimals could stay the same as that of the asset, but the exchange rate would arbitrarily start at an inflated value. For 6 decimals, 1 million shares in the DAI vault would be worth 1 DAI. Both options are further problematic if vaults would be nested (vaults holding shares of other vaults as assets), in which case the decimals or initial exchange rate would compound with every nesting level. 

Yet another approach would be to track shares with increased precision internally only, and convert them to asset’s decimals in all external getters and logs, which comes at the cost of additional code complexity and minor gas cost increase.

Averaging share price
To protect against immediate share price spikes, convertToAssets and convertToShares functions, according to ERC4626 specs, could average the exchange rate over some time period, similar to how DEX price oracles average spot prices. The averaging could also include heuristics about the specific vault implementation. For lending vaults, the convert functions might be taking into account limits on the possible exchange rate changes stemming from limits on interest rates. This approach has an advantage of not assuming any method of exchange rate manipulation, which should make it robust against novel attack vectors, but is imprecise and introduces runtime costs and code complexity.

Discarding stealth donations
This is a novel approach to mitigate the stealth donation manipulation method. The method consists of not including assets from rounding remainder during deposit or withdrawal to the internal asset balance of the vault. 

Consider an example: normally in a vault with an exchange rate of 2, a deposit of 3 assets would create a single share for the user (3/2 rounded down), worth 2 assets, but the total internal balances would be increased by the full deposited amount (3). The rounding remainder, 1 wei, constitutes a stealth donation.

As a mitigation to the stealth donation, the vault could recognize it and ignore it when reporting on the assets it holds. To achieve this, the vault would need to track its own asset holdings and only add the exact amount of assets that was needed to issue shares, or the vault could keep an additional counter of assets that were not used for issuing shares and should be ignored for exchange rate calculations. A similar logic can be applied to withdrawals.

The method doesn’t eliminate the inflation entirely, but it greatly reduces its effectiveness. Instead of compounding the stealth deposits in an exponential fashion, the method limits the effects of each additional rounding as the exchange rate grows, transforming the function to logarithmic.

Virtual assets during flash loans
During flash loans, the vault will lend its assets without changing the number of shares issued. Several of the exchange rate implementations would see the exchange rate fluctuate as flash loans are served. A possible mitigation is to track the amount of assets that have been flash lent, which would be added to the total assets held by the vault.

Tracking exchange rates explicitly, heuristics
Given how vaults are built around some particular strategy to handle shares to assets exchange rates, to make sure no other possibility exists to influence them, developers can opt for tracking the exchange rates explicitly in storage, instead of using vault assets and shares totals in each conversion. This method eliminates the donation manipulations entirely, but introduces additional complexity and has not been tried with real assets at the time of writing. Of course, it doesn’t prevent attacks that don’t involve forcing additional assets into the vault.

If relying totally on exchange rate updates, without taking into account actual asset balances, is too far fetched, there could be ways to take advantage of the particular way the exchange rate is designed to change.

In the case of Euler’s lending vaults, the exchange rate should only change due to interest accrual, which in turn only happens when time elapses. The vault implementation only accrues interest once, in the first transaction in a block. For the following transactions no time would have elapsed, so the exchange rate should stay the same. By snapshotting the exchange rate for the duration of a block we could prevent stealth donations’ exponential effects within one transaction.

This mechanism has never been implemented in production vaults at the time of writing, and therefore its performance remains theoretical.

Conclusion

In this article we identified two attack scenarios for tokenized vaults (first deposit and large donation) and two main attack vectors (direct donation, and stealth donation). We then listed several possible mitigations and detailed how each one defends against each scenario and vector combination. We expect that in many cases two or more mitigation mechanisms might need to be combined for greater effect. We hope that in sharing this research it can benefit the broader DeFi developer community and bring about a safer ecosystem for all.

Thanks to Kasper, Michael and Doug. Their contributions to the research made it possible.

Euler Blog

Related blogs

2024 Euler © All Rights Reserved

Press enquiry:

[email protected]