# Risk of silent overflow in reserves update

Caviar PrivatePool buy/sell cast uint256 reserve deltas to uint128 without bounds checks, silently overflowing and breaking the xy=k invariant to misprice trades.

- Payout: Contest reward
- Severity: high
- Program: Caviar
- Credited: sashik\_eth
- Disclosed: 2023-05-18
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-04-caviar-findings/issues/167
- Library page: https://bugbountylibrary.com/r/c4%3A2023-04-caviar%3AH-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:precision-loss

**Target:** PrivatePool.sol buy()/sell() virtual reserve accounting
**Root cause:** netInputAmount/feeAmount/weightSum are uint256 but virtualBaseTokenReserves and virtualNftReserves are uint128; the downcast is done without checking the value fits, so it truncates silently.
**Steps:**
1. Deploy a base token with 30 decimals (HighDecimalsToken) and initialize a PrivatePool with virtualBaseTokenReserves = 100_000_000 * 1e30 and virtualNftReserves = 10 * 1e18.
2. Mint 10 NFTs to the pool and prepare tokenIds for 8 of them.
3. Record kBefore = virtualBaseTokenReserves * virtualNftReserves.
4. Call buyQuote(8 * 1e18), deal the required netInputAmount, approve the pool.
5. Call `privatePool.buy(tokenIds, tokenWeights, proofs)`.
6. Compute kAfter and assert kBefore == kAfter, showing the invariant was broken by the uint128 truncation.
**How found:** Code reading of buy()/sell() reserve updates: noticed uint256 arithmetic results being cast to uint128 without a bounds check, then built a high-decimals-token test to demonstrate the truncation.
**Exploitation:** With a high-decimal base token, netInputAmount - feeAmount - protocolFeeAmount exceeds type(uint128).max; the cast truncates, so virtualBaseTokenReserves is updated with a wrong small value while virtualNftReserves decreases normally, breaking xy=k and letting the attacker buy NFTs at a discounted price.
**Insight:** When a pool stores virtual reserves as uint128 but computes deltas in uint256, test with tokens of extreme decimals (e.g. 30) so the delta exceeds uint128 max; assert the xy=k invariant before/after to prove truncation.
