Closed c4-bot-7 closed 4 months ago
raymondfam marked the issue as insufficient quality report
raymondfam marked the issue as duplicate of #4
See #4.
hansfriese marked the issue as unsatisfactory: Out of scope
Hi @hansfriese,
Appeal
This issue is not a duplicate of #4.
As #4 refers to the missing checks for roundId
, price
, updateTime
, and answeredInRound
, which was flagged as a known issue. This issue refers to an incorrect implementation of the base oracle
(ETH/USD price
) in the oracleCircuitBreaker()
.
To elaborate, the docs (bullet 5) mentioned that the check: "timeStamp + 2 hours < block.timestamp
" (i.e., the 2-hour heartbeat
for the base price
) must be performed to verify the Chainlink data.
Apparently, the baseOracleCircuitBreaker()
correctly checks the base oracle in question (see @2
in the snippet below). However, the sponsor didn't implement this check for the same base oracle in the oracleCircuitBreaker()
(see @1
).
Hence, this issue is not a duplicate of #4, and it raises another point that is a valid issue because the sponsor implemented the use of base oracle
(ETH/USD price
) incorrectly (please refer to the docs (bullet 5)).
//@audit -- This report raises an issue regarding the lack of a stale price check for the base oracle (ETH/USD price) in the oracleCircuitBreaker() only, as the lack of a stale price check for the non-ETH/USD asset oracle (multi-asset oracle) was flagged as a known issue
function oracleCircuitBreaker(
uint80 roundId,
uint80 baseRoundId,
int256 chainlinkPrice,
int256 baseChainlinkPrice,
uint256 timeStamp,
uint256 baseTimeStamp
) private view {
@1 bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
@1 || baseRoundId == 0 || baseTimeStamp == 0 || baseTimeStamp > block.timestamp || baseChainlinkPrice <= 0; //@audit -- The oracleCircuitBreaker() lacks checking the stale price for the base oracle (ETH/USD price) (condition: "block.timestamp > 2 hours + baseTimeStamp")
if (invalidFetchData) revert Errors.InvalidPrice();
}
...
function baseOracleCircuitBreaker(
uint256 protocolPrice,
uint80 roundId,
int256 chainlinkPrice,
uint256 timeStamp,
uint256 chainlinkPriceInEth
) private view returns (uint256 _protocolPrice) {
bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
@2 || block.timestamp > 2 hours + timeStamp; //@audit -- Whereas the baseOracleCircuitBreaker() already checks that condition as per the docs
uint256 chainlinkDiff =
chainlinkPriceInEth > protocolPrice ? chainlinkPriceInEth - protocolPrice : protocolPrice - chainlinkPriceInEth;
bool priceDeviation = protocolPrice > 0 && chainlinkDiff.div(protocolPrice) > 0.5 ether;
...
}
@1 -- The oracleCircuitBreaker() lacks checking the stale price for the base oracle (ETH/USD price) (condition: "block.timestamp > 2 hours + baseTimeStamp")
: https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L125-L126
@2 -- Whereas the baseOracleCircuitBreaker() already checks that condition as per the docs
: https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L77
Agree, this seems like a valid issue. Returned base oracle price could be stale.
ditto-eth (sponsor) confirmed
hansfriese marked the issue as satisfactory
hansfriese marked the issue as not a duplicate
hansfriese marked the issue as selected for report
hansfriese marked the issue as primary issue
hansfriese marked the issue as not selected for report
hansfriese marked the issue as duplicate of #164
hansfriese marked the issue as partial-50
Lines of code
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#L77
Vulnerability details
Impact
The
LibOracle::oracleCircuitBreaker()
does not check the stale price for the base oracle (ETH/USD price). Specifically, I'm talking about the condition: "block.timestamp > 2 hours + baseTimeStamp
" (i.e., the 2-hour stale heartbeat as per the docs). Hence, the function cannot verify whether or not thebaseChainlinkPrice
is stale.Consequently, the
oracleCircuitBreaker()
will not revert the transaction as expected if thebaseChainlinkPrice
is stale. As a result, the protocol's core functions will consume the stale price, harming the protocol's and users' funds.Proof of Concept
The
oracleCircuitBreaker()
does not check the stale price for the base oracle (ETH/USD price) (i.e., the condition: "block.timestamp > 2 hours + baseTimeStamp
") compared to thebaseOracleCircuitBreaker()
.Without the stale price check mentioned above, the
oracleCircuitBreaker()
cannot verify whether thebaseChainlinkPrice
is stale. For this reason, theoracleCircuitBreaker()
will not revert the transaction as expected if thebaseChainlinkPrice
is stale.@1 -- The oracleCircuitBreaker() lacks checking the stale price for the base oracle (ETH/USD price) (condition: "block.timestamp > 2 hours + baseTimeStamp")
: https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L125-L126@2 -- Whereas the baseOracleCircuitBreaker() already checks that condition as per the docs
: https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L77Tools Used
Manual Review
Recommended Mitigation Steps
Add the stale price check for the base oracle (the condition:
block.timestamp > 2 hours + baseTimeStamp
) in theoracleCircuitBreaker()
.Assessed type
Oracle