# YearnVault.sol#pull() will most certainly fail

YearnVault.pull() returns intended tokenAmounts instead of actual withdrawn amounts, so LpIssuer.withdraw() reverts on insufficient balance due to Yearn share precision loss.

- Payout: Audit contest
- Severity: high
- Program: Mellow Protocol
- Found by: WatchPug
- Disclosed: 2022-02-09
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2021-12-mellow-findings/issues/121
- Library page: https://bugbountylibrary.com/r/c4%3A2021-12-mellow%3AH-01
- Tags: area:web3 domain:web3 impact:dos kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:precision-loss

**Target:** YearnVault.sol pull() / LpIssuer.sol withdraw()
**Root cause:** yTokenAmount is computed by dividing by pricePerShare, truncating shares; the actual tokens returned by yToken.withdraw() are less than tokenAmounts[i], but actualTokenAmounts is set to the unadjusted tokenAmounts.
**Steps:**
1. Call LpIssuer.withdraw() with tokenAmounts[0] = 100 tokens.
2. pull() computes yTokenAmount = (tokenAmounts[i] * 10**decimals) / yToken.pricePerShare(), truncating.
3. yToken.withdraw(yTokenAmount, to, maxLoss) returns fewer than 100 tokens to the contract.
4. actualTokenAmounts[i] is still set to 100.
5. LpIssuer.withdraw() calls IERC20(_vaultTokens[i]).safeTransfer(to, actualTokenAmounts[i]) and reverts on insufficient balance.
**How found:** Code review of pull() loop noticing the return value of yToken.withdraw() is discarded and tokenAmounts is returned unadjusted, combined with precision-loss reasoning on the share division.
**Insight:** When a vault pull computes shares from a desired token amount via division, always capture the actual return value of the underlying withdraw rather than echoing the requested amount; truncation makes the two diverge and downstream transfers revert.
