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

Superstate A-4

Security Audit

Nov 18th, 2024

Version 1.0.0

Presented by 0xMacro

Table of Contents

Introduction

This document includes the results of the security audit for Superstate's smart contract code as found in the section titled ‘Source Code’. The security audit was performed by the Macro security team from Nov 12th to Nov 15th, 2024.

The purpose of this audit is to review the source code of certain Superstate 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 2 - - 2
Medium 1 - - 1
Low 2 - - 2
Code Quality 5 - - 5
Informational 2 - - -

Superstate 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 the ustb repository:

Source Code SHA256
src/SuperstateToken.sol

5e7cdd784f4ae8d6fd635e165a52401c673ce5ede8635cba6cabc18f5307233a

src/allowlist/AllowList.sol

99cbfe02ee1f655dd0766426add1c954280696fbb6933e7b9d52a148c7f8b612

Additionally, the following contracts within onchain-redemptions repository were also reviewed:

Source Code SHA256
src/Redemption.sol

e16842881cf4c105b253151d2e0001dda395ba22cc647e8371d0c897fe778faa

src/RedemptionIdle.sol

6b65b42559c1766e3339df3e575f37ecc8dd5fbe8f34e673919c999c366a7bec

src/RedemptionYield.sol

03876f287d1793ad041bd1623282126c2c00e42c502a8dd195f504347e8ca4fc

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

H-1

Redemption fee is not charged

Topic
Spec
Status
Impact
High
Likelihood
High

A new feature in the Redemption contract introduces a redemption fee. The redemption fee is initially set through the initializer, can be updated using setRedemptionFee(), and can be calculated using calculateFee(). In addition, in the calculateUstbIn(), the redemption fee is considered when calculating how much USTB token the user may need to provide to obtain the desired USDC amount.

However, the redemption process flow, which includes RedemptionIdle.redeem() and RedemptionYield.redeem() together with underlying Redemption.calculateUsdcOut(), does not consider the redemption fee at all. As a result, a redemption fee would not be charged in contradiction to the system specification and contract configuration.

Remediations to Consider

  • Apply redemption fee in Redemption.calculateUsdcOut().
H-2

Token access constraints not respected in subscribe()

Topic
Spec
Status
Impact
High
Likelihood
High

In the SuperstateToken contract, a new feature allows an end user to mint superstate tokens using subscribe(). This function takes in stablecoins and mints SuperstateToken in the proper amount for the msg.sender, depending on the current Net Asset Value per Share. SuperstateToken, part of a regulation-compliant system, has extra token access constraints that allow only preapproved addresses to obtain tokens. These constraints are enforced in the internal mint wrapper function _mintLogic(). This function is used by mint() and bulkMint() owner-accessible functions.

function _mintLogic(address dst, uint256 amount) internal {
    if (!hasSufficientPermissions(dst)) revert InsufficientPermissions();

    _mint(dst, amount);
    emit Mint(msg.sender, dst, amount);
}

However, subscribe(), which is publicly accessible, does not call _mintLogic() but calls directly the underlying _mint() function, which skips permission checking. As a result, non-approved addresses may obtain SuperstateToken in contradiction with system specifications.

Remediations to Consider

  • Replace in subscribe() call to _mint() with a call to the _mintLogic().
M-1

Redemption fee amount incorrectly calculated

Topic
Spec
Status
Impact
High
Likelihood
Medium

In the Redemption contract, calculateUstbIn() function calculates how much superstate token should be provided to obtain the desired amount of USDC tokens in return. As part of the calculation, the redemption fee is also considered.

function calculateUstbIn(uint256 usdcOutAmount)
  public
  view
  returns (uint256 ustbInAmount, uint256 usdPerUstbChainlinkRaw)
  {
      if (usdcOutAmount == 0) revert BadArgs();

      **uint256 usdcOutAmountWithFee = usdcOutAmount + calculateFee(usdcOutAmount);**

      (bool isBadData,, uint256 usdPerUstbChainlinkRaw_) = _getChainlinkPrice();
      if (isBadData) revert BadChainlinkData();

      usdPerUstbChainlinkRaw = usdPerUstbChainlinkRaw_;

      ustbInAmount = (usdcOutAmountWithFee * CHAINLINK_FEED_PRECISION * SUPERSTATE_TOKEN_PRECISION)
          / (usdPerUstbChainlinkRaw * USDC_PRECISION);
  }

However, the redemption fee is incorrectly calculated. Currently, a fee is applied to the net amount of USDC instead of the gross amount of USDC. As a result, the final result underestimates the redemption fee amount. In addition, when invoking redemption with the result of calculateUstbIn(), the user will receive less than the desired usdcOutAmount.

Remediations to Consider

  • Update calculateUstbIn() implementation to apply fee on gross USDC amount.
L-1

Rounding issues may result in bad UX

