Shiny Garnet Pheasant
Medium
Chainlink oracle gives wrong price if the latest price from aggregator hits the extreme values (either minPrice
or maxPrice
)
Chainlink aggregators include a circuit breaker that activates when an asset's price moves outside a preset range. In extreme cases, like the LUNA crash, the oracle will return the minPrice
or maxPrice
instead of the asset's actual value.
In getOraclePrice
function in OracleReader.sol contract, it tries to get the latest price from the oracle.
In the above function it calls the latestRoundData
function, which returns latest price and updated timestamp, which helps to check the price is not staled one.
When an asset's price exceeds these predefined thresholds, the oracle returns the capped value (minAnswer
or maxAnswer
) instead of the actual market price. This mechanism is intended to protect protocols from outlier data but fails in scenarios where the real market price diverges significantly, such as during the LUNA crash.
Example — Let's assume the token that to be created be TokenA, TokenA has minPrice
of $1. The price of TokenA drops to $0.1. The aggregator still returns $1 allowing the user to borrow against TokenA as if it is $1 which is 10X it’s actual value.
No response
Oracle price get's down below the minPrice
of aggregator. Let's say TokenA drops to $0.1 from $1.
Attacker Strategy:
- Buy that asset using DEX at very low price
- Deposit the asset into lending/ borrowing platforms using Chainlinks price feeds
- Borrow against that asset at the minimum price Chainlink price feed returns, even though actual price is far lower
This allow users to create or redeem token at wrong prices. This is precisely what occurred with Venus on BSC during the LUNA crash.
No response
(uint80, int256 answer, uint, uint, uint80) = oracle.latestRoundData();
// minPrice check
require(answer > minPrice, "Min price exceeded");
// maxPrice check
require(answer < maxPrice, "Max price exceeded");