Ensure the security of your smart contracts

Yield Aggregators Common Pitfalls.
Beefy Case Study

Author: Daniil Ogurtsov
Security researcher at MixBytes
In this article we will outline the security issues encountered in yield aggregators, a very common type of projects in DeFi. They have been around since the dawn of the industry, so they have come a long way and accumulated a wealth of experience in security techniques and best practices.

The article is aimed at describing bugs specific to yield aggregators. They have a unique context, so security audits can be more detailed and profound with these specifics taken into account. Additional frameworks of analysis can help to detect more sophisticated bugs.
How do yield aggregators work
Source: https://arxiv.org/pdf/2210.04194.pdf

DeFi projects usually require much liquidity for operating. Liquidity is limited in the industry and projects compete to attract it. Thus, DeFi projects are designed to incentivize liquidity providers - in the form of fees or token minting. These rewards depend on the user activity in these DeFi projects, hence the return for a liquidity provider is volatile.

Yield aggregator projects "aggregate" some of the options for liquidity providers and offer a few additional features and instruments:

1. They build "strategies" - special smart contracts responsible for:

  • determining the way the yield will be generated (where, how, in which tokens)
  • delivering liquidity to the final smart contracts where returns are generated
  • withdrawing and conveying liquidity back to liquidity providers
  • modifying risk/return profile (like take leverage for more risk/return by folding)
  • maximizing returns by special transaction flows (to receive more rewards in additional tokens)
  • optimizing return delivery (like selling reward tokens in pairs gradually)
  • taking into account special situations (liquidation risk, emergency withdrawals).

2. They operate in a growth-only manner. Yield aggregators prefer not to take impermanent loss risks and avoid situations of negative returns. Thus, investments are usually accepted in LP tokens. In some cases a yield aggregator can offer additional options for single-side depositing - sometimes it is available for stablecoin pools with large liquidity (like stable swap pools on Curve).

3. They do compounding - rewards are reinvested hourly/daily to boost further returns. It is the main reason for using yield aggregators for most users. It is far more effective to reinvest in one transaction for all liquidity providers simultaneously - in most of the projects it is called "harvesting". However, it is not only the reinvestment but also the moment for some agents to collect fees on positive returns (strategy builder, the yield aggregator itself, harvest function caller).

Features described above are the backbone of any yield aggregator, but the promoted product may vary. Some of them show off yield aggregating as a core tool (like in Yearn Finance, Beefy Finance), others can promote other products to users leaving yield generation itself secondary (like OUSD offering their stablecoin or Idle offering fixed or leveraged rates in the form of tranches). These special product pivots will be excluded from our analysis.
Main security analysis principles
We see two fundamental components of yield aggregators that make security analysis special.

1) Usually they have many vaults/strategies - their design and risk may vary significantly.

Yield aggregators prefer taking responsibility for the security of their vaults/strategies. Thus, yield aggregators design the processes of internal audits, testing, monitoring so that strategies are well analyzed before going live. Also, they invite external strategy builders and incentivize them to extend the marketplace of vaults/strategies.

It is different from most of the DEXes where pairs share the same smart contract but can have unpredictable tokens traded. DEX teams build only smart contract standards but usually are not responsible for tokens traded in pairs.

In contrast, yield aggregator vaults/strategies are poorly standardized - they have different transaction flows, different DeFi projects connected, different tokens transferred. Thus, even more variety of bad scenarios are possible. So, it is normal practice for yield aggregators to invite external auditors to analyze some of the vaults (especially the core ones).

2) Unique set of Trust Assumptions.

Vaults have strategy builders and managers. They have some privilege functions designed to be in favor of liquidity providers. But these owners are potentially bad actors. So, auditors must make a point of owners as attackers who wait for a moment to exploit their privileges.
Summary of common security pitfalls
Manipulated conversion rate between shares and underlying
The conversion between shares and tokens accepted is a tender spot for many DeFi projects. Wrong calculations are easily exploited.

Yield aggregators are not far different here - they usually mint tokens, perfectly described by Beefy Finance as "interest-bearing tokenized proof of deposit". Users' balances do not rise with more rewards earned, but one share should represent more and more underlying tokens with more rewards accounted. This conversion rate is dynamic and can be exploited.

Rate calculation tend to use oracles or DEXes. Excessive usage of DEXes is the core point where vulnerabilities are hidden. This is the thing attackers exploited when hacking the most significant yield aggregators. An attacker's goal is to decrease share conversion rate and increase the later one.

