Skip to content

Compound Comet Wrapper Audit

August 1, 2023

This security assessment was prepared by OpenZeppelin.

Table of Contents

Summary

Type
DeFi
Timeline
From 2023-06-05
To 2023-06-09
Languages
Solidity
Total Issues
8 (7 resolved, 1 partially resolved)
Critical Severity Issues
0 (0 resolved)
High Severity Issues
3 (3 resolved)
Medium Severity Issues
4 (3 resolved, 1 partially resolved)
Low Severity Issues
1 (1 resolved)
Notes & Additional Information
0 (0 resolved)

Scope

We were asked to audit the comet_wrapper repository at commit 13f2081. In scope were the following contracts:

 src/
├─ CometHelpers.sol
└─ CometWrapper.sol

System Overview

The Comet Wrapper is an ERC-4626 vault which wraps around a Comet token like cUSDCv3. It is designed to provide a non-rebasing token, due to the rebasing nature of the Comet token making it inconvenient to use, while still being able to track the interest accrued. The design anchors the wrapper's "share" balance to the Comet token's "principal" since the principal is not affected by the rebasing nature of the Comet token. This design allows the Comet Wrapper to rely on Comet to calculate the conversion between the share and the asset amount.

Another major function of the Comet Wrapper is to track individual users' Comet reward balance and allow them to withdraw rewards directly through it. This is achieved by tracking each user's reward accrued through trackingSupplyIndex from Comet as well as the amount claimed with a rewardClaimed mapping.

Trust Assumptions

The codebase uses dependencies from the Solmate library, which we assume works as documented. We also assume Comet works as documented since the wrapper relies on it.

 

High Severity

Reward Tracking in Wrapper Could Be Incorrect

The wrapper allows depositors to claim Comet rewards directly, by keeping an internal track of Comet rewards for each depositor through userBasic[]. The internal tracking accrues users' rewards with the latest trackingSupplyIndex from Comet whenever a user's wrapper balance changes, which happens during deposit, mint, withdraw and redeem.

However, the wrapper does not accrue users' rewards during a transfer, which means the sender could lose the unaccrued rewards while the receiver could gain extra rewards depending on how long it has been since the last time rewards were accrued.

Consider accruing rewards for both the sender and receiver in transferInternal.

Update: Resolved in pull request #2 at commit 1f2899d.

redeem May Lead to Insolvency

Due to how Comet computes transfers, it is possible that the intended transfer in redeem reduces the Comet "principal" of the wrapper by more than shares.

During a transfer within Comet, the sender's "principal" balance is computed by converting it to "underlying" value, subtracting the transfer amount, and converting back to "principal". During these conversions, the result is rounded down. This may result in a "lost unit" of principal, where the sender loses one more unit of principal than the receiver gets.

In the case of redeem, this means that the Comet Wrapper's principal balance decreases by shares + 1, while only shares is burned. This can lead to the insolvency of the Comet Wrapper.

Consider computing assets in redeem using some method other than convertToAssets. Consider implementing the various calculations which happen in Comet to ensure that the change in principal of the Comet Wrapper is always equal to shares for whatever assets value is computed. Consider that a loss of 1 unit is possible each time redeem is called in its current state.

Update: Resolved in pull request #6 at commit 0bea271.

redeem Accrues Rewards Incorrectly

The redeem function burns shares before accruing rewards, which results in an incorrect reward calculation for the user.

Rewards owed on the amount being redeemed will not be accounted for, as the reward accrual will use the balanceOf the user after their shares have been burned.

Consider moving the accrual actions to the beginning of the redeem function to properly account for the accrual of rewards.

Update: Resolved in pull request #4 at commit 0bea271.

Medium Severity

Principal Not Checked in redeem

Within mint, deposit, and withdraw, there are checks enforcing that the user's balance change equals the change in Comet's principal of the wrapper.

However, this invariant is not enforced in the redeem function.

Consider enforcing this rule in the redeem function to ensure accurate accounting within the wrapper. See related issues "redeem May Lead to Insolvency" and "Repeated code".

Update: Resolved in pull request #6 at commit 0bea271.

mint Does Not Follow ERC-4626

The ERC-4626 specification states that the mint function "mints exactly shares Vault shares to receiver by depositing assets of underlying tokens."

Within the Comet Wrapper, this is not the case. The function re-computes shares and thus does not mint exactly the user-specified number as defined in the specification.

