Reach out for an audit or to learn more about Macro
or Message on Telegram

Sommelier A-9

Security Audit

Aug 4, 2023

Version 1.0.0

Presented by 0xMacro

Table of Contents

Introduction

This document includes the results of the security audit for Sommelier's smart contract code as found in the section titled ‘Source Code’. The security audit was performed by the Macro security team from July 31, 2023 to August 4, 2023.

The purpose of this audit is to review the source code of certain Sommelier Solidity contracts, and provide feedback on the design, architecture, and quality of the source code with an emphasis on validating the correctness and security of the software in its entirety.

Disclaimer: While Macro’s review is comprehensive and has surfaced some changes that should be made to the source code, this audit should not solely be relied upon for security, as no single audit is guaranteed to catch all possible bugs.

Overall Assessment

The following is an aggregation of issues found by the Macro Audit team:

Severity Count Acknowledged Won't Do Addressed
High 3 - - 3
Medium 1 - - 1
Low 2 - - 2
Code Quality 4 - - 4
Informational 1 1 - -

Sommelier was quick to respond to these issues.

Specification

Our understanding of the specification was based on the following sources:

Source Code

The following source code was reviewed during the audit:

Specifically, we audited the following contracts within this repository for the initial part of the audit:

Contract SHA256
src/Deployer.sol

8de003d3f99f5ee9ec9af9878f60e7f951ef81ee024c74d24e4b7c1586239fd2

src/Registry.sol

3f2260cefb52d14a46b1c58f388c29003fd936e0378ec6100e340e7cbe91b898

src/base/Cellar.sol

da45d0063828460c28f1b7bf61aea41dbf8b55c068d0e176e1a77b05a7885cfb

src/base/ERC4626SharePriceOracle.sol

8fb479f2a5e5923277f2c1c24071ae8202442d05e6d7b17189948183e9dff6cf

src/base/permutations/CellarWithAaveFlashLoans.sol

84ccc31b14adb1630540117df3a171e023c30bda8eb6d80947de2eb8d814dbe9

src/base/permutations/CellarWithBalancerFlashLoans.sol

abc25211a31b23c18ef38412e9ab572d39cb417e58a7342ce56d07a3f73e857d

src/base/permutations/CellarWithOracle.sol

54a2d06a27398d807443f5d10e3c67ca380ed9aa0aac138ef5931576e9d846ca

src/base/permutations/CellarWithOracleWithAaveFlashLoans.sol

9ab08dcb8beb3756b26bf1a6084a3d47403c4c2e8da89e26ce5479519e644121

src/base/permutations/CellarWithOracleWithBalancerFlashLoans.sol

a22e32be23195483227b51326396aec6c3a3f21468cc5c2ead9be949d23dea23

src/base/permutations/CellarWithShareLockPeriod.sol

73bb18be6bac1dd3af305099e15e6306e0fef214fef370d7757d22bb9d948e2a

src/modules/adaptors/Balancer/BalancerPoolAdaptor.sol

ce73ac1e92105ad85c707a9b4946d836f782eba469c161d5a33c429f7cca65ec

src/modules/adaptors/Sommelier/LegacyCellarAdaptor.sol

a2162b8f914b0a68d4bc51aa4623e6ec585acca7136b7561c27934cf588fed38

src/modules/price-router/Extensions/Lido/StEthExtension.sol

8201d6303d412a5d0fd3a2a6065d07a42f4654be9fda199a331ef1453727768f

Issue Descriptions and Recommendations

Click on an issue to jump to it, or scroll down to see them all.

Security Level Reference

We quantify issues in three parts:

  1. The high/medium/low/spec-breaking impact of the issue:
    • How bad things can get (for a vulnerability)
    • The significance of an improvement (for a code quality issue)
    • The amount of gas saved (for a gas optimization)
  2. The high/medium/low likelihood of the issue:
    • How likely is the issue to occur (for a vulnerability)
  3. The overall critical/high/medium/low severity of the issue.

This third part – the severity level – is a summary of how much consideration the client should give to fixing the issue. We assign severity according to the table of guidelines below:

Severity Description
(C-x)
Critical

We recommend the client must fix the issue, no matter what, because not fixing would mean significant funds/assets WILL be lost.

(H-x)
High

We recommend the client must address the issue, no matter what, because not fixing would be very bad, or some funds/assets will be lost, or the code’s behavior is against the provided spec.

(M-x)
Medium

We recommend the client to seriously consider fixing the issue, as the implications of not fixing the issue are severe enough to impact the project significantly, albiet not in an existential manner.

(L-x)
Low

The risk is small, unlikely, or may not relevant to the project in a meaningful way.

Whether or not the project wants to develop a fix is up to the goals and needs of the project.

(Q-x)
Code Quality

