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

Endaoment A-2

Security Audit

September 22, 2023

Version 1.0.0

Presented by 0xMacro

Table of Contents

Introduction

This document includes the results of the security audit for Endaoment's smart contract code as found in the section titled ‘Source Code’. The security audit was performed by the Macro security team from September 18, 2023 to September 19, 2023.

The purpose of this audit is to review the source code of certain Endaoment 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
Low 1 - - 1
Code Quality 4 - 1 3
Gas Optimization 3 - 2 1

Endaoment was quick to respond to these issues.

Specification

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

Trust Model, Assumptions, and Accepted Risks (TMAAR)

Endaoment is a nonprofit 501c3 and, as such, is legally required (through the Pension Protection Act of 2006) to have exclusive legal control over all contributions made to the system at any moment. Due to this, the contracts pose a centralization risk whereby the contract owner can shut down the portfolio; withdraw all assets; change the redemption, deposit, and AUM fees; and arbitrarily execute any function at any time. It is trusted that the owner will not abuse this power.

Source Code

The following source code was reviewed during the audit:

Specifically, we audited the following contracts within this repository:

Source Code SHA256
src/portfolios/AaveUSDCPortfolio.sol

21fff0824694a525bb947f409a76cd4a9e15548a535911b289f8839e89e8c5c0

src/portfolios/AaveV3USDCPortfolio.sol

3631f3e3f27100b179bb3d5342bc2e74d0c3db5ef1f7f4c80af750b03f14267e

src/portfolios/CompoundUSDCPortfolio.sol

cf79d72906d61254e887efeae54ca8c6ae0a7503165bb44f05f31247ac77bf82

src/portfolios/CompoundV3USDCPortfolio.sol

f9a2d7fac30e0efc35ec30ddcf9015846a422eb06968cd1955103a92e71dd203

src/portfolios/YearnUSDCPortfolio.sol

a2da3add3bb4c3adcd6c29d3435ff103ccdf6c69a86fa0460ba67559d823f459

src/Portfolio.sol

df36195ba086b5251a17264b03e7ca1188383dfd7d0fe17f2ee9fd358405acc6

src/lib/auth/EndaomentAuth.sol

fc2c744004e4bc4445878f4ae932f121240a1fd47c285c1e0d37d3fbadd0ea30

Note: This document contains an audit solely of the Solidity contracts listed above. Specifically, the audit pertains only to the contracts themselves, and does not pertain to any other programs or scripts, including deployment scripts.

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

L-1

Aave may activate the referral program

Topic
Protocol Design
Status
Impact
Low
Likelihood
Low

Currently Aave’s referral program is inactive for both V2 and V3 contracts, but could be re-implemented pending a governance vote. Currently in AaveUSDCPortfolio and AaveV3USDCPortfolio the referalCode used is set to 0, which indicates no referral, but in the case where the program starts back up, there may be a benefit to setup a referalCode for Endaoment and use on deposits.

Remediations to Consider

Add a permissioned function that allows setting the referalCode in case Endaoment would benefit from referring entities to use Aave.

Q-1

Repeated asset checks in constructor

Topic
Code Quality
Status
Quality Impact
Low

In each of the yield bearing portfolio contracts, in their constructor there are two checks to ensure that the baseToken is equal to the set asset and the asset returned by _getAsset.

// The `asset` should match the base token, which means we expect it to be USDC. So we
// execute some checks to make sure inputs are consistent.
if (address(baseToken) != _getAsset(_receiptAsset)) revert AssetMismatch();
if (address(baseToken) != asset) revert AssetMismatch();

Reference: CompoundV3USDCPortfolio.sol#L52-L55

However, both are equivalent checks, as the value of asset is set in the constructor of Portfolio.sol using the _getAsset() function.

asset = _getAsset(_receiptAsset);

Reference: Portfolio.sol#L120

Remediations to Consider

In each of the yield bearing portfolio contracts, remove one of these checks, as they are duplicates.

Q-2

Use baseToken rather than casting asset to an ERC20

Topic
Code Quality
Status
Wont Do
Quality Impact
Low

