F-2024-0003·decimal-mismatch

Multiple decimal precision issues in ticket calculation

Acknowledgeddexammraffle
TL;DR

calculateTicketsToMint hardcodes 18 decimals for all tokens and mishandles Pyth oracle expo data, so users with non-18-decimal tokens receive orders of magnitude more or fewer tickets than intended.

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

Description

The calculateTicketsToMint function in the MonadexV1Library contract contains multiple critical issues related to decimal precision handling. These issues stem from incorrect assumptions about token decimals, price representations, and Pyth oracle data formatting.

Key issues identified:

  1. Hardcoded 18 decimal places assumption for all tokens.
  2. Incorrect representation of the ticket price without proper decimal scaling.
  3. Mishandling of Pyth oracle data, which may not always be in 18 decimal precision.
03Section · Impact

Impact

Users could receive orders of magnitude more or fewer tickets than intended, especially for tokens with decimals other than 18.

The raffle system's economy could be completely destabilized, with some users gaining a massive unfair advantage. If more tickets are minted than intended, the prize pool could be drained much faster than designed.

04Section · Recommendation

Recommendation

  • Modify the function to accept and use the specific decimal places of each token:
solidity
function calculateTicketsToMint(
uint256 _amount,
PythStructs.Price memory _pythPrice,
uint256 _pricePerTicket,
uint8 _tokenDecimals
) internal pure returns (uint256) {
uint256 adjustedAmount = _amount * 10**(18 - _tokenDecimals);
// ... rest of the calculation
}
  • Ensure the PRICE_PER_TICKET constant is correctly scaled:
solidity
uint256 internal constant PRICE_PER_TICKET = 1e18; // $1 with 18 decimal places
  • Use Pyth's utility functions to correctly interpret price data:
solidity
import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";
uint256 price = PythUtils.convertToUint(_pythPrice.price, _pythPrice.expo, 18);
uint256 confidence = PythUtils.convertToUint(_pythPrice.conf, _pythPrice.expo, 18);
F-2024-0003

oog
zealynx

Smart Contract Security Digest

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

© 2026 Zealynx