The issue identified does not pose any obvious risk, but fixing could improve overall code quality, on-chain composability, developer ergonomics, or even certain aspects of protocol design.

(I-x)
Informational

Warnings and things to keep in mind when operating the protocol. No immediate action required.

(G-x)
Gas Optimizations

The presented optimization suggestion would save an amount of gas significant enough, in our opinion, to be worth the development cost of implementing it.

Issue Details

H-1

Governance can set malicious oracle

Topic
Trust Assumption
Status
Impact
High
Likelihood
Low

Currently the trust model is setup so that both governance and the multi-sig need to be compromised before anyone can act maliciously. This requires that positions and trusted addresses that governance may want to use are set in the registry contract by it’s multi-sig owner. However, CellarWithOracle.sol’s setSharePriceOracle() allows governance to set the cellars sharePriceOracle to any address as it is not verified by the registry contract.

function setSharePriceOracle(ERC4626SharePriceOracle _sharePriceOracle) external onlyOwner {
    if (_sharePriceOracle.ORACLE_DECIMALS() != ORACLE_DECIMALS) revert Cellar__OracleFailure();
    sharePriceOracle = _sharePriceOracle;
    emit SharePriceOracleUpdated(address(_sharePriceOracle));
}

Reference: CellarWithOracle.sol#L63-L67

This could allow governance to set the sharePriceOracle to be a malicious contract that could return favourable pricing, allowing minting and redeeming of shares to drain the cellar of its assets.

Remediations to Consider

Instead of passing in the address of the new sharePriceOracle, use a registry id and call getAddress() on the registry with that id to receive an address that has been approved. This adds an additional layer of protection for shareholders as it would require both the multisig and governance to act maliciously to set an invalid oracle. There should also be a check to ensure the returned address from the registry is the one expected, doing so prevents the registry’s multisig from front-running the transaction and setting the address to an unintended address, as is done in Cellar.sol’s setAutomationActions().

H-2

Governance can set an unverified automationActions

Topic
Trust Assumption
Status
Impact
High
Likelihood
Low

As discussed in issue H-1, addresses set should be trusted by the registry beforehand. In Cellar.sol’s setAutomationActions() it seems like the address being set has been verified by the registry.

function setAutomationActions(uint256 _registryId, address _expectedAutomationActions) external onlyOwner {
    address registryAutomationActions = registry.getAddress(_registryId);
    if (registryAutomationActions != _expectedAutomationActions) revert Cellar__ExpectedAddressDoesNotMatchActual();
    automationActions = registryAutomationActions;
}

Reference: Cellar.sol#L1206-L1210

However, in Registry.sol’s setAddress(), if the id being set is 0, and the current address set as the zero id is the caller, they can change the address.

function setAddress(uint256 id, address newAddress) external {
    if (id > 0) {
        _checkOwner();
        if (id >= nextId) revert Registry__ContractNotRegistered(id);
    } else {
        if (msg.sender != getAddress[0]) revert Registry__OnlyCallableByZeroId();
    }

    emit AddressChanged(id, getAddress[id], newAddress);

    getAddress[id] = newAddress;
}

Reference: Registry.sol#L60-L71

Since the address of id zero is the gravityBridge address, which is controlled by governance, this allows governance to set the id zero to be any address. Doing so allows governance to set a malicious address as the cellar’s automationActions, allowing this address to call callOnAdaptor(), which could be used to make many rebalances and drain assets from the cellar.

Remediations to Consider

When using the Registry.sol’s getAddress() ensure that the _registryId is not zero, preventing governance from setting an unverified automationActions address.

H-3

Trusted positions can be forced out

Topic
Trust Assumption
Status
Impact
High
Likelihood
Low

Cellar.sol’s forcePositionOut() allows the strategist to remove a position from being tracked while it still has a balance. This is supposed to only be used in cases where there is an issue with the position, such as it reverting when calculating share price. If the strategist were to force out a position that the cellar has a large balance in, the share price would drop. If shares were purchased, then governance added the position back, it would increase the share price, allowing bought shares to be sold for a profit and drain assets from the cellar.

Remediations to Consider

Check to ensure that the position being forced out is set to be untrusted in the registry. Doing so prevents the position from being added back by governance maliciously.

M-1

Missing oracle data validation

Topic
Input Validation
Status
Impact
High
Likelihood
Low

In ERC4626SharePriceOracle.sol’s performUpkeep() there are multiple checks to ensure the upkeep condition has been met, and the data’s timestamp is newer than the previous observation. However, it assumes that the data provided is honest, and not malicious. If chainlink automation were to act maliciously, performUpKeep() could accept either a very large new share price, and/or a timestamp that is larger than the current time. A sufficiently large share price would effect anything directly using the latest price, and would slowly effect the TWAA. If the provided timestamp is larger than the current time, then the price could have an immediate effect on the TWAA as well.

