The timestamp of the price update (priceTimestamp) is checked to be less than 60 days in the past, however the oracle's heartbeat is 24 hours. Hence, any price older than the heartbeat might actually be stale
Vulnerability Details
In RewardsDistributor.amountToCompound, the function use require(block.timestamp - priceTimestamp < staleThreshold, "Price is stale"); to check for stale price, which is not correct.
116functionamountToCompound(uint256_alcxAmount) publicviewreturns (uint256,uint256[] memory) {117// Increased for testing since tests go into future118uint256 staleThreshold =60days;119120 (uint80 roundId,int256 alcxEthPrice,,uint256 priceTimestamp,uint80 answeredInRound) = priceFeed121 .latestRoundData();122123require(answeredInRound >= roundId,"Stale price");124 require(block.timestamp - priceTimestamp < staleThreshold, "Price is stale"); <<<--- Here the function checks stale price using 60 days
125require(alcxEthPrice >0,"Chainlink answer reporting 0");126127uint256[] memory normalizedWeights =IManagedPool(address(balancerPool)).getNormalizedWeights();128129uint256 amount = (((_alcxAmount *uint256(alcxEthPrice)) /1ether) * normalizedWeights[0]) /130 normalizedWeights[1];131132return (amount, normalizedWeights);133 }
Impact Details
The timestamp of the price update (priceTimestamp) is checked to be less than 60 days in the past, however the oracle's heartbeat is 24 hours. Hence, any price older than the heartbeat might actually be stale.
References
Add any relevant links to documentation or code
Proof of Concept
In the follow code, I will demo that RewardsDistributor.amountToCompound can use stale price. And because RewardsDistributor.amountToCompound is used by RewardsDistributor.claim in RewardsDistributor.sol#L175, so the stale price might impact the transaction.
Add the following code in src/test/Minter.t.sol and run