Consider a different computation of assets so that the principal balance of the wrapper will change by exactly shares as specified by the user or caller. Examine the balance-changing computations in Comet to assist. Also, see the related issue "redeem May Lead to Insolvency".

Update: Resolved in pull request #7 at commit 52fb0ef. Note that the fix is simply documenting the discrepancy between the behavior and the ERC-4626 definition.

Repeated Code

Within the CometWrapper contract, there are many instances of repeated code that should be removed.

  • The functions accrueRewards and updateTrackingIndex implement nearly identical logic, with the exception of the call to comet.accrueAccount within accrueRewards. All calls to updateTrackingIndex and their preceding accrueInternal calls can be replaced by a call to accrueRewards. updateTrackingIndex and accrueInternal could be removed.
  • The functions claimTo and getRewardOwed implement similar logic. The duplication between these functions can be removed by having claimTo make a call to getRewardOwed instead of implementing its logic a second time.
  • The pattern of checking the wrapper's principal before and after transfers to or from it (present in the deposit, mint, and withdraw functions) can be functionalized.

This duplicated code should be removed as it greatly increases the surface for error, as well as gas consumption. Slight differences in duplicated code may be overlooked by developers or reviewers resulting in errors or vulnerabilities. Duplicated code also makes the entire codebase harder to comprehend and document.

Update: Partially resolved in pull request #5 at commit 3dd46f2. The third bullet point has been acknowledged, but a change was decided against for code readability reasons.

Zero Transfer Is Not Allowed

The ERC-20 standard spec states that ERC-20 tokens MUST not revert on zero-value transfers. The ERC-4626 Standard states that the ERC-4626 vault MUST implement ERC-20. transfer and transferFrom may revert if an ERC-4626 vault is non-transferrable, but this is not the case with the Comet Wrapper.

Therefore, transfer and transferFrom calls should not revert on transfers of 0 in order to be ERC-4626 compliant.

Currently, transferInternal disallows transfers where amount == 0. Consider removing this check to be compliant with ERC-4626. Understand that integrations may compute a transfer amount of 0 and still expect the call to succeed. Alternatively, consider documenting the discrepancy versus the ERC-4626 specification for external developers to be aware.

Update: Resolved in pull request #3 at commit e43b148.

Low Severity

Missing Docstrings

CometWrapper.sol's function convertToAssets() is missing docstrings.

Consider thoroughly documenting all functions (and their parameters) that are part of any contract's public API. Functions implementing sensitive functionality, even if not public, should be clearly documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).

Update: Resolved in pull request #7 at commit e43b148.

 

Conclusions

Three high and many lower-severity issues were discovered. Overall, the codebase's design is compact and well thought out, but lacking in consistency and somewhat scattered. Some recommendations were made to improve the code's quality and efficiency.

We recommend increasing the test coverage, utilizing fuzzing and formal verification to enforce invariants such as balance guarantees for the wrapper, and checking that rewards accrual works after users perform various other actions with the wrapper. We also recommend writing extensive formal documentation, indicating the various guarantees of the wrapper and the effects that rounding within the Comet protocol may have on its functionality.

 

Monitoring Recommendations

After deployment, monitoring the Comet Wrapper system can assist in diagnosing bugs or exploits and stopping them as quickly as possible. It may also prove a useful source of data for analytics at a later time. Here, we recommend some monitoring strategies to ensure the healthy operation of the protocol after deployment.

  • Monitor for direct transfers of tokens to the contract (not using mint/deposit/redeem/withdraw). This may indicate a bug in the front end or misinformation in the documentation.
  • Monitor for Deposit and Withdraw events along with the principal and balance changes of the sender and recipient. Ensure the changes in the balances of the sender and recipient match, and ensure that the conversion between assets and shares is reasonable.
  • Monitor for reward claims and match them against an off-chain calculation of rewards. Ensure that reward claims are reasonable.
  • Monitor for approvals of the Comet Wrapper ERC-4626 token. Many approvals in a short time window may indicate an ongoing approval fishing attack.
  • Monitor for frequent calls to accrueRewards or claimTo as they may indicate an issue related to rewards accounting.
  • Monitor for frequent calls to claimTo with the same to address, as it may indicate a malicious frontend exploiting users for their rewards.
  • Monitor for Deposit and Withdraw events from the same address within a short timeframe. It may indicate a mathematical exploit or abuse of the system.