Missing fee-on-transfer accounting in _handleWrap leads to potential under-collateralization of wrapped tokens
_handleWrap mints based on the nominal recipient.amount, so registering a fee-on-transfer original token gradually under-collateralizes the wrapped supply.
Description
In _handleWrap, the bridge mints wrapped tokens based on the nominal
transfer amount without verifying how many original tokens were actually
received:
IERC20(pair.originalToken).safeTransferFrom(wctx.user, address(this), recipient.amount - tokenFee);if (tokenFee > 0) {IERC20(pair.originalToken).safeTransferFrom(wctx.user, feeCollector, tokenFee);}WrappedToken(pair.wrappedToken).mint(wctx.prerotatedKeyHash, recipient.amount - tokenFee);
If the original token applies a transfer tax (fee-on-transfer), the bridge
receives fewer tokens than recipient.amount - tokenFee, but still mints
the full nominal amount of wrapped tokens. Over time, this creates a
collateral deficit: more wrapped tokens exist than the bridge holds in
original tokens.
Token pair registration via registerKeyPairWithTransfer has no token-type
validation or whitelist, so nothing prevents a fee-on-transfer token from
being registered. This is particularly relevant on BSC, where
fee-on-transfer tokens (SafeMoon-style) are prevalent.
If a fee-on-transfer token is registered as an original token, the bridge gradually becomes under-collateralized for that pair. Late unwrappers would find insufficient original tokens in the bridge to redeem their wrapped tokens.
Recommendation
Use balance-before/after accounting to mint only the actually received amount:
uint256 balBefore = IERC20(pair.originalToken).balanceOf(address(this));IERC20(pair.originalToken).safeTransferFrom(wctx.user, address(this), recipient.amount - tokenFee);uint256 actualReceived = IERC20(pair.originalToken).balanceOf(address(this)) - balBefore;WrappedToken(pair.wrappedToken).mint(wctx.prerotatedKeyHash, actualReceived);
Resolution
YadaCoin, Confirmed.
Zealynx, Fixed.