When the 1inch router returns empty bytes, the function falls back to reading the contract's entire current token balance as the returnAmount. This is semantically wrong and can mask a completely failed or zero-output swap.
In the OPEN flow, the user's collateralAmount is transferred into the contract BEFORE the flash loan executes. So even if the swap produces zero output, balanceOf(address(this)) may still return a non-zero amount from that pre-existing deposit, causing require(returnAmount >= _minReturnAmount) to pass falsely.
A zero-output swap passes silently. The flash loan repayment logic then attempts to pull from whatever balance exists, potentially consuming the user's original collateral to repay the flash loan — effectively stealing the user's deposit. Even when it causes a revert, it does so only after state has been partially dirtied.
User deposits 10 WETH as collateral — contract holds 10 WETH
Flash loan of 20 WETH arrives — all 30 WETH supplied to Aave
Borrow 60,000 USDC, approve 1inch
Swap executes but router returns success=true with empty bytes (valid for some router paths)
Fallback: returnAmount = IERC20(WETH).balanceOf(address(this)) — if any WETH residual exists, check passes
Flash loan repayment proceeds against wrong accounting
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.