OpenZeppelin, in its role as Security Partner to the Compound DAO, audited the latest version of the ERC-4626-compliant wrapper contract for the Comet token.
Summary
Timeline: 2024-09-05 - 2024-09-10
Languages: Solidity
Total Issues: 10 (10 resolved)
High Severity Issues: 1 (1 resolved)
Medium Severity Issues: 1 (1 resolved)
Low Severity Issues: 6 (6 resolved)
Notes & Additional Information: 2 (2 resolved)
Scope
OpenZeppelin reviewed pull request #28 of the compound-finance/comet-wrapper repository at commit 26fc656. Under review were the changes introduced since the last audit performed at commit 1c08f3f.
In scope were the following files:
contracts
βββ src
β βββ CometHelpers.sol
β βββ CometWrapper.sol
β βββ vendor
β βββ CometConfiguration.sol
β βββ CometCore.sol
β βββ CometExtInterface.sol
β βββ CometInterface.sol
β βββ CometMainInterface.sol
β βββ CometMath.sol
β βββ CometStorage.sol
β βββ ICometConfigurator.sol
β βββ ICometRewards.sol
System Overview
The CometWrapper
contract is an ERC-4626-compliant wrapper around the Comet market, allowing users to interact with a stable balance of shares tied to their principal in the Comet market. This wrapper provides a non-rebasing alternative, addressing potential integration challenges with the wider DeFi ecosystem due to the rebasing nature of Comet tokens.
This audit primarily involved fixes from previous findings and the removal of compliance with EIP-7246. Changes included updating Comet interfaces, downgrading the Solidity version, and refining the CometWrapper
functionalities. The removal of EIP-7246 simplifies the interface by eliminating the ability to encumber shares, leaving the contract compliant only with ERC-20 and ERC-4626 standards. This reduces complexity while preserving key functionality, including reward accruals. However, Comet
βs permissions do not apply to the CometWrapper
. Thus, managers cannot use the wrapper contract on behalf of the users.
Security Model and Trust Assumptions
The CometWrapper
contract acts as a wrapper around the Comet and involves no privileged functionality. Users rely on the Compound DAO to manage both the market and the wrapper, with future upgrades and functionality determined through the usual DAO governance process.
High Severity
Anyone Can Withdraw
or Redeem
Shares, Returning Assets to Owner
The withdraw
and redeem
functions of the CometWrapper
contract can be used to burn the shares of an account and return the assets to its owner. However, these functions can be used by anyone, redeeming the shares and withdrawing the assets to the ownerβs account, without the ownerβs authorization.
Any malicious user could call the functions, passing in any owner
account that has shares, specifying the receiver
as the owner
. The spendAllowanceInternal
function would return successfully since owner == spender
, allowing anyone to unwrap another accountβs wrapped Comet tokens. A similar issue exists in the transferFrom
function, allowing anyone to execute transactions that transfer shares from an account to themselves.
Consider setting the spender
parameter of the spendAllowanceInternal
to msg.sender
instead of receiver
.
Update: Resolved in commit cfea9b0.
Medium Severity
Mismatch Between RewardConfig
Interface and Implementation
The RewardConfig
struct defined in the IRewardConfig
interface is outdated. As defined in CometRewards, the RewardConfig
struct now includes an additional uint256 multiplier
variable. The CometRewards
contracts deployed on Arbitrum, Optimism, Base, and Scroll are deployed with this additional variable. The absence of the multiplier
factor can lead to incorrect reward calculations in getRewardsOwedInteral
for any Comets that use a different multiplier
than 1e18
.
Consider updating the CometWrapper
implementation to incorporate a multiplier
in the RewardConfig
struct to ensure compatibility with underlying Comets that use multipliers other than 1e18
in reward calculations.
_Update: Resolved in commit cfea9b0 in line 9 of the ICometRewards
contract, and line 325 of the CometWrapper
.
Low Severity
Commented Code
In the CometWrapper
contract, line 615 contains commented-out code.
Consider removing the commented line of code to improve the readability and clarity of the codebase.
Update: Resolved in commit cfea9b0.
Incorrect Validation of CometRewards
In line 94 of the CometWrapper
contract, a call is made to the CometRewards
contract but its return value is never checked. This call will not revert if the recipient address is an EOA.
Consider checking that the reward token in the returned RewardConfig
is not the zero address to ensure that it is the CometRewards
contract.
Update: Resolved in commit cfea9b0 in line 92.
Incorrect EIP-2612 Implementation
EIP-2612 defines a standardized way to allow users to provide token spend allowances gaslessly. The Comet Wrapper contract has implemented EIP-2612 but it deviates from the standard in three ways:
- The
permit
function in the Comet Wrapper contract usesAUTHORIZATION_TYPEHASH
instead of thePERMIT_TYPEHASH
defined in EIP-2612. - The
permit
function uses different names for the parameters than the ones defined in the EIP. - The EIP states that a signature should be accepted as long as
block.timestamp <= deadline
but the implementation of this function in the wrapper rejects a signature whenblock.timestamp == deadline
.
Consider making changes to comply with EIP-2612. This will help increase the ease of integration of the wrapper token.
Update: Resolved in commits d55ff88 in line 34 and 8d208ed in line 566.
Duplication of Code
The spendAllowanceInternal
function essentially has the same functionality as the inherited _spendAllowance
function.
The only difference between the two functions is that spendAllowanceInternal
checks whether the owner
of the tokens is also the spender
. If that is the case, it returns without making any changes to the allowances. This extra check is not needed if msg.sender
is passed as the spender and the spendAllowance
function is called directly.
Consider using the spendAllowance
function directly to reduce code complexity and improve readability.
Update: Resolved in commit cfea9b0.
Deviation From EIP-4626
The CometWrapper
contract implements ERC-4626 functionality for Compoundβs Comet protocol. However, its implementation of the maxDeposit
, maxMint
, and maxRedeem
functions does not align with the EIP-4626 standardβs expectation that these functions should return the maximum amount that can be deposited, minted, or redeemed under existing conditions. Currently, these functions do not consider whether transfers are paused in the Comet contract. This would result in these functions displaying a non-zero value when transfers have been paused in Comet.
Consider adding a check ensuring that transfers are not paused in the above-mentioned functions to maintain compliance with EIP-4626.
Update: Resolved in commit cfea9b0 for all instances in lines 389-393, 395-399, and 401-405.
Incomplete Validation in the isValidSignature
Function
The isValidSignature
function does not ensure that the data
returned from the staticcall
is at least 32 bytes long.
Consider ensuring that data
is at least 32 bytes long.
Update: Resolved in commit d55ff88 in line 601.
Notes & Additional Information
Users Cannot Claim Rewards Without Accruing Them First
Unlike the claim and claimTo functions in CometRewards
, the CometWrapper
βs claimTo
function does not allow users to choose to accrue rewards before claiming them.
In CometRewards
, the claimTo
function calls getRewardOwedInternal
which, in turn, calls accrueRewards
. In accrueRewards
, rewards are accrued for the contract by calling comet.accrueAccount(address(this))
and for the user by updating the baseTrackingIndex
in the updateTrackingIndex
function. However, in CometWrapper
βs claimTo
function, users do not have the option to specify whether rewards should accrue before being claimed.
Consider aligning CometWrapper
with Comet
βs standard by adding a shouldAccrue
parameter to the claimTo
function. If shouldAccrue
is set to false
, the function should return the current reward based on the existing baseTrackingAccrued
of the user without updating it.
_Update: Resolved in commit d55ff88 in line 354.
Unused Constant
In the Wrapper contract, the ENCUMBER_TYPEHASH
constant is declared but never used.
In order to improve code clarity and maintainability, consider removing the unused constant.
Update: Resolved in commit cfea9b0.
Conclusion
The recent updates to the CometWrapper
contract simplify its interface and address key audit findings. While we identified one high-severity and one medium-severity issue, overall, the changes enhance security and usability, positioning the contract as a valuable tool for the Compound community.