Summary
TWAP is Time Weighted Average Price. Let's look on example:
Suppose price = 5 for 5 seconds; price = 0 for 5 seconds; price = 10 for 5 seconds. As a result TWAP is (5 * 5 + 0 * 5 + 10 * 5) / 15 = 5
However current implementation will return 0 price if any of the observed rounds contained 0 price.
Vulnerability Details
In TWAP function it loops thorogh previous rounds and fetches data. Note that it calls checkForInvalidTimestampOrAnswer()
every time to validate retrieved data and in case of error immediately returns 0:
function getTwap(
address priceAggregatorAddress,
uint256 maxTimeout,
uint256 lookback
) internal view returns (uint256 price) {
...
try priceAggregator.latestRoundData() returns (
uint80 roundId,
int256 answer,
uint256 ,
uint256 timestamp,
uint80
) {
if (roundId == 0) return 0;
@> if (checkForInvalidTimestampOrAnswer(timestamp, answer, block.timestamp, maxTimeout)) {
return 0;
}
TwapVariables memory t;
t.endTimestamp = block.timestamp.sub(lookback);
if (timestamp <= t.endTimestamp) {
return uint256(answer).mul(PRECISION).div(10 ** decimals);
} else {
t.lastTimestamp = block.timestamp;
while (timestamp > t.endTimestamp) {
t.cumulativePrice = t.cumulativePrice.add(
uint256(answer).mul(t.lastTimestamp.sub(timestamp))
);
roundId -= 1;
t.lastTimestamp = timestamp;
(answer, timestamp) = getRoundData(priceAggregator, roundId);
if (
@> checkForInvalidTimestampOrAnswer(
timestamp,
answer,
t.lastTimestamp,
maxTimeout
)
) {
return 0;
}
}
t.cumulativePrice = t.cumulativePrice.add(
uint256(answer).mul(t.lastTimestamp.sub(t.endTimestamp))
);
return t.cumulativePrice.mul(PRECISION).div(10 ** decimals).div(lookback);
}
} catch {
return 0;
}
}
Here you can see that it will signall an error if returned price is 0 assuming theer is an error on Chainlink side:
function checkForInvalidTimestampOrAnswer(
uint256 timestamp,
int256 answer,
uint256 currentTimestamp,
uint256 maxTimeout
) private pure returns (bool) {
if (timestamp == 0 || timestamp > currentTimestamp) return true;
if (currentTimestamp.sub(timestamp) > maxTimeout) return true;
@> if (answer <= 0) return true;
return false;
}
Impact
TWAP incorrectly prices tokens if any of the observed rounds had 0 price.
Chainlink team confirmed that Price feed will report price 0 if it's fair price is 0, 0 answer is not error anymore. Here's answer:

Here is link to answer in Chainlink's Discord server
Tools Used
Manual Review
Recommendations
Refactor check:
function checkForInvalidTimestampOrAnswer(
uint256 timestamp,
int256 answer,
uint256 currentTimestamp,
uint256 maxTimeout
) private pure returns (bool) {
...
- if (answer <= 0) return true;
+ if (answer < 0) return true;
return false;
}