(
round_id, price, started_at, updated_at, answered_in_round
) = staticcall price_price.latestRoundData()
assert updated_at != 0, "DSCEngine_StalePrice"
assert answered_in_round >= round_id, "DSCEngine_StalePrice"
...
<assert seconds_since <= TIMEOUT, "DSCEngine_StalePrice">
The oracle safety check only verifies that the round data is fresh.
It does not verify that the returned price is greater than zero.
So the following sequence is possible:
1. latestRoundData() returns:
- updated_at = recent timestamp
- answered_in_round >= round_id
- price = 0
2. oracle_lib._stale_check_latest_round_data() accepts this result because:
- updated_at != 0
- answered_in_round >= round_id
- seconds_since <= TIMEOUT
3. The engine then uses this zero price in downstream calculations.
In _get_token_amount_from_usd():
(usd_amount_in_wei * PRECISION)
If price == 0, this becomes:
(usd_amount_in_wei * PRECISION)
Which simplifies to division by zero and reverts.
Example:
- user or liquidator calls a function that needs token/USD conversion
- the oracle answer is fresh but zero
- the conversion path reverts
- the whole operation fails
This can break any path that depends on price-based conversion, including:
- liquidation flows
- collateral redemption calculations
- view/helper functions that determine token amount from USD
- any operational logic that depends on these conversions
So even though the data is "fresh", it is still invalid and can deny protocol functionality.
*/
- accept any fresh price
+ reject non-positive prices before returning oracle data
+ add an explicit check such as: assert price > 0, "DSCEngine_InvalidPrice"
+ use a dedicated invalid-price error instead of reusing the stale-price error
+ add unit tests for:
+ - price == 0
+ - price < 0
+ - fresh but invalid rounds
+ ensure all downstream pricing logic can only operate on validated oracle answers