Summary
When users input collateral amounts that are not evenly divisible by 1e10, the WBTC to wWBTC conversion causes precision loss that may result in transaction reverts.
Vulnerability Detail
The WBTCWrapper converts between 8-decimal WBTC and 18-decimal wWBTC by multiplying/dividing by 1e10. When a user provides collAmount that is not divisible by 1e10:
- User deposits 1.5e10 wWBTC worth of collateral
- Actual WBTC transferred = 1.5e10 / 1e10 = 1 WBTC (truncated)
- Zapper wraps 1 WBTC → 1e10 wWBTC
- Zapper tries to use
collAmount(1.5e10) but only has 1e10 wWBTC - Transaction reverts
Impact
Users must ensure their input amounts are divisible by 1e10 or transactions will revert.
Code Snippet
function openTroveWithWBTC(OpenTroveParams calldata _params) external {
// User provides _params.collAmount in wWBTC (18 decimals)
// But WBTC transfer truncates to 8 decimals
IWBTC(wbtcWrapper.WBTC()).transferFrom(msg.sender, address(this), _params.collAmount / 1e10);
// Wrap back to wWBTC - may have less than _params.collAmount
wbtcWrapper.depositFor(address(this), _params.collAmount / 1e10);
// This may revert if there's not enough wWBTC
borrowerOperations.openTrove(..., _params.collAmount, ...);
}Tool Used
Manual Review
Recommendation
Add input validation or round up the wrapped amount:
function openTroveWithWBTC(OpenTroveParams calldata _params) external {
require(_params.collAmount % 1e10 == 0, "Amount must be divisible by 1e10");
// ... rest of function
}Or round up to handle dust:
uint256 wbtcAmount = _params.collAmount / 1e10;
uint256 actualWrappedAmount = wbtcAmount * 1e10;
// Use actualWrappedAmount instead of _params.collAmount