sherlock-audit/2023-06-unstoppable-judging

0xbepresent - The `Vault._to_usd_oracle_price()` function uses the same `ORACLE_FRESHNESS_THRESHOLD` for all token prices feeds which is incorrect

Opened this issue · 2 comments

0xbepresent

medium

The Vault._to_usd_oracle_price() function uses the same ORACLE_FRESHNESS_THRESHOLD for all token prices feeds which is incorrect

Summary

The same ORACLE_FRESHNESS_THRESHOLD is used for all the token prices feeds which can be dangerous because different pairs of tokens have different freshness intervals.

Vulnerability Detail

The ORACLE_FRESHNESS_THRESHOLD is 24 hours constant. It is used to check if the Oracle price is fresh in the 580 code line.

File: Vault.vy
562: def _to_usd_oracle_price(_token: address) -> uint256:
...
...
576:     round_id, answer, started_at, updated_at, answered_in_round = ChainlinkOracle(
577:         self.to_usd_oracle[_token]
578:     ).latestRoundData()
579: 
580:     assert (block.timestamp - updated_at) < ORACLE_FRESHNESS_THRESHOLD, "oracle not fresh"
...
...

The problem is that different pairs have diferrent heartbeats. For example the LINK/USD has a heartbeat of 3600 seconds so since the ORACLE_FRESHNESS_THRESHOLD is set to 24 hours, the check for LINK/USD is useless since the its hearbeat is 3600 seconds. The same behaivour in CRV/USD which has a heartbeat of 3600 seconds.

Impact

Using the same ORACLE_FRESHNESS_THRESHOLD (heartbeat) for all the price feeds is not correct becuase the freshness validation would be useless for some pairs which can return stale data.

Code Snippet

The Vault._to_usd_oracle_price() function:

File: Vault.vy
562: def _to_usd_oracle_price(_token: address) -> uint256:
563:     """
564:     @notice
565:         Retrieves the latest Chainlink oracle price for _token.
566:         Ensures that the Arbitrum sequencer is up and running and
567:         that the Chainlink feed is fresh.
568:     """
569:     assert self._sequencer_up(), "sequencer down"
570: 
571:     round_id: uint80 = 0
572:     answer: int256 = 0
573:     started_at: uint256 = 0
574:     updated_at: uint256 = 0
575:     answered_in_round: uint80 = 0
576:     round_id, answer, started_at, updated_at, answered_in_round = ChainlinkOracle(
577:         self.to_usd_oracle[_token]
578:     ).latestRoundData()
579: 
580:     assert (block.timestamp - updated_at) < ORACLE_FRESHNESS_THRESHOLD, "oracle not fresh"
581: 
582:     usd_price: uint256 = convert(answer, uint256)  # 8 dec
583:     return usd_price

Tool used

Manual review

Recommendation

Use the corresponding heartbeat ORACLE_FRESHNESS_THRESHOLD for each token in the Vault._to_usd_oracle_price() function.

Fixed by introducing a mapping that has an individual ORACLE_FRESHNESS_THRESHOLD for each token, instead of using the same one for all of them.