Quantstamp reviewed USDE (ERD) (Ethereum Reserve Dollar (ERD)) on 30 Oct 2023: 33 findings, 0 critical; 9 marked fixed, 21 acknowledged, 3 open.
Report hosted on certificate.quantstamp.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 2 | 3 | 2 |
| Medium | 0 | 9 | 2 |
| Low | 1 | 9 | 5 |
The function `setGasCompensation()` allows the owner of `CollateralManager` to set the value `USDE_GAS_COMPENSATION`, which is used to determine how much a borrower owes for compensating liquidators for calling liquidation functions. The value of `USDE_GAS_COMPENSATION` is used to determine, upon opening a trove, how much of the issued debt (in the form of USDE) is to be sent to the gas pool to compensate the liquidation of the trove. Suppose the USDE gas compensation has been increased by calling the function. Now, via `TroveManager.getTroveDebt()` which calculates a trove's debt by summing up a certain amount with the _new_ USDE gas compensation amount, the ICR of **all existing troves** is increased. However, the TCR of the protocol, which calculates the gas compensation portion by relying on the balance of the gas pool, will remain unchanged. The result will be a potentially massive discrepancy between the ICRs and the TCR, whereas in general one should be able to draw a direct formal relationship between the two. Potential consequences of this may include troves being put up for liquidation sooner or later than expected, recovery mode being triggered sooner or later than expected, and the TCR or ICRs of troves being materially misrepresented to users and devs.
ETokens are transferable tokens that represent a borrower's collateral. For each different collateral type, there is a different type of EToken. Burning EToken is necessary to release the corresponding collateral. Moreover, when redeeming USDE and paying off a trove's debt, the trove's EToken also gets burned proportionately. ETokens are transferable. Therefore, they may be sent to accounts that do not have open troves. If this is the case, then the collateral is essentially held in an account wherein it cannot be claimed by people redeeming USDE, as doing so requires redeeming from open troves. As a result, sending ETokens to accounts without open troves will lock up collateral indefinitely, creating a situation where that collateral is effectively no longer backing USDE. While on its own this is not a dire issue, the problem is exacerbated by the fact that the TCR (Total Collateralization Ratio) is always calculated with respect to the total supply of the various ETokens. Every path to calculate TCR involves passing through `CollateralManager.getEntireCollValue(_price)`, where on lines 588-590, the amount of collateral that does not comprise pending liquidation rewards is calculated with respect to EToken supply. As a result, collateral that is no longer backing USDE will erroneously be included in the calculation of TCR, which will have systemic effects like affecting the interest rate and affecting the timing of when recovery mode is activated.
ETokens require that the trove address from with they are being sent does not drop in ICR below the MCR (enforced by `EToken._requireValidAdjustment()`). However, in `CollateralManager.validAdjustment()`, where this condition is determined, only the prices of ETH and the collateral type being transferred are being updated (on lines 617-618). This means that the ICR and TCR calculations that are made to determine whether the trove adjustment is valid may be relying on stale data. For example, suppose that an rETH EToken is being transferred. In this case, although the prices of ETH and rETH may be updated, the price of stETH will not be updated. As a result, if the price of stETH dropped significantly since the last price update, the EToken transfer may be allowed to proceed despite the actual ICR of the trove decreasing below the MCR as a result.
ERD applies a variable interest rate in the calculation of outstanding debt. Per the function `getMaxBorrowRate()`, this amount should be capped at `baseBorrowRate + rateSlope1 + rateSlope2`. However, the function `calculateInterestRates()`, in the case where `vars.utilizationRate < optimalUtilizationRate`, returns `baseBorrowRate + rateSlope1 + rateSlope2 * remaindUtilizationRateRatio` where the value `remaindUtilizationRateRatio` may be greater than 1. This means that the returned value can be greater than the value returned by `getMaxBorrowRate()`.
`EToken.transferFrom()` converts the `_amount` to `share` by calling `share = getAmount(_amount)`. Although `getShare()` and `getAmount()` currently implement the same logic, this may result in transferring an unexpected quantity of tokens if the logic of `getAmount()` ever differs from `getShare()` in a future revision.
There is no restriction on how low the `MIN_NET_DEBT` can be set by calling `CollateralManager.setMinDebt()`. If the `MIN_NET_DEBT` is set lower than `USDE_GAS_COMPENSATION`, a trove could be adjusted to have inadequate gas compensation.
The implementation contracts behind a proxy can be initialized by any address. This is not a security problem in the sense that it impacts the system directly, as the attacker will not be able to cause any contract to self-destruct or modify any values in the proxy contracts. However, taking ownership of implementation contracts can open other attack vectors, like social engineering or phishing attacks.
The affected contracts inherit from `ReentrancyGuardUpgradeable`. However, at no point is an initialization called for this parent contract from within the affected contracts.
Below are a number of instances where we identified missing input validation. * `BorrowerOperations._adjustTrove()`: Validate that the entries in `_params.amountIn[]` are non-zero. * `CollateralManager.addCollateral()`: Validate that the input addresses are non-zero. * `CollateralManager.setOracle()`: Validate that the input addresses are non-zero. * `CollateralManager.setEToken()`: Validate that the input addresses are non-zero. * `CollateralManager.setMinDebt()`: Validate that `_minDebt` is greater than or equal to `USDE_GAS_COMPENSATION`. * `CollSurplusPool.setAddresses()`: Validate that `_collateralManagerAddress` is a non-zero address. * `EToken.setAddresses()`: Validate that the input addresses are non-zero. * `TroveInterestRateStrategy.setAddresses()`: Validate that the input addresses are non-zero. * `SortedTroves.getNext()`: Require that `data.nodes[_id].exists == true`. Otherwise `_id` may be misinterpreted to be the tail. * `SortedTroves.getPrev()`: Require that `data.nodes[_id].exists == true`. Otherwise `_id` may be misinterpreted to be the head. * `USDEToken.sendToPool()` and `USDEToken.returnFromPool()`: require that `_poolAddress` is a valid pool address.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (57)
236 projects reviewed · 332 reports · latest Oct 2026
See Quantstamp’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us