Closed code423n4 closed 1 year ago
0xean marked the issue as primary issue
0xean marked the issue as selected for report
This issue is only partially correct.
The intention of the oracle is not to return a 18 decimal price, but a price that will give a correct dollar price as if the underlying token had 18 decimals, since we do no decimal math in the market.
Let's take an example:
Their collateral value is calculated as follows by the market:
return collateralBalance * oracle.getPrice(address(collateral), collateralFactorBps) / 1 ether;
Leaving them with 1e8 * 1.6e32 / 1e18 = 1.6e22 collateral value, or what amounts to 16,000 DOLA which is correct.
The part about the oracle failing if the token decimals + feed decimals > 36 is correct, but considered a lower severity issue, is that is highly unorthodox. Severity considered medium for the correct part of the issue.
08xmt marked the issue as disagree with severity
08xmt marked the issue as sponsor confirmed
0xean marked the issue as not selected for report
I think M is reasonable here, it requires some pre-existing conditions that are atypical for the issue to occur.
0xean changed the severity to 2 (Med Risk)
0xean marked the issue as satisfactory
Simon-Busch marked the issue as duplicate of #533
Lines of code
https://github.com/code-423n4/2022-10-inverse/blob/main/src/Oracle.sol#L87 https://github.com/code-423n4/2022-10-inverse/blob/main/src/Oracle.sol#L121
Vulnerability details
Impact
The price returned by the Oracle is wrong in certain conditions, leading to prices multiplied or divided by 10^n. This situation makes the calculation of the collateral value, the withdrawal limit and the liquidation reward in
Market.sol
absolutely wrong, which could potentially be a loss of funds for the protocol. See that the price is supposed to be 18 decimals, and getting it in another number of decimals would be a disaster for the protocol. See for example of use of thegetPrice
function of the oracle in line 326 of Market.sol:how the multiplication is divided by 1 ether (10^18, or one with 18 decimals).
Proof of Concept
In both
getPrice
andviewPrice
functions inOracle.sol
, the error is located when trying to set the price to return as a 18 decimal value.Feed decimals is the number of decimals of the
Chainlink
feed. The price that returns the feed is withfeedDecimals
so the last line aboveuint normalizedPrice = price * (10 ** decimals);
just should add (18 -feedDecimals
) to the price, so thatnormalizedPrice
turns into a 18 decimal value. Instead of this, the functions calculate the decimals asuint8 decimals = 36 - feedDecimals - tokenDecimals;
which works fortokenDecimals
= 18, but won't work otherwise. So let's see the different possibilities here:tokenDecimals
= 18 the functions will work OK as long asfeedDecimals <= 18
. If it's greater than 18 the calls to both functions will revert because36 - 18 - feedDecimals < 0
.tokenDecimals != 18
the calculation won't work OK. Even worse, iftokenDecimals + feedDecimals > 36
both functions will revert.The following forge test (which I included in the Market.t.sol file) illustrates some wrong returns by the oracle supposing that the
feedDecimals
are always 18, for different values oftokenDecimals
:To test the cases for when the feed is different than 18 decimals I first changed file EthFeed.sol, precisely these two lines:
into
Then added this test to Market.t.sol:
So it is clear that the returned price has the wrong decimals in some of the cases, and so the calculations in the
Market
contract would lead to disastrous consequences.Tools Used
Forge tests and manual analysis
Recommended Mitigation Steps
If the oracle is supposed to return the price of the feed with 18 decimals, a simple solution would be changing this lines in both
viewPrice
andgetPrice
:with this:
This way, if price feed has less than 18 decimals, it would be right padded with zeroes up to 18, and if it has more than 18 decimals, the excess decimals would be taken out.