Hardcoded 18-decimal wrapped tokens break unwrap fees and wallet displays
WrappedToken.initialize does not accept a decimals parameter and ERC20Upgradeable defaults to 18 for all wrapped tokens, so unwrap fees collapse to near-zero for non-18-decimal originals and wallets display tiny incorrect balances.
Description
WrappedToken.initialize never accepts or sets custom decimals.
ERC20Upgradeable defaults to 18 decimals for all wrapped tokens
regardless of the original token's decimals.
The Bridge mints and burns raw amounts directly without decimal conversion. This creates two concrete problems:
Asymmetric fee calculation between wrap and unwrap. In _handleWrap,
the fee is computed using permit.token (the original token), so
decimals is correct. In _handleUnwrap, the fee is computed using
permit.token (the wrapped token), which is always 18:
// _handleUnwrap, permit.token is the wrapped tokenuint8 decimals = (permit.token == address(0))? 18: IERC20WithDecimals(permit.token).decimals();uint256 maxFeeRate = 10 ** decimals;uint256 tokenFee =(recipient.amount * uctx.feeInfo.fee) / maxFeeRate;
For a 6-decimal original token (e.g., USDT on some chains): wrapping uses
maxFeeRate = 10^6 (correct), but unwrapping uses maxFeeRate = 10^18
(fees become near-zero). The protocol loses fee revenue on every unwrap
of non-18-decimal token pairs.
Misleading wallet and explorer display. Wrapping 1 USDC (1,000,000 raw units at 6 decimals) mints 1,000,000 raw wrapped tokens interpreted as 18 decimals, displaying as 0.000000000001 in wallets and block explorers.
While most BSC tokens use 18 decimals, the protocol has no validation preventing registration of non-18-decimal tokens.
Recommendation
Pass the original token's decimals to WrappedToken.initialize and
override decimals() to match:
uint8 private _decimals;function initialize(string memory name,string memory symbol,address _bridge,uint8 decimals_) public initializer {__ERC20_init(name, symbol);__ERC20Permit_init(name);__Ownable_init(_bridge);__UUPSUpgradeable_init();bridge = _bridge;_decimals = decimals_;}function decimals() public view override returns (uint8) {return _decimals;}
Resolution
YadaCoin, Confirmed. Added a decimals parameter to
WrappedToken.initialize and stored it in a _decimals state variable
with a corresponding decimals() override.
Zealynx, Fixed. Verified the fix including alignment of the
Factory.createToken parameters to pass the decimals value through the
full token creation flow.