The general advise for analysis is the same as for many projects using DEXes for accounting - to assume the DEX can be set to any price within the same transaction. The red flag is detected if it is possible to make deposits and withdrawals in the same transaction while using different conversion rates.

Relying on DEXes may have different forms. Consider PancakeBunny hack (link to exploit analysis) that rewarded depositors with 3 BUNNY tokens for every 1 BNB in fees collected. However, the return is calculated in LP tokens and required conversion to BNB nomination and PancakeBunny made a mistake to use DEX for this conversion which resulted in recursive manipulated BUNNY minting for the attacker.
Swaps not protected from sandwich attacks
Strategies widely use swaps. They often try not to earn just protocol fees; they tend to maximize additional incentives in additional tokens, like PancakeSwap mints additional CAKE swaps for liquidity provision in relevant pools. These additional rewards are never accumulated for a long time - a strategy swaps them in DEXs immediately on every harvest.

Attackers can exploit these predictable transactions that sell tokens in a constant manner. They make sandwich attacks to increase slippage for the vault and receive risk free tokens as their returns.

The second spot with the same problem can be found in the vaults that receive investments in different tokens. The vaults swap these tokens in the very beginning to receive LP for yield generation.

The recommendation here is straightforward - to control risk tolerance in swaps, include invariants of a minimal token to receive after swaps.
Attacks from the owner
As we stated previously, it is the best practice to assume vault owners/managers as bad actors. They are usually not related to the yield aggregator's team.

Here are some vectors typically available for owners/managers:

  • when current vault/strategy functions allow arbitrary calls;
  • when a vault allows changing a strategy. It is the case when the previous one turns ineffective and requires an update. Many yield aggregators have this feature. It is very convenient for all parties, otherwise updating would require withdrawals from every single liquidity provider. As a result, a new version can introduce additional hidden privileges for owners;
  • tricks to boost rewards/fees for managers artificially.
Token-specific risks
For many yield aggregators the number of active strategies covered can be in dozens or hundreds - a unique set of projects affected and tokens transferred.

The variety of tokens deviating from the predictable ERC-20 standard can introduce additional exploit vectors. It is the case for many DeFi projects, but here the situation is less under control as the nature of yield aggregators itself implies a large diversity of scenarios that is why the appearance of unusual tokens is considered likely.

All types of unusual tokens are risky for strategies - inflationary/deflationary, rebasing, interest-bearing, hookable, having weird decimals, returning nothing on function calls. That is why a special focus is placed on tokens when analyzing strategies.

That is also the reason why auditors recommend putting nonReentrant modifiers for all key functions - to get rid of the risk of unexpected hooks. Very simple but very effective.
Bad balance management
Sometimes rewards are calculated wrong. Some reward tokens can be ignored, or stuck on some smart contracts. Rewards deserve strong testing, as there are so many spots to have an error.
Poor vault lifecycle design
Bad vault design can underestimate bad scenarios occurring with vaults. Some of them imply that only deposits and withdrawals demonstrate the full lifecycle. That is obviously not true. Vaults must work with the wider risk profile and must design risk mitigation procedures. Pools that generate yield can be hacked, positions on lending protocols can be liquidated, etc.

Team must check that special functions are embedded for owners/managers to deal with such situations (to pause, to emergency withdrawal, to manage liquidity position and leverage/folding).

It must be noted that this is one of the hardest topics to analyze, since it requires not only an understanding the code, but also the variety of scenarios of what can go wrong.

In the following case study, we will take a simple vault and try to analyze it in terms of these attack vectors.
Case study: evaluating Beefy Finance
Here, we will describe Beefy Finance vaults and how they deal with attack vectors.
Our example is the CAKE-BNB LP Vault.

Here is the illustration of smart contract system for this vault, including the money flow.
This Beefy vault touches five contracts, three of them are written by Beefy: Vault, Strategy, BoostStaker. Every contract has its own role.

Vault - is responsible for interaction with users (receiving LPs and sending LPs grown). Every deposit mints shares, every withdrawal burns shares. The contract is ERC20, thus shares are transferable as usual tokens. The vault is a key spot for conversion logic between LPs and shares.

Strategy - manages the whole logic of token transfers. Harvesting takes place here - it should 1) reinvest grown LP and 2) sell additional rewards in other tokens, sell them on DEX to receive token0 and token1, deposit them into the DEX pair to convert them to more LP.

