Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how
F-2026-0024·fee-on-transfer-mismatch

Missing fee-on-transfer accounting in _handleWrap leads to potential under-collateralization of wrapped tokens

Fixedbridgecross-chainkey-registrygithub.com/pdxwebdev/yadakeyeventwallet
TL;DR

_handleWrap mints based on the nominal recipient.amount, so registering a fee-on-transfer original token gradually under-collateralizes the wrapped supply.

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

Description

In _handleWrap, the bridge mints wrapped tokens based on the nominal transfer amount without verifying how many original tokens were actually received:

solidity
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.

03Section · Recommendation

Recommendation

Use balance-before/after accounting to mint only the actually received amount:

solidity
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
);
04Section · Resolution

Resolution

YadaCoin, Confirmed.

Zealynx, Fixed.

Status
Fixed
F-2026-0024