When fetching the oracle price there are no checks for the built in circuit breakers from the Chainlink aggregator for the minimum price in PriceTierVesting._getOraclePrice() which is used from PriceTierVestingSale_2_0.getClaimableAmount(). The circuit breaker should be used to check if the price of an asset goes outside of a predetermined price band.
If an asset suffers a huge drop in value (i.e. LUNA crash), the chainlink oracle will still return the minPrice instead of the actual price of the asset, and the protocol can go into debt or suffer losses, see Venus on BSC when LUNA imploded.
Vulnerability Detail
PriceTierVestingSale_2_0.claim() calls PriceTierVestingSale_2_0.getClaimableAmount() which uses PriceTierVesting.getVestedFraction() which calls PriceTierVesting._getOraclePrice() to determine the price which is used to determine the vested fraction.
PriceTierVesting._getOraclePrice() is missing necessary checks via the built in circuit breaks to determine whether the price of an asset went outside of the bounds of min/max prices.
Impact
The protocol can potentially suffer from a wrong price fetched from the Chainlink oracle, where the fetched price potentially is higher than the actual price of the asset, resulting in calculating claimable amounts higher than they should be, resulting in a loss.
Consider checking the returned answer from the Chainlink oracle against the min/max price of the built in circuit breakers and revert if the answer is outside of the bounds:
lemonmon
medium
Missing price checks for Chainlink oracle
Summary
When fetching the oracle price there are no checks for the built in circuit breakers from the Chainlink aggregator for the minimum price in
PriceTierVesting._getOraclePrice()
which is used fromPriceTierVestingSale_2_0.getClaimableAmount()
. The circuit breaker should be used to check if the price of an asset goes outside of a predetermined price band.If an asset suffers a huge drop in value (i.e. LUNA crash), the chainlink oracle will still return the minPrice instead of the actual price of the asset, and the protocol can go into debt or suffer losses, see Venus on BSC when LUNA imploded.
Vulnerability Detail
PriceTierVestingSale_2_0.claim()
callsPriceTierVestingSale_2_0.getClaimableAmount()
which usesPriceTierVesting.getVestedFraction()
which callsPriceTierVesting._getOraclePrice()
to determine the price which is used to determine the vested fraction.PriceTierVesting._getOraclePrice()
is missing necessary checks via the built in circuit breaks to determine whether the price of an asset went outside of the bounds of min/max prices.Impact
The protocol can potentially suffer from a wrong price fetched from the Chainlink oracle, where the fetched price potentially is higher than the actual price of the asset, resulting in calculating claimable amounts higher than they should be, resulting in a loss.
Code Snippet
https://github.com/sherlock-audit/2023-06-tokensoft/blob/main/contracts/contracts/claim/abstract/PriceTierVesting.sol#L30-L45
https://github.com/sherlock-audit/2023-06-tokensoft/blob/main/contracts/contracts/claim/abstract/PriceTierVesting.sol#L84-L91
https://github.com/sherlock-audit/2023-06-tokensoft/blob/main/contracts/contracts/claim/PriceTierVestingSale_2_0.sol#L128
https://github.com/sherlock-audit/2023-06-tokensoft/blob/main/contracts/contracts/claim/PriceTierVestingSale_2_0.sol#L100
Tool used
Manual Review
Recommendation
Consider checking the returned answer from the Chainlink oracle against the min/max price of the built in circuit breakers and revert if the answer is outside of the bounds:
See Chainlink API docs on minAnswer, maxAnswer that provide the prices for minPrice and maxPrice referenced above.