Topic
Spec
Status
Impact
Medium
Likelihood
Low

In the Redemption contract, the calculateUstbIn() function calculates the necessary superstate token amount to be provided to receive the desired amount of USDC.

ustbInAmount = (usdcOutAmountWithFee * CHAINLINK_FEED_PRECISION * SUPERSTATE_TOKEN_PRECISION)
    / (usdPerUstbChainlinkRaw * USDC_PRECISION);

However, due to integer division and rounding ustbInAmount may underestimate the necessary superstate token amount to be provided to retrieve the desired amount of USDC. As a result, users may receive less than they initially asked for. On the user side, this would require a more complex integration implementation to handle this edge case.

Remediations to Consider

  • Implement rounding up in calculateUstbIn() so that the user receives the desired amount of USDC when redeeming.
L-2

Inadequate check for validating proper subscription configuration

Topic
Spec
Status
Impact
Medium
Likelihood
Low

In the SuperstateToken contract, the _requireOnchainSubscriptionsEnabled() function verifies necessary dependencies for enabling subscriptions. This includes verifying that the superstateOracle, which provides real-time NAVS price, is configured and set.

function _requireOnchainSubscriptionsEnabled() internal view {
    if (superstateOracle == address(0)) revert OnchainSubscriptionsDisabled();
}

However, this check skips to validate that maximumOracleDelay is also set and has a value greater than 0. In cases when maximumOracleDelay is not configured or has a default value of 0, subscriptions will also not work, as Oracle data will always be treated as stale data.

Remediations to Consider

  • Include in _requireOnchainSubscriptionsEnabled() check that maximumOracleDelay is appropriately configured.
Q-1

Unnecessary backward compatibility for Allowlist implementation

Topic
Unnecessary code
Status
Quality Impact
High

The latest, version 2 of Allowlist system component, diverges significantly in the implementation from the previous version. Storage layout, permission handling, and function interface differ. In addition, v2 of Allowlist is upgradeable, while version 1 wasn’t.

However, version 2 of Allowlist tries to maintain backward interface compatibility even though it isn’t an end user-facing system component but rather a wrapped component used by the Superstate token contract and by the internal management system.

Due to the above, maintaining backward compatibility seems unnecessary. Consider removing unnecessary Allowlist version 1 features and code for the benefit of a more clean version 2 Allowlist implementation.

Q-2

Set the redemptionFee variable type to uint256

Topic
Spec
Status
Quality Impact
Low

In the Redemption contract, the redemptionFee is defined as uint96. If, in the next upgrade, a new variable is smaller than uint256 and can fit in the same slot, it may not require an update to the storage gap. However, if it is bigger, it will require a new slot and storage gap update.

To reduce potential storage layout slot misalignment in future upgrades, consider using uint256 for redemptionFee instead of uint96.

Q-3

Extra Ownable features present in Redemption and Allowlist contracts

Topic
Spec
Status
Quality Impact
Low

The Redemption and Allowlist contracts inherit from OwnableUpgredeable. As a result, they contain the unnecessary functionality of renounceOwnership().

Consider overriding it and reverting whenever it is called so it is clear that it is not supported.

Q-4

Improve natspec documentation

Topic
Documentation
Status
Quality Impact
Low

Consider updating natspec documentation in the following cases:

  • In the SuperstateToken, the natspec for the setOracle() function refers to the old, now obsolete, updateOracle() function. Consider updating the natspec to keep it up to date.
  • In the Redemption contract, withdraw() is an abstract function, and withdrawToSweepDestination() relies on it. Since these functions are critical, add natspec documentation that concrete implementation of withdraw() in child functions should implement proper access control.
Q-5

Add validation for sweepDestination address

Topic
Input validation
Status
Quality Impact
Low

In the Redemption contract, the sweepDestination variable represents the address where the contract owner-controlled withdrawToSweepDestination() will send the requested amount of USDC tokens. This variable is configured in initialize() and can be updated using setSweepDestination().

However, the underlying implementation in _setSweepDestination() does not have a check that the provided address value for sweepDestination is valid and not 0 address.

To prevent asset loss, consider adding a zero address check for sweepDestination, similar to what is done in SuperstateToken.

I-1

Centralization risk

Topic
Trust model
Impact
Informational

The Superstate system is a centralized system with various regulatory compliance requirements. A multi-sig account that is an authorized party in the system has extensive privileges that control all assets through configuration and privileged operations.

I-2

Stablecoins used with a hardcoded peg to USD

Topic
Trust model
Impact
Informational

Various contracts within the Superstate system, such as Superstate.sol and Management.sol, are implemented with hardcoded peg between USD and configured stablecoins. This is an explicit assumption within the system and an acceptable risk to the system owners. Even though potential malicious end users may attempt to exploit de-peg events, all system users are KYC, and the likelihood of this happening is minimal.

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