BoostStaker - this is the intermediate aiming to boost the received token rewards via sending a portion of them to a veContract if it exists for a reward token and if it is included in the strategy. This Beefy strategy uses this BoostStaker but does not send tokens to veContract - so additional rewards isn't farming in fact.

MasterChef - a contract from PancakeSwap receiving LPs, LPs must come from the whitelist, usually from pairs with CAKE token from one side. MasterChef holds ~1M dollar balance in CAKES and distributes them to the depositors of LPs. That is the well-known instrument for projects to incentivize liquidity provision. This is the source of CAKES in the strategy.

DEX - the strategy does not evaluate the return in CAKE tokens. In order not to deal with them, every CAKE on balance is sold on DEXes to receive more LPs.

Sometimes earned minted tokens (like CAKE in our example) can be staked into veContract with the timelock to receive an additional mint distribution (BoostStaker module). But it is not the case for this CAKE-BNB LP Vault.

Let's go through some common attack vectors, one by one, and find some arguments that this vault is secure from attacks.
Vector one: Manipulated conversion rate between shares and underlying
We will illustrate a simple framework to analyze conversion rates. We will check that an attacker cannot withdraw more than deposited.
Steps are as follows:

  1. to take functions where conversions happen
  2. to decompose conversions to understandable formulas
  3. to analyze every element in the formulas, to check whether an attacker has options to manipulate these elements. Manipulating any of elements means manipulating the whole formula return, thus the whole conversion rate.
The main contract dealing with this issue is Vault.
This is the code of the deposit/withdraw function.
Let's decompose it to simpler formulas.
These formulas are derived from the code and aimed at illustrating all components separately. Some lines deal with the risk of deflationary tokens (where tokens can be lost during transfers). We leave this in formulas.

Also, you may notice from the code that function withdraw() additionally checks for b - LP balance on the Vault contract. We ignore it for now, but we will get back to it later when analyzing other vectors. We have found interesting math errors.

We will focus on each formula element and try to estimate possibilities for an attacker to manipulate an element number. Also, we assume that an attacker has unlimited funds and can flashloan at any time. An attacker's goal is to boost [shares per LP] when depositing and lower [share per LP] when withdrawing - potentially it will allow to withdraw more funds than deposited (but not necessary).

Now, let's analyze all of the elements:

  1. Total shares minted
    This parameter is calculated before any token movements, and there is no way for an attacker to mint/burn shares using other functions. Additionally, this parameter is stored as a contract variable, and no calls are required to calculate it.
    Therefore, this element is protected.
  2. LP balance on Vault + LP balance on Strategy + LP accrued on MasterChef
    These parameters are easily boosted - an attacker can send tokens on these contracts, but it does not always mean anything dangerous. First of all, an attacker cannot revoke these sent tokens. Secondly, in our case an attacker boosts [shares per LP], but the return from these manipulations is less than the cost of balance boost. Imagine 100 shares of supply and 200 LP tokens in balances, the conversion rate is 0.5. First, an attacker deposits on the current rate with 100 LP tokens. Now we have 150 shares and 300 LP tokens. Then, the attacker sends directly 150 tokens on balances. The conversion rate turns 150 /450 = 0.25. Yes, the attacker managed to lower the [shares per LP], but there will be no profit from such an attack. The attacker withdraws 50 shares but receives 450 *50 /150 = 150 LP tokens though the cost was 100 LP tokens (for deposit) and 150 tokens (for direct balance boost).
    Therefore, this element is not protected but it is not vulnerable.
  3. Percentage of LPs lost during transfers
    Here, the vault tries to deal with deflationary tokens.
    This element is protected and an attacker usually cannot affect this parameter in one transaction but some manipulations are possible for rebase tokens. An attacker can frontrun a balance update transaction and have a sequence of transactions with different conversion rates. However, it must be noted that yield aggregators prefer not to deal with rebase tokens (like many DeFi project).

As a result, the formula is protected from attacks. What lessons can we derive here?

  1. Most of numbers are taken before any token movements.
  2. It is simple.
  3. It does not fetch price feed and does not read external contracts much.
  4. Deposit and withdrawal formulas are the same.

Final words here, you could notice that the formulas are not quite the same. The trick is in the Percentage of LPs lost during transfers. We have multiplication in deposit and division in withdrawal. But that is ok in the context of deflationary tokens. It is always the losses for users: receive less shares when depositing, require more shares when withdrawing. Thus, deviation in formulas here is necessary.
Vector two: Swaps not protected from sandwich attacks
For yield aggregators, there are two common spots where unprotected swaps may exist:

  1. Vault externals: token conversions when depositing and withdrawing.
  2. Vault internals: token conversions when harvesting or other actions.
