Fee-on-Transfer Remediation
Overview
Fee-on-transfer (deflationary) tokens deduct a percentage on every transfer, so a contract that pulls tokens with transferFrom receives less than the nominal amount. Crediting the user with the requested amount makes the protocol undercollateralized; the shortfall compounds until withdrawals can no longer be honored and the last users find the contract insolvent.
Related Detector: Fee-on-Transfer
Recommended Fix
Before (Vulnerable)
function deposit(address token, uint256 amount) external {
IERC20(token).transferFrom(msg.sender, address(this), amount);
balances[msg.sender] += amount; // Credits full amount
}
After (Fixed)
function deposit(address token, uint256 amount) external {
uint256 before = IERC20(token).balanceOf(address(this));
IERC20(token).transferFrom(msg.sender, address(this), amount);
uint256 received = IERC20(token).balanceOf(address(this)) - before;
balances[msg.sender] += received; // Credits actual received
}
Measuring the balance before and after the transfer captures the real amount that arrived, fee included. Internal accounting then matches the contract’s actual token holdings, so withdrawals always have backing.
Alternative Mitigations
- Maintain an explicit allowlist of vetted non-fee tokens and reject all others, so the requested amount is guaranteed to equal the received amount.
- Apply the same before/after balance measurement on the withdrawal path when sending tokens out, since fees also apply on outbound transfers.
- For rebasing or elastic-supply tokens, track shares rather than absolute balances.
Common Mistakes
- Computing
receivedbut still using the originalamountlater in the function for share or reward math. - Reusing a single contract-wide balance snapshot across reentrant or batched deposits, which corrupts the delta.
- Assuming a token is fee-free because its fee is currently set to zero; some tokens (such as configurable USDT) can enable a fee later.