Since there is a constraint for each of the yield bearing portfolio contracts that the asset equals the baseToken in their constructors, any call to transfer the asset tokens can directly be called on baseToken instead of casting asset to an ERC20.

Remediations to Consider

Replace casting asset to an ERC20 and use baseToken instead.

Response by Endaoment

Casting asset is more idiomatic than using baseToken directly

Q-3

Portfolio contracts are limited to Ethereum

Topic
Code Quality
Status
Quality Impact
Low

Endaoment is deployed on both Ethereum and Base, however the yield bearing portfolios use constant address values for the protocols they interact with that are deployed on Ethereum. In the case where a portfolio would want to be deployed on another chain, like Base, it would require a duplicate contract to be added to the repo with a differing protocol address, if the address of the protocol varies from the Ethereum version. If these addresses were immutable and initialized in the constructor, then these portfolio contracts could be deployed on any desired chain that the portfolio’s protocol supports.

Remediations to Consider

Replace constant protocol addresses with immutable values and initialize the values in their constructors.

Q-4

Incomplete NatSpec documentation

Topic
Code Quality
Status
Quality Impact
Low

In the following instances, the NatSpec documentation is incomplete:

  1. In YearnUSDCPortfolio, convertToUsdc() and convertToYvUsdc() are missing @param and @return fields
  2. In CompoundUSDCPortfolio, compoundExchangeRateCurrent() is missing the @return field
G-1

Can reduce the number of ERC20 transfer calls

Topic
Gas Optimization
Status
Wont Do
Gas Savings
Medium

Whenever a entity either deposits or redeems, it invokes multiple ERC20 transfers to the treasury due to AUM fees as well as deposit/redemption fees. Each of these costs the entity extra gas to execute, and could be reduced.

Remediations to Consider

Portfolio could be reworked to transfer the sum of the AUM fees and the deposit/redeem fees to reduce the number of external transfer calls. Alternatively instead of transferring fees immediately, keep track of fees owed and allow a permissioned function to take the owed fees and reset the tracked value, while making sure to subtract the fees owed from any calculations relating to the total supply of funds in the portfolio, while ensuring rebasing tokens are handled appropriately. Doing either would reduce the gas cost for users interacting with the protocol.

Response by Endaoment

Prefer separation of AUM and deposit/redemption code and fee assessment

G-2

Unnecessary asset check in _maxCap()

Topic
Gas Optimization
Status
Wont Do
Gas Savings
Low

In Portfolio.sol, the function _maxCap() checks the registry to ensure the asset is the same as the registry’s baseToken.

function _checkCap() internal virtual {
    if (asset != address(registry.baseToken())) revert BadCheckCapImplementation();
    if (totalAssets() > cap) revert ExceedsCap();
}

Reference: Portfolio.sol#L295-L298

However, in each of the 5 portfolio contracts in scope, it is ensured in the constructor that the asset is the same as the registry’s baseToken, since both of which are immutable, the check is unnecessary.

if (address(baseToken) != asset) revert AssetMismatch();

Reference: AaveUSDCPortfolio.sol#L56

Remediations to Consider

In each of the portfolio contracts in scope, override the _checkCap() function, removing the unnecessary check, reducing gas costs.

Response by Endaoment

Check was recommended in last audit, so we’ll leave it in place

G-3

Use baseToken value in _checkCap()

Topic
Gas Optimization
Status
Gas Savings
Low

In Portfolio.sol, the _checkCap() function queries the registry for the baseToken and checks to see if the portfolios asset is the same.

function _checkCap() internal virtual {
    if (asset != address(registry.baseToken())) revert BadCheckCapImplementation();
    if (totalAssets() > cap) revert ExceedsCap();
}

Reference: Portfolio.sol#L295-L298

However, the baseToken value in the registry is immutable, so it cannot change, and in Portfolio.sol’s constructor it sets the value of baseToken by querying it from the registry, so the value of baseToken will be the same as the value queried from the registry, but costs less gas to use as it isn’t an external call.

Remediations to Consider

Use baseToken instead of registry.baseToken() in _checkCap() to prevent an external call, and reduce gas cost.

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 Endaoment 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.