Let's take a look on both areas for our chosen CAKE-BNB Vault.
Token conversions when depositing and withdrawing
As you can see from the interface page, this Vault allows to deposit and withdraw multiple tokens.
But the Vault contract accepts only one token type - LP.
Thus, the conversion happens somewhere on external contracts.
This is the transaction flow described by Beefy.
This is an example of such a transaction.

From the transaction we derive the contract dealing with swaps.

Swaps here go through the DEX router an implement AmountOutMin everywhere - the parameter is input as data within transaction initiation, thus calculated offchain. The transaction example proves this amount is not set zero by default. This is enough to mitigate sandwich attacks.
Token conversions when harvesting or other actions
The Strategy contract is the one to deal with these swaps. In our strategy, swaps are used to exchange CAKEs received from the MasterChef as rewards.
As you can see, AmountOutMin is set to zero here. Thus, it is vulnerable for frontrun in case of significant slippage swaps. But the context is that high slippage swaps here are unlikely to occur - the pool is much larger than the average CAKE harvest amount. Moreover, harvesting can take place frequently, as the Vault operates on Binance Smart Chain - it lowers the likelihood of large CAKE harvesting to happen in one transaction.
Vector three: Attacks from the owner
Our CAKE-BNB Vault use Beefy standards to set role privileges - all vaults are the same and quite secure.
Vector four: Token-specific risks
In our Vault, tokens are quite common and do not have additional risks.
Vector five: Bad balance management
The Vault deals with simple tokens but leaves some code to mitigate surprises if deflationary tokens are transferred. It is not the case in our Vault, but it exists as a standard, as the code migrates from strategy to strategy. The code applies balance checking, thus only tokens received in fact are accounted.

We found one minor mistake that can lead to a wrong withdrawal calculation for a shareholder. This case doesn't look critical but it is interesting how it is possible by design.

The problem lies in this Vault function and how it is used.
This function calculates total vault tokens and is used to calculate [share per LP]. It summurizes LP balance on Vault, Strategy and accrued LP tokens on MasterChef.

Two conditions must be met:

  1. A significant amount of tokens on the Vault and Strategy contracts.
  2. LP is a deflationary token (the code tries to manage these situations).

In this case withdrawals will go wrong for the shareholders.
Recall the function.
Let's take a simple situation.
We have 100 shares minted, two shareholders 50/50.

And we have 200 LP tokens returning from the balance() function, with the distribution between contracts:

  1. 50 on Vault
  2. 50 on Strategy
  3. 100 on MasterChef

Let's apply 1% burning for the deflationary token when it is transferred.

Imagine the first shareholder to withdraw 50 shares.
According to the withdraw() function, this shareholder will withdraw 98.5 tokens.
When the second shareholder withdraws 50 shares, they will receive 96.05 tokens.
As a result, they had the same shares, but withdrew different LP tokens.

How did it happen?
Every contract withdraws funds from the next contract in the chain.
If the Vault contract has some LP tokens on balance, it will not withdraw them from Strategy. The same goes for Strategy - it tries to pay with its own funds before withdrawing from BoostStaker. And so on.
LP tokens on balances of "deeper" contracts are more expensive in case of deflationary tokens.
Applying 1% of burnrate on transfer, 100 LP tokens on MasterChef will take 4 transfers to appear on the user's wallet, only 96.05 will remain.

Thus, the first shareholder could withdraw "less expensive" tokens, with minimum transfers.
While the second shareholder is left with "more expensive" tokens that will experience 4 transfers when withdrawing.
In the withdraw() function, the key parameter is r which leans on the balance() function. This function adds up three components of the balance, each with different effective value.
For deflationary tokens it is not correct to summarize token balance on different contracts if they require different number of transfers.
Vector six: Poor vault lifecycle design
This vector is more applied for more sophisticated strategies. In our CAKE-BNB Vaults everything is straight-forward. Nevertheless, the Vault follows the internal standards and has emergency functions.
Internal processes to control risks
Some yield aggregators apply internal procedures to assess vaults and strategies.

Yearn onboards strategies with many steps and a high-risk scoring:

The same thing applies to Beefy Finance:
Yield aggregators deal with the whole set of situations possible in DeFi. That is why auditors must pay special attention to vault security analysis with its special vectors.

We have described only the most well-known security issues, those specific for yield aggregators. Obviously, all of the smart contract vulnerabilities can be met there and be destructive.
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