Incorrect Handling of Fee-On-Transfer Tokens in swapToPaymentCoinAdmin Function
swapToPaymentCoinAdmin assumes the full amount is received from the payment token. Any fee-on-transfer token causes amountReceived to differ from pointToSwap, breaking accounting.
Description
The protocol intends to support all ERC20 tokens but does not currently support fee-on-transfer tokens. These tokens charge a fee on each transfer, meaning the amount received by the recipient is less than the amount sent by the sender. The current implementation of the swapToPaymentCoinAdmin function and similar functions in the Vault contract assumes that the transferred amount is received in full, which may lead to incorrect calculations and potential vulnerabilities when handling fee-on-transfer tokens.
Impact
- Incorrect Token Amounts: If a token charges a transfer fee, the contract may end up with fewer tokens than expected, leading to incorrect calculations and potential financial discrepancies.
- Potential Exploits: Malicious users could exploit this discrepancy to manipulate token balances in their favor, causing financial loss to the contract or other users.
- Operational Failures: Functions that rely on the assumption of exact amounts being transferred may fail or behave unpredictably, affecting the contract's functionality and reliability.
Some tokens take a transfer fee (e.g., STA, PAXG), while others do not currently charge a fee but may do so in the future (e.g., USDT, USDC).
function swapToPaymentCoinAdmin(address user, uint pointToSwap) public {require(onlyApprovedAdmin[msg.sender] == true, "You are not permitted");require(_Ipointscoin.balanceOf(user) >= pointToSwap, "Not enough points");require(pointToSwap >= pointsMin, "Points to swap less than minpoints");uint balanceBefore = _Ipaymentcoin.balanceOf(address(this));_Ipointscoin.burn(user, pointToSwap);uint balanceAfter = _Ipaymentcoin.balanceOf(address(this));uint amountReceived = balanceAfter - balanceBefore;claimedPaymentCoin += amountReceived;uint _fee = (amountReceived * withdrawalFee) / 100;uint amountAfterFee = amountReceived - _fee;_Ipaymentcoin.transfer(user, amountAfterFee);_Ipaymentcoin.transfer(msg.sender, _fee);emit Swap(user, block.timestamp, pointToSwap, amountAfterFee, _fee);}
Potential Issue
If the _Ipaymentcoin charges a fee on transfer, amountReceived will be less than pointToSwap, causing the function to behave incorrectly. This issue arises because the implementation verifies that the transfer was successful by checking that the balance of the recipient is greater than or equal to the initial balance plus the amount transferred. This check will fail for fee-on-transfer tokens because the actual received amount will be less than the input amount.
Recommendation
To mitigate this issue, modify the function to check the contract's balance before and after the transfer to calculate the actual amount received.
function swapToPaymentCoinAdmin(address user, uint pointToSwap) public {require(onlyApprovedAdmin[msg.sender] == true, "You are not permitted");require(_Ipointscoin.balanceOf(user) >= pointToSwap, "Not enough points");require(pointToSwap >= pointsMin, "Points to swap less than minpoints");uint balanceBefore = _Ipaymentcoin.balanceOf(address(this));_Ipointscoin.burn(user, pointToSwap);uint balanceAfter = _Ipaymentcoin.balanceOf(address(this));uint amountReceived = balanceAfter - balanceBefore;claimedPaymentCoin += amountReceived;uint _fee = (amountReceived * withdrawalFee) / 100;uint amountAfterFee = amountReceived - _fee;_Ipaymentcoin.transfer(user, amountAfterFee);_Ipaymentcoin.transfer(msg.sender, _fee);emit Swap(user, block.timestamp, pointToSwap, amountAfterFee, _fee);}
Explanation
- Check Balance Before Transfer: Capture the contract's balance of the payment token before the transfer.
- Perform the Transfer: Burn the points token from the user and transfer the payment token to the contract.
- Check Balance After Transfer: Capture the contract's balance of the payment token after the transfer.
- Calculate Amount Received: Calculate the actual amount received by the contract.
- Proceed with Fee Calculation and Transfer: Calculate and transfer the fee, then transfer the remaining amount to the user.
Additional recommendations:
- Calculate Actual Amount Received: Ensure the function calculates the actual amount received by checking the balance before and after the transfer.
- Handle Transfer Fees: Update the function to account for transfer fees and adjust the transferred amounts accordingly.
- Documentation: Clearly document the handling of fee-on-transfer tokens to inform users and developers of the expected behavior.

