F-2024-0009·fee-on-transfer-mishandling

Incorrect ticket calculation for Tokens with fee-on-transfer

Acknowledgeddexammraffle
TL;DR

purchaseTickets calculates tickets from the input _amount but transfers the tokens via safeTransferFrom, so fee-on-transfer tokens credit users with more tickets than the contract actually receives.

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

Description

The purchaseTickets function in the MonadexV1Raffle contract does not account for tokens that implement a fee-on-transfer mechanism. These tokens deduct a fee from the transfer amount, resulting in the recipient receiving less than the amount specified in the transfer.

The current implementation calculates the number of tickets to mint based on the input _amount. However, it then transfers the tokens using:

solidity
IERC20(_token).safeTransferFrom(_swapper, address(this), _amount);

For fee-on-transfer tokens, the actual amount received by the contract will be less than _amount. This discrepancy leads to an incorrect calculation of tickets, where users receive more tickets than they should based on the actual amount of tokens received by the contract.

03Section · Impact

Impact

  1. Users of fee-on-transfer tokens would receive more raffle tickets than they should, giving them an unfair advantage over users of standard tokens.
  2. The raffle system would consistently overvalue fee-on-transfer tokens, potentially leading to economic exploits or imbalances in the protocol.
  3. The contract's internal accounting of received tokens versus issued tickets would be inaccurate, potentially causing issues in other parts of the system.
04Section · Recommendation

Recommendation

To address this issue, implement a two-step process for purchasing tickets with fee-on-transfer tokens:

First, transfer the tokens and calculate the actual amount received:

solidity
uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
IERC20(_token).safeTransferFrom(_swapper, address(this), _amount);
uint256 balanceAfter = IERC20(_token).balanceOf(address(this));
uint256 actualAmount = balanceAfter - balanceBefore;

Then, use the actualAmount to calculate the number of tickets to mint:

solidity
uint256 ticketsToMint = previewPurchase(_token, actualAmount, _multiplier);
F-2024-0009

oog
zealynx

Smart Contract Security Digest

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

© 2026 Zealynx