Remediations to Consider

  • Add a check to unsure the provided currentTime is not greater than the current timestamp.
  • Add a check to ensure the new share price is not too far deviated from the current price values
  • Consider adding a flag that marks the oracle unsafe to use if the provided values do not satisfy the suggested checks, as it may mean chainlink is acting malicious.
L-1

setAutomationActions missing event

Topic
Events
Status
Impact
Low
Likelihood
Low

Cellar.sol’s setAutomationActions() is missing an event that would indicate it’s been called.

It may be useful to watch for calls to this function, especially if they are not expected.

Remediations to Consider

Add an event to indicate what the automationActions address is set to.

L-2

distrustPosition missing event

Topic
Events
Status
Impact
Low
Likelihood
Low

In Registry.sol’s distrustPosition() does not emit an event. It would be beneficial to emit an event here so that call to this function can be more easily watched and reacted to in case a position is unexpectedly distrusted by the registry’s owner as well as to more easily determine which positions are trusted by the registry.

Remediations to Consider

Add an event that emits the information of the position when it is distrusted.

Q-1

Comments mention curve rather than uniswap

Topic
Code Quality
Status
Quality Impact
Low

In StEthExtension.sol there are multiple mentions of using curve as a secondary pricing source, however uniswapV3 is used for pricing instead.

Remediations to Consider

Replace all mentions of curve with uniswap.

Q-2

Missing callable address for callOnAdaptor()

Topic
Code Quality
Status
Quality Impact
Low

In Cellar.sol’s callOnAdaptor() a change was made to allow a set automationActions address to call it as well as the strategist, however the comments only mention that its callable by the strategist.

* @dev Callable by Sommelier Strategist.

Reference: Cellar.sol#L1311

Remediations to Consider

Mention that callOnAdaptor() is also callable by the automationActions address.

Q-3

Comment typo

Topic
Code Quality
Status
Quality Impact
Low

There is a typo haas in Cellar.sol

// After making every external call, check that the totalAssets haas not deviated significantly, and that totalShares is the same.

Reference: Cellar.sol#L1333

Remediations to Consider

Replace haas with has

Q-4

positionCount no longer used

Topic
Code Quality
Status
Quality Impact
Low

In Registry.sol the public variable positionCount is not longer used or referenced by anything and displays an inaccurate position count.

/**
 * @notice Stores the number of positions that have been added to the registry.
 *         Starts at 101.
 */
uint32 public positionCount = 100;

Reference: Registry.sol#L387-L391

Remediations to Consider

Remove this variable as it is no longer used and displays inaccurate information about the registry.

I-1

AutomationActions is in development

Topic
Informational
Status
Acknowledged
Impact
Informational

In Cellar.sol, there is an addition to allow an automationActions contract to be able to call callOnAdaptor(). The intention is for this to be a contract that strategists can set actions to be called if certain conditions are met, with the intent to use chainlink automation to verify and trigger these calls. This contract has yet to be developed, and should be audited before cellars use it, but this functionality was added so that new cellars would be able to take advantage of it in the future once it is ready.

Response by Sommelier

all Automation Action contracts will be audited BEFORE automationActions is set to a non zero value in newly deployed Cellars.

Disclaimer

Macro makes no warranties, either express, implied, statutory, or otherwise, with respect to the services or deliverables provided in this report, and Macro specifically disclaims all implied warranties of merchantability, fitness for a particular purpose, noninfringement and those arising from a course of dealing, usage or trade with respect thereto, and all such warranties are hereby excluded to the fullest extent permitted by law.

Macro will not be liable for any lost profits, business, contracts, revenue, goodwill, production, anticipated savings, loss of data, or costs of procurement of substitute goods or services or for any claim or demand by any other party. In no event will Macro be liable for consequential, incidental, special, indirect, or exemplary damages arising out of this agreement or any work statement, however caused and (to the fullest extent permitted by law) under any theory of liability (including negligence), even if Macro has been advised of the possibility of such damages.

The scope of this report and review is limited to a review of only the code presented by the Sommelier team and only the source code Macro notes as being within the scope of Macro’s review within this report. This report does not include an audit of the deployment scripts used to deploy the Solidity contracts in the repository corresponding to this audit. Specifically, for the avoidance of doubt, this report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and it is not a guarantee as to the absolute security of the project. In this report you may through hypertext or other computer links, gain access to websites operated by persons other than Macro. Such hyperlinks are provided for your reference and convenience only, and are the exclusive responsibility of such websites’ owners. You agree that Macro is not responsible for the content or operation of such websites, and that Macro shall have no liability to your or any other person or entity for the use of third party websites. Macro assumes no responsibility for the use of third party software and shall have no liability whatsoever to any person or entity for the accuracy or completeness of any outcome generated by such software.