Let’s say the collateral used was a non-18 decimals token, for example USDT (6 decimals). Looking at the getPrice() method from Oracle.sol we see the following code:
The “USDT/USD” price feed in Chainlink has 18 decimals. Following the math we will see that normalizedPrice will have 12 more decimals than USDT’s price feed, 30 in total.
Now if we go back again to, for example, this math
The debt always has 18 decimals, because the Dola token has 18 decimals. But if we multiply it by 1e18 or 1 ether now it has 36 decimals, even though the price has not been “normalized” to have 18 decimals and it has only 12. This will result in the result from repaidDebt * 1 ether / price having 24 decimals instead of 18, and doing * liquidationFeeBps / 10000; after it will result in a much bigger liqudationFee. This results in 2 scenarios:
If escrow has enough balance, it will pay the huge liquidation fee to the protocol, which is a loss for the protocol users.
If escrow does not have enough balance in this case it won’t pay any liquidaton fee, but since we do the same math for liquidatorReward, there we have the same case and if escrow does not have enough balance it will result in a DoS, which means all debts won’t be liquidateable.
The same thing applies for the other places that we have the debt * 1 ether math.
Impact
The impact of this is a loss of value for users or a DoS on core protocol functionality. It can only happen if a non-18 decimal token is used, but this can easily be the case if USDT is used as collateral, hence the High severity.
Recommendation
Enforce only 18 decimals tokens to be used in a Market or just properly get the price to always be with 18 decimals in Oracle.sol's getPrice() & viewPrice()
Lines of code
https://github.com/code-423n4/2022-10-inverse/blob/cc281e5800d5860c816138980f08b84225e430fe/src/Market.sol#L360 https://github.com/code-423n4/2022-10-inverse/blob/cc281e5800d5860c816138980f08b84225e430fe/src/Market.sol#L377 https://github.com/code-423n4/2022-10-inverse/blob/cc281e5800d5860c816138980f08b84225e430fe/src/Market.sol#L597 https://github.com/code-423n4/2022-10-inverse/blob/cc281e5800d5860c816138980f08b84225e430fe/src/Market.sol#L606
Vulnerability details
Proof of Concept
In multiple places in the code, when doing calculations with both
debt
andprice
(of collateral) there is a multiplication by 1e18 -* 1 ether
.We have the following calculations:
Let’s say the collateral used was a non-18 decimals token, for example
USDT
(6 decimals). Looking at thegetPrice()
method fromOracle.sol
we see the following code:The “USDT/USD” price feed in Chainlink has 18 decimals. Following the math we will see that
normalizedPrice
will have 12 more decimals than USDT’s price feed, 30 in total.Now if we go back again to, for example, this math
The debt always has 18 decimals, because the
Dola
token has 18 decimals. But if we multiply it by1e18
or1 ether
now it has 36 decimals, even though theprice
has not been “normalized” to have 18 decimals and it has only 12. This will result in the result fromrepaidDebt * 1 ether / price
having 24 decimals instead of 18, and doing* liquidationFeeBps / 10000;
after it will result in a much biggerliqudationFee
. This results in 2 scenarios:escrow
has enough balance, it will pay the huge liquidation fee to the protocol, which is a loss for the protocol users.escrow
does not have enough balance in this case it won’t pay any liquidaton fee, but since we do the same math forliquidatorReward
, there we have the same case and ifescrow
does not have enough balance it will result in a DoS, which means all debts won’t be liquidateable.The same thing applies for the other places that we have the
debt * 1 ether
math.Impact
The impact of this is a loss of value for users or a DoS on core protocol functionality. It can only happen if a non-18 decimal token is used, but this can easily be the case if USDT is used as collateral, hence the High severity.
Recommendation
Enforce only 18 decimals tokens to be used in a
Market
or just properly get the price to always be with 18 decimals inOracle.sol
'sgetPrice()
&viewPrice()