Closed c4-bot-2 closed 6 months ago
raymondfam marked the issue as insufficient quality report
raymondfam marked the issue as duplicate of #98
See #98.
hansfriese marked the issue as unsatisfactory: Insufficient proof
Hi @hansfriese,
Appeal
I don't understand why the judge marked this issue as insufficient proof
.
Especially when considering these valid issues in the past contests:
Could you have a second look at the issue details thoroughly again?
It may be subjective to decide whether this issue is validated or not. However, this issue affected Venus Protocol and Blizz Finance in the past.
Here are some of the valid ref issues in the past contests: ref-1 (also reported by the lookout himself), ref-2, ref-3, ref-4, and ref-5.
The following lists some assets the protocol supports (see docs) and their pre-defined minAnswer
/maxAnswer
(represented by 8 decimals precision).
XAU/USD
price aggregator has the minAnswer
== 10000000000 and maxAnswer
== 1000000000000.EUR/USD
price aggregator has the minAnswer
== 10000000 and maxAnswer
== 10000000000.JPY/USD
price aggregator has the minAnswer
== 100000 and maxAnswer
== 10000000.If this issue is invalidated, but the incident happens in the future, who will be responsible for the determination?
Lines of code
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L33-L46 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L50-L65 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L43 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L37 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L27 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L54 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L44 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L125-L126 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L60
Vulnerability details
Impact
The
LibOracle::getOraclePrice()
does not detect or handle the Chainlink aggregators'minAnswer
andmaxAnswer
. For instance, in case of a significant price drop, the asset price reported by the Chainlink price feed will continue to be at the pre-determinedminAnswer
instead of the actual price (below theminAnswer
). For more, refer to the case of Venus Protocol and Blizz Finance in the crash of LUNA.In other words, in the flash crash event, Chainlink's price feeds can report incorrect
basePrice
and/orprice
variables. These incorrectly reported price variables lead to miscalculating thebasePriceInEth
(base price in ETH) orpriceInEth
(non-base price in ETH).Whereas the calculation of the
basePriceInEth
has a price deviation detection mechanism to partially mitigate the issue, the calculation of thepriceInEth
does not validate or handle the issue.Subsequently, the
getOraclePrice()
will return the miscalculatedbasePriceInEth
and/orpriceInEth
(especially this one) to the protocol's core functions to consume, harming the protocol's and users' funds.Please refer to the
Proof of Concept
section for more details.Proof of Concept
This PoC section can be categorized into three sub-sections.
minAnswer
andmaxAnswer
1. Introduction
The
getOraclePrice()
calculates the base price in ETH (ETH/USD
asset oracle) or non-base price in ETH (non-ETH/USD
asset oracle) for the protocol's core functions to consume. The function can be divided into four code paths.try
casecatch
casetry
casecatch
caseIn the first code path, the base price in ETH (
basePriceInEth
) is calculated from thebasePrice
fed by Chainlink'sETH/USD
price feed. The function validates and handles the flash crash event using the price deviation detection mechanism. In the second code path, the function will derive thebasePriceInEth
from Uniswap's TWAP instead (in case Chainlink's price feed reverts a query). Both code paths can partially mitigate the flash crash event. Thus, I will leave out discussing these two code paths for brevity's sake.This report will discuss the third and fourth code paths of the
getOraclePrice()
, which calculate the non-base price in ETH (non-ETH/USD
asset oracle). I discovered neither code path validates or handles the flash crash event. As a result, thegetOraclePrice()
will return a miscalculated non-base price (i.e., thepriceInEth
variable).2. Vulnerability Details
The third code path of the
getOraclePrice()
calculates thepriceInEth
usinguint256(price).div(uint256(basePrice))
(see@1.1
below) . Where theprice
(see@1.2
) is fed by Chainlink'snon-ETH/USD
price feed (e.g.,XAU/USD
), and thebasePrice
(see@1.3
) is from Chainlink'sETH/USD
price feed.Meanwhile, the fourth code path of the
getOraclePrice()
calculates thepriceInEth
usinguint256(price * C.BASE_ORACLE_DECIMALS).mul(twapInv)
(see@2.1
). Where theprice
(see@2.2
) is reported by Chainlink'snon-ETH/USD
price feed, and thetwapInv
(see@2.3
) is returned from Uniswap's TWAP (WETH/USDC
pool).As mentioned earlier, Chainlink's price aggregators have pre-determined
minAnswer
andmaxAnswer
price ranges they will return. In a flash crash event, an asset price can fall significantly below the pre-determinedminAnswer
. However, the aggregator will continue to report itsminAnswer
. For more, refer to the case of Venus Protocol and Blizz Finance in the crash of LUNA.For example, the following lists some assets the protocol supports (see docs) and their pre-defined
minAnswer
/maxAnswer
(represented by 8 decimals precision).XAU/USD
price aggregator has theminAnswer
== 10000000000 andmaxAnswer
== 1000000000000.EUR/USD
price aggregator has theminAnswer
== 10000000 andmaxAnswer
== 10000000000.JPY/USD
price aggregator has theminAnswer
== 100000 andmaxAnswer
== 10000000.I noticed that the
getOraclePrice()
does not detect such a flash crash event. If the market crashes, theprice
and/orbasePrice
reported by Chainlink can be higher than the actual asset price (below theminAnswer
).As a result, the incorrectly reported
price
orbasePrice
leads to miscalculating thepriceInEth
.3. Lack of Detecting Chainlink Aggregators'
minAnswer
andmaxAnswer
This sub-section shows that the
getOraclePrice()
does not detect or handle the Chainlink aggregators'minAnswer
andmaxAnswer
.After calculating the
priceInEth
, the third code path of thegetOraclePrice()
validates the obtainedprice
andbasePrice
by invoking theoracleCircuitBreaker()
(see@3.1
below).As you can see, the
oracleCircuitBreaker()
does not verify both price variables against their corresponding aggregators'minAnswer
ormaxAnswer
(see@3.2
). Therefore, the function cannot be aware of the flash crash events.Also, be the same in the case of the fourth code path of the
getOraclePrice()
. The function does not verify the obtainedprice
against its corresponding aggregator'sminAnswer
ormaxAnswer
(see@4
below). Hence, the function cannot detect whether the flash crash event occurs.Tools Used
Manual Review
Recommended Mitigation Steps
Implement a mechanism to detect the Chainlink aggregators'
minAnswer
andmaxAnswer
for bothbaseOracle
(ETH/USD
) andoracle
(non-ETH/USD
) price feeds.Suppose the reported
basePrice
orprice
reaches theminAnswer
ormaxAnswer
(e.g., the flash crash event occurs). In that case, the mechanism can revert a transaction or even fall back to consume a more accurate price from Uniswap's TWAP or other oracle services instead (if necessary).Assessed type
Oracle