F-2024-0004·oracle-staleness

Missing Price Update Leads to User Being Unable to Receive Tickets

Acknowledgeddexammraffle
TL;DR

_getTicketsToMint reads getPriceNoOlderThan() from Pyth without first calling updatePriceFeeds(), so the price feed is stale and the post-swap ticket flow always reverts.

Severity
HIGH
Impact
HIGH
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
LOW
Exploitability
HIGH
02Section · Description

Description

The _getTicketsToMint function in the contract does not call updatePriceFeeds() before retrieving the price data from the Pyth oracle using getPriceNoOlderThan(). This function fetches the latest available price within a specified time window, but without updating the price feed beforehand, the contract may be using stale data. As a result, the process of receiving tickets after a swap fails.

According to the Pyth official documentation, it is recommended to call updatePriceFeeds() before using price data to ensure it is fresh and up-to-date.

solidity
PythStructs.Price memory price = IPyth(i_pyth).getPriceNoOlderThan(
config.priceFeedId,
config.noOlderThan
);
03Section · Impact

Impact

Not calling updatePriceFeeds() causes the function to always revert, making users unable to receive tickets.

04Section · Recommendation

Recommendation

To mitigate this issue, ensure that the price feed is updated before retrieving the price from the oracle. This can be achieved by calling updatePriceFeeds() with the appropriate price update data before retrieving the price. Keep in mind that this updateData should be obtained off-chain using the Hermes API as recommended by the Pyth official documentation and passed as an argument to the function.

Implementation pattern:

  1. Update the swap functions in the IMonadexV1Router interface to have an extra updateData parameter:
solidity
function swapExactTokensForTokens(
uint256 _amountIn,
uint256 _amountOutMin,
address[] calldata _path,
address _receiver,
uint256 _deadline,
MonadexV1Types.PurchaseTickets memory _purchaseTickets,
bytes[] memory updateData
) external returns (uint256[] memory, uint256);
  1. Add the extra updateData parameter to the swap functions in the MonadexV1Router contract and call _purchaseRaffleTickets with the updateData parameter.

  2. Add the extra updateData parameter to the _purchaseRaffleTickets function and call purchaseTickets from the MonadexV1Raffle contract with updateData.

  3. Create an _updatePriceFeeds function in the MonadexV1Raffle contract:

solidity
function _updatePriceFeeds(bytes[] memory updateData) internal {
uint256 updateFee = IPyth(i_pyth).getUpdateFee(updateData);
IPyth(i_pyth).updatePriceFeeds{ value: updateFee }(updateData);
}
  1. Add the extra updateData parameter to the purchaseTickets function and call _updatePriceFeeds before calling previewPurchase, which calls _getTicketsToMint, where the getPriceNoOlderThan function is called.
F-2024-0004

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx