---
# System prepended metadata

title: 'Audit Recap #1: Blueberry Protocol'

---

# Audit Recap #1: Blueberry Protocol
*We regularly publish short recaps on a decentralized audit in which we participated. This time, we cover the audit of Blueberry Protocol, a DeFi yield farming protocol.*

![](https://i.imgur.com/gwUVS6Z.png)

*brainbot is a web3 service provider, offering consulting and development services as well as [smart contract audits](https://brainbot.com/smart-contract-audits/). To gain more experience in auditing, our security researchers regularly participate in [decentralized audits](https://medium.com/@brainbot/decentralised-audits-are-here-to-stay-bcee6d1118a8). In this series, we will publish recaps of audits in which we participated in order to provide some insight into the functioning, the smart contract architecture and our findings for the respective protocols.*

## Blueberry Protocol

Blueberry is a defi yield farming protocol. It allows users to deposit collateral into Banks and withdraw debt tokens from it to farm yield (e.g. on Ichi). The goal of this content is to give insight on the structure of the code as audited during the Sherlock contest, as well as a couple of interesting findings.

## Contracts structure

### BlueBerryBank.sol

The main contract of the protocol is `BlueBerryBank.sol`, it allows the owner of the protocol to define which tokens can be lent / borrowed via `addBank()`.
Each bank has the following fields:

```solidity
struct Bank {
    bool isListed; // Whether this market exists.
    uint8 index; // Reverse look up index for this bank.
    address cToken; // The CToken to draw liquidity from.
    address softVault;
    address hardVault;
    uint256 totalDebt; // The last recorded total debt since last action.
    uint256 totalShare; // The total debt share count across all open positions.
    uint256 totalLend; // The total lent amount
}
```

The cToken represents the compound-like token that accrues interests for the users of the bank. The softVault / hardVault are addresses to vaults holding the cTokens for a bank. Each softVault only holds one type of token while hardVaults hold an erc1155 token representing multiple underlying tokens.

Users Interact with the BlueBerryBank via “spells”. They do so using the `execute()` function, which checks that the spell is whitelisted at the beginning and
checks whether the user’s impacted / opened position is liquidatable at the end.

Spells will call `lend()`, `withdrawLend()`, `borrow()`, `repay()`, `putCollateral()`, and `takeCollateral()` functions on BlueBerryBank, that will impact the status of the
user’s position and transfer the adequate tokens.

A user’s position is represented with the following struct:

```solidity
struct Position {
    address owner; // The owner of this position.
    address collToken; // The ERC1155 token used as collateral for this position.  // @audit-info used to determine if liquidatable for value of position. = LP token from ichi vault
    address underlyingToken;
    uint256 underlyingAmount;  // @audit-info when you lend, you increase your undelyingAmount of your posisiton.
    uint256 underlyingVaultShare;  // @audit-info when you lend, you increase the underlyingVaultShare by the value returned by the soft/hard vault
    uint256 collId; // The token id used as collateral.
    uint256 collateralSize; // The size of collateral token for this position.
    uint256 debtMap; // Bitmap of nonzero debt. i^th bit is set iff debt share of i^th bank is nonzero.
    mapping(address => uint256) debtShareOf; // The debt share for each token.  // @audit-info share * bank.totalDebt / bank.totalShare is debt of position in token
}
```
collToken is the collateral token used for the position, it is used to determine if a position is liquidatable. This in effect will be the LP tokens for a deposit in ICHI for example. collId represents the token id used for erc1155 collateral tokens. The underlyingToken is the token provided by the user and deposited into the soft/hard vaults. The underlyingVaultShare is the amount of share returned by the vault upon deposit, it represents the percentage of the vault owned by the user for the corresponding underlying token. debtMap and debtShareOf are used in case the user has debt tokens in multiple banks.
As an example, the `lend(token, amount)` function will withdraw the amount of token from the user, set it as the `underlyingToken` of the position,
deposit these tokens into the corresponding vault, and set `underlyingVaultShare` accordingly for the position.

### Spells

A basic spell abstract contract is currently defined in `BasicSpell.sol` which provides a simple example of how a spell would interact with BlueBerryBank.
`IchiVaultSpell.sol` defines more interesting functions to `openPosition` / `openPositionFarm` and `closePosition` / `closePositionFarm`, among other functions.

The function `openPosition()` calls `lend()` and `borrow'()` on `BlueBerryBank` to put the underlying token of the user into the bank and borrow borrowToken from it.
It then deposits the borrowed token on an Ichi Vault to farm yield and puts the vault LP token into the bank via `putCollateral()`.

`openPositionFarm()` functions similarly, but deposits the LP tokens into an IchiFarm instead of into the `BlueBerryBank` and deposits the LP tokens of the IchiFarm into 
the bank instead.

`closePosition` and `closePositionFarm` are the corresponding functions to close the open position. They withdraw the LP tokens back from the bank, burn the LP tokens on 
the Ichi vault (after withdrawing from the farm for `closePositionFarm`) to get back borrowed tokens, repay the debt to the `BlueBerryBank`,
withdraw the underlying tokens lent by the user and refund the user.

### Oracles

The protocol uses multiple oracles to get the price of debt tokens or collateral used in the protocol. This is necessary used to determine if the position of a user
is liquidatable. A position is liquidatable if and only if `debtValue - positionValue >= threshold * underlyingValue`, the `threshold` is a parameter depending on the 
specific underlying token and is in between 80% and 90%.

The project provides adapter for price oracle using UnsiwapV2-V3, Chainlink, Band, and Ichi, as well as an aggregator oracle that takes prices of multiple oracles to
provide a median or average price response.

Actions on a position by a user is reverted if the position after the action is liquidatable.

A liquidatable debt can be liquidated by anyone calling `liquidate()` on `BlueBerryBank`. The liquidator will repay the debt of the user and receive its whole position: 
collateral as well as underlying token.

## Findings

In this section I'll describe two interesting vulnerabilities found for this audit. The first one that I found myself, the second that I missed and was found by other auditors.

### Liquidation logic flawed for multiple token positions

The `liquidate()` function allows a liquidator to repay the debt of an underwater user to take part of their collateral. The function is flawed as it considers the share of repaid debt for a single token of the position to repay the equivalent share of collateral to the liquidator. That is, for a position with debt in multiple tokens the liquidator can fully repay the debt in a single token to receive the total collateral of the liquidated user.

The faulty code is:

```solidity
    uint256 oldShare = pos.debtShareOf[debtToken];
    (uint256 amountPaid, uint256 share) = repayInternal(
        positionId,
        debtToken,
        amountCall
    );

    uint256 liqSize = (pos.collateralSize * share) / oldShare;
    uint256 uTokenSize = (pos.underlyingAmount * share) / oldShare;
    uint256 uVaultShare = (pos.underlyingVaultShare * share) / oldShare;

    pos.collateralSize -= liqSize;
    pos.underlyingAmount -= uTokenSize;
    pos.underlyingVaultShare -= uVaultShare;
```

As we can see, the code divides the `share` result of `repayInternal` and `pos.debtShareOf[debtToken]` representing the debt share and repaid debt share in a single token in the calculation for the liquidated collateral. It does not take into account the other debt tokens of the position.

This vulnerability can be categorised as a protocol / implementation specific bug, as one needs to understand the logic behind what the contract does to exploit it.

### Stale oracle data

The Chainlink adapter oracle uses the following code to get the price of a token:

```solidity
function getPrice(address _token) external view override returns (uint256) {
    // remap token if possible
    address token = remappedTokens[_token];
    if (token == address(0)) token = _token;

    uint256 maxDelayTime = maxDelayTimes[token];
    if (maxDelayTime == 0) revert NO_MAX_DELAY(_token);

    // try to get token-USD price
    uint256 decimals = registry.decimals(token, USD);
    (, int256 answer, , uint256 updatedAt, ) = registry.latestRoundData(
        token,
        USD
    );
    if (updatedAt < block.timestamp - maxDelayTime)
        revert PRICE_OUTDATED(_token);

    return (answer.toUint256() * 1e18) / 10**decimals;
}
```

According to [Chainlink's documentation](https://docs.chain.link/data-feeds/historical-data), the return values of `latestRoundData` are `(uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)`. The documentation further states that if "answeredInRound is less than roundId, the answer is being carried over. If answeredInRound is equal to roundId, then the answer is fresh." Additionally, "A read can revert if the caller is requesting the details of a round that was invalid or has not yet been answered. If you are deriving a round ID without having observed it before, the round might not be complete. To check the round, validate that the timestamp on that round is not 0. In a best-case scenario, rounds update chronologically. However, a round can time out if it doesn’t reach consensus. Technically, that is a timed out round that carries over the answer from the previous round."

The returned values should be checked for staleness by requiring that `answeredInRound >= RoundID`, `startedAt != 0`, and `answer > 0`.

This vulnerability falls in the category of oracle / price manipulation (an attacker could take advantage of a stale price).

## What's next?
We'll continue to regularly publish audit recaps for different protocols. Meanwhile, you can also have a look at our other publications on [Medium](https://medium.com/@brainbot).

## Hard Facts
**Decentralized Audit Platform:** [Sherlock](https://www.sherlock.xyz)
**Audited Protocol:** [Blueberry Protocol](https://app.sherlock.xyz/audits/contests/41)
**Security Researcher:** Côme du Crest (ranked 6/284 in this contest)
