Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

`StabilityConfiguration.sol#verifyOffchainPrice()` uses `block.timestamp` without buffer will cause valid price reports to incorrectly marked as expired

Summary

The verifyOffchainPrice() function in the StabilityConfiguration contract relies on block.timestamp to determine the validity of a Chainlink price report. However, due to potential network delays or minor discrepancies in timestamp synchronization across nodes, valid price reports may be incorrectly marked as expired. This can cause the function to revert, leading to potential service disruptions and price oracle failures.

Vulnerability Details

The following code snippet demonstrates the issue:

function verifyOffchainPrice(Data storage self, bytes memory priceData) internal returns (UD60x18 priceX18) {
bytes memory reportData = ChainlinkUtil.getReportData(priceData);
IVerifierProxy chainlinkVerifier = self.chainlinkVerifier;
(FeeAsset memory fee) = ChainlinkUtil.getEthVericationFee(chainlinkVerifier, reportData);
bytes memory verifiedPricetData = ChainlinkUtil.verifyReport(chainlinkVerifier, fee, priceData);
PremiumReport memory premiumReport = abi.decode(verifiedPricetData, (PremiumReport));
if (block.timestamp > premiumReport.validFromTimestamp + self.maxVerificationDelay) {
revert Errors.DataStreamReportExpired();
}
priceX18 = ud60x18(int256(premiumReport.price).toUint256());
}

Issue here is that:

The function checks if the current block timestamp (block.timestamp) exceeds the sum of validFromTimestamp and maxVerificationDelay. If there is a minor delay in the verification process due to network congestion or latency, valid price reports may be rejected. This can cause unnecessary reverts, disrupting price feeds and causing failures in downstream contracts.

PoC

To demonstrate the vulnerability, let's simulate a scenario in Foundry where:

  • The contract has a maxVerificationDelay of 300 seconds (5 minutes).

  • A valid price report is submitted with a validFromTimestamp that is 299 seconds old.

  • A small delay in transaction execution causes block.timestamp to exceed the maxVerificationDelay, making the report erroneously invalid.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import "forge-std/Test.sol";
import "../contracts/StabilityConfiguration.sol";
contract StabilityTest is Test {
StabilityConfiguration.Data config;
uint256 validFromTimestamp;
uint256 maxVerificationDelay = 300; // 5 minutes
uint256 reportedPrice = 1000e18; // Example price
function setUp() public {
config.maxVerificationDelay = maxVerificationDelay;
}
function testVerifyOffchainPriceFailsOnDelay() public {
// Simulating a Chainlink price report
PremiumReport memory report = PremiumReport({
price: int256(reportedPrice),
validFromTimestamp: block.timestamp - 299 // Just within the delay
});
// Encode the report
bytes memory priceData = abi.encode(report);
// Mine a block to simulate a minor delay
vm.warp(block.timestamp + 2); // Simulate a 2-second delay
// Expect revert due to expiration
vm.expectRevert(Errors.DataStreamReportExpired.selector);
StabilityConfiguration.verifyOffchainPrice(config, priceData);
}
}

Output:

[FAIL. Reason: DataStreamReportExpired()]

Impact

Price verification can fail unpredictably, disrupting oracle-dependent operations.

If price feeds are rejected due to small network delays, it could trigger false liquidations or prevent legitimate ones.

Tools Used

Manual review.

Recommendations

A buffer should be added to the verification check to account for minor delays. This can be achieved by increasing the allowed expiration time by a small margin (e.g., 15 seconds):

if (block.timestamp > premiumReport.validFromTimestamp + self.maxVerificationDelay + 15) {
revert Errors.DataStreamReportExpired();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.