Summary
Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. The result is that if an asset experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset. This would allow user to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNA imploded.
Vulnerability Details
* @notice get data about the latest round. Consumers are encouraged to check
* that they're receiving fresh data by inspecting the updatedAt and
* answeredInRound return values.
* Note that different underlying implementations of AggregatorV3Interface
* have slightly different semantics for some of the return values. Consumers
* should determine what implementations they expect to receive
* data from and validate that they can properly handle return data from all
* of them.
* @param base base asset address
* @param quote quote asset address
* @return roundId is the round ID from the aggregator for which the data was
* retrieved combined with a phase to ensure that round IDs get larger as
* time moves forward.
* @return answer is the answer for the given round
* @return startedAt is the timestamp when the round was started.
* (Only some AggregatorV3Interface implementations return meaningful values)
* @return updatedAt is the timestamp when the round last was updated (i.e.
* answer was last computed)
* @return answeredInRound is the round ID of the round in which the answer
* was computed.
* (Only some AggregatorV3Interface implementations return meaningful values)
* @dev Note that answer and updatedAt may change between queries.
*/
function checkForInvalidTimestampOrAnswer(
uint256 timestamp,
int256 answer,
uint256 currentTimestamp,
uint256 maxTimeout
) private pure returns (bool) {
if (timestamp == 0 || timestamp > currentTimestamp) return true;
if (currentTimestamp.sub(timestamp) > maxTimeout) return true;
if (answer <= 0) return true;
return false;
}
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/4e0ad0b964f74a1b4880114f4dd5b339bc69cd3e/protocol/contracts/libraries/Oracle/LibChainlinkOracle.sol#L182C5-L196C6
ChainlinkFeedRegistry#latestRoundData
pulls the associated aggregator and requests round data from it. ChainlinkAggregators have minPrice and maxPrice circuit breakers built into them. This means that if the price of the asset drops below the minPrice, the protocol will continue to value the token at minPrice instead of it's actual value. This will allow users to take out huge amounts of bad debt and bankrupt the protocol.
Example with Ubiquity & LUSD Collateral.
LUSD price = 1$ minPrice = 0.5$
Black Swan event = price drops to 0.1$ Due to the minPrice, the aggregator still returns 0.5$
Alice buys 1000$ of LUSD = 10_000 LUSD token. Alice calls mintDollar for 10_000 LUSD and receives 4950 Dollar (1% minting fee deducted) Alice swap the 4950 Dollar on the curve Dollar/3pool for USDC and makes around 4800$ in profit.
Impact
In the event that an asset crashes (i.e. LUNA) the protocol can be manipulated to give out Dollars at an inflated price of collateral.
Tools Used
Recommendations
include the below check to the function checkForInvalidTimestampOrAnswer
+ require(answer >= maxPrice && answer <= minPrice, "Invalid price");