Considering the comment atFed.sol#L73:
@dev Useful for immediately removing chair powers in case of a wallet compromise.
the governance should not be allowed to be the chair in Fed.changeChair().
[L-03] Don't uncheck balance updates
While unlikely, it is not inconceivable that an account's balance will overflow (especially considering the comment @dev This function will revert if a user has a balance of more than 2^255-1 DBR and the fact that the accounting of the effective balance is done by a difference such that balances[user] can be arbitrarily high while the effective balance still is zero). DBR.transfer(), DBR.transferFrom() and DBR._mint() may then annihilate an account's balance by the unchecked balances[to] += amount;:
When deploying Market.sol a failure to properly set gov, escrowImplementation, dbr and collateral will result in a broken contract, necessitating redeployment. Consider adding address(0)-checks for these in the constructor
[L-05] Functions return bool but cannot return false
Some functions return bool but only return true unless reverting. This means the return value is meaningless. This may cause problems if they are elsewhere expected to return false rather than revert.
[NC-01] Inconsistent range check for replenishmentIncentiveBps
In Market.sol replenishmentIncentiveBps is required to be >0 in its setter function but not in the constructor. Consider making the checks the same for both. (It seems not strictly necessary to require that it be >0 as this simply means there will be no incentive, and the difference in incentive between 0 and 1 is minimal, and can always be easily changed, so this requirement might be dispensed with.)
[NC-02] Refactor oracle price
Oracle.sol returns normalizedPrice from Oracle.viewPrice() and Oracle.getPrice() in 36 - tokenDecimals decimals (see Oracle.sol#L82-L88 for the calculation).
Since it returns the price in DOLA, it seems unnecessarily confusing to not return this in DOLA.decimals() (18).
It seems better to compartmentalize the decimals such that these functions simply return in DOLA decimals, and wherever the value is used it's converted into the appropriate decimals there.
This value is currently retrieved in Market.sol by getCollateralValue(), getCollateralValueInternal(), getWithdrawalLimitInternal(), getWithdrawalLimit() and liquidate(). In all of these cases is the returned normalizedPrice divided by 18 decimals (1 ether) (which means the 36 could just have been 18 from the beginning) and gives the value of the (collateral) token in 18 decimals. This also means there is a hardcoded dependency on DOLA's having 18 decimals.
Consider refactoring by changing Oracle.sol#L82-L88:
which returns normalizedPrice in 18 decimals.
And changing every occurrence of
<collateral token> * oracle.[view|get](address(collateral), collateralFactorBps) / 1 ether
( + 36 - - 18 = 18 decimals)
into
<collateral token> * oracle.refactored[view|get]Price(address(collateral), collateralFactorBps) / 10**(<collateral token decimals>) {[*|/] 10**<optional to debt token decimals if other than 18>}
( + 18 - = 18 decimals).
[NC-03] Refactor price calculation in Oracle.sol
Oracle.viewPrice() really just returns the minimum of dampenedPrice and normalizedPrice, except if twoDayLow == 0 in which case it returns normalizedPrice. Consider the below steps to refactor to make this clearer. The same is applicable to getPrice() at L134-L140.
[L-01] Two-step transfer of operator and governance
The operator is critical for the functioning of BorrowController.sol. Currently it is transferred in a single transaction and no check is performed on the address. Consider implementing a two-step process just like in DBR.sol. And similarly for governance in Fed.sol. And similarly for governance in Market.sol.
[L-02] Don't let chair == gov
Considering the comment atFed.sol#L73:
@dev Useful for immediately removing chair powers in case of a wallet compromise.
the governance should not be allowed to be the chair inFed.changeChair()
.[L-03] Don't uncheck balance updates
While unlikely, it is not inconceivable that an account's balance will overflow (especially considering the comment
@dev This function will revert if a user has a balance of more than 2^255-1 DBR
and the fact that the accounting of the effective balance is done by a difference such thatbalances[user]
can be arbitrarily high while the effective balance still is zero).DBR.transfer()
,DBR.transferFrom()
andDBR._mint()
may then annihilate an account's balance by the uncheckedbalances[to] += amount;
:Consider removing the
unchecked
from DBR.sol#L173-L175, DBR.sol#L197-L199 and DBR.sol#L361-L363, and perhaps also, for good measure, the unchecked for total supply reduction inDBR._burn()
at DBR.sol#L375-L377.[L-04] Missing constructor address(0)-checks
When deploying Market.sol a failure to properly set
gov
,escrowImplementation
,dbr
andcollateral
will result in a broken contract, necessitating redeployment. Consider adding address(0)-checks for these in the constructor[L-05] Functions return bool but cannot return false
Some functions return bool but only return true unless reverting. This means the return value is meaningless. This may cause problems if they are elsewhere expected to return
false
rather than revert.Instances: https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/DBR.sol#L158-L162 https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/DBR.sol#L170-L178 https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/DBR.sol#L188-L202
[NC-01] Inconsistent range check for
replenishmentIncentiveBps
In Market.sol
replenishmentIncentiveBps
is required to be >0 in its setter function but not in the constructor. Consider making the checks the same for both. (It seems not strictly necessary to require that it be >0 as this simply means there will be no incentive, and the difference in incentive between 0 and 1 is minimal, and can always be easily changed, so this requirement might be dispensed with.)[NC-02] Refactor oracle price
Oracle.sol returns
normalizedPrice
fromOracle.viewPrice()
andOracle.getPrice()
in36 - tokenDecimals
decimals (see Oracle.sol#L82-L88 for the calculation). Since it returns the price in DOLA, it seems unnecessarily confusing to not return this inDOLA.decimals()
(18). It seems better to compartmentalize the decimals such that these functions simply return in DOLA decimals, and wherever the value is used it's converted into the appropriate decimals there. This value is currently retrieved in Market.sol bygetCollateralValue()
,getCollateralValueInternal()
,getWithdrawalLimitInternal()
,getWithdrawalLimit()
andliquidate()
. In all of these cases is the returnednormalizedPrice
divided by 18 decimals (1 ether
) (which means the36
could just have been18
from the beginning) and gives the value of the (collateral) token in 18 decimals. This also means there is a hardcoded dependency on DOLA's having 18 decimals. Consider refactoring by changing Oracle.sol#L82-L88:into
which returns + 36 - - 18 = 18 decimals)
into
+ 18 - = 18 decimals).
normalizedPrice
in 18 decimals. And changing every occurrence of<collateral token> * oracle.[view|get](address(collateral), collateralFactorBps) / 1 ether
(<collateral token> * oracle.refactored[view|get]Price(address(collateral), collateralFactorBps) / 10**(<collateral token decimals>) {[*|/] 10**<optional to debt token decimals if other than 18>}
([NC-03] Refactor price calculation in Oracle.sol
Oracle.viewPrice()
really just returns the minimum ofdampenedPrice
andnormalizedPrice
, except iftwoDayLow == 0
in which case it returns normalizedPrice. Consider the below steps to refactor to make this clearer. The same is applicable togetPrice()
at L134-L140.Inline
newBorrowingPower
and move definition ofdampenedPrice
out of the if-clause:normalizedPrice * collateralFactorBps / 10000 > twoDayLow
<=>normalizedPrice > twoDayLow * 10000 / collateralFactorBps
<=>normalizedPrice > dampenedPrice
, so we getwhich is the same as
[NC-04]
Fed.getProfit()
may return intFed.getProfit()
may as well return an int to indicate loss. That profit > 0 is already checked inFed.takeProfit()
at [L133] (https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Fed.sol#L133).[NC-05] Typos
addres -> address oprator -> operator replen -> replenishment price allowe -> allowed th -> the deb -> debt replenishment price -> replenishment cost dola -> DOLA mus -> must liqudation -> liquidation liquidation factor -> liquidation incentive od -> of liquidateable -> liquidatable Th -> The user user -> user chainlink -> Chainlink chainlink -> Chainlink