# `TradingUtils._executeTrade()` doesn't check `preTradeBalance` properly.

TradingUtils._executeTrade leaves preTradeBalance at 0 for ERC20-to-WETH/ETH trades, so the contract wraps/unwraps its entire ETH/WETH balance and misreports amountBought.

- Payout: Contest reward
- Severity: high
- Program: Notional
- Credited: 0x52
- Disclosed: 2022-09-15
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2022-09-notional-judging/issues/110
- Library page: https://bugbountylibrary.com/r/sherlock%3A2022-09-notional%3AH-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error

**Target:** Solidity trading library (TradingUtils) in leveraged vaults
**Root cause:** preTradeBalance is only set when sellToken is WETH/ETH; for ERC20 sell tokens it stays 0, so the post-trade wrap/unwrap branch treats the contract's whole ETH or WETH balance as newly acquired.
**Steps:**
1. Call _executeTrade with trade.sellToken = arbitrary ERC20, trade.buyToken = WETH, spender != ETH_ADDRESS.
2. Both preTradeBalance-setting branches are skipped, leaving preTradeBalance = 0.
3. After target.call, address(this).balance > 0 triggers depositAmount = full ETH balance, wrapping all contract ETH to WETH.
4. amountBought computed from postTradeBalance - preTradeBalance includes the entire pre-existing WETH balance, corrupting accounting.
5. Symmetric case: buyToken = ETH unwraps the contract's entire WETH balance.
**How found:** Code reading of _executeTrade's conditional preTradeBalance assignment; noticed the variable is only initialized in the WETH-sell/ETH-sell branches, leaving it 0 for ERC20 sells while the post-trade branches still compare against it.
**Exploitation:** Any trade path where sellToken is a non-WETH ERC20 and buyToken is WETH/ETH causes the contract to wrap or unwrap its full ETH/WETH holdings, which are then attributed as trade proceeds, leaking vault funds into the trade accounting.
**Insight:** When a balance-delta pattern initializes a baseline only in some branches, check every path that later reads that baseline; uninitialized deltas turn whole-contract balances into 'proceeds'.
