# Incorrect invariant used for Balancer's composable pools

Notional's Balancer spot-price check computed the stable invariant from only two token balances, so manipulation of multi-token composable pools could evade the deviation guard.

- Payout: Contest reward
- Severity: high
- Program: Notional
- Credited: xiaoming90
- Disclosed: 2023-10-09
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-10-notional-judging/issues/83
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-10-notional%3AH-5
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:oracle-manipulation

**Target:** BalancerSpotPrice.sol _calculateStableMathSpotPrice in leveraged-vaults
**Root cause:** _calculateStableMathSpotPrice passes only scaledPrimary and secondary into StableMath._calculateInvariant, but a composable/phantom stable pool's invariant requires balances of all tokens except BPT (up to 5).
**Steps:**
1. Read BalancerSpotPrice.sol#L90: `StableMath._calculateInvariant(ampParam, StableMath._balances(scaledPrimary, secondary), true)`.
2. Note composable pools support up to 5 tokens excluding BPT; invariant formula uses n = number of tokens.
3. Compare with Balancer SDK `_poolDerivatives`, which passes all balances except BPT into `_invariant`.
4. Deploy a composable pool with >2 tokens; manipulate one of the omitted token balances.
5. Observe computed spot price deviates from true value, so `_checkPriceAndCalculateValue` fails to revert (false negative) or reverts spuriously (false positive).
**How found:** Code review of the spot-price helper against Balancer's own StableMath/ComposableStablePool and the balancer-sor SDK `_poolDerivatives`, noticing the invariant call only received two balances while the formula's n is the full token count.
**Exploitation:** Attacker manipulates a >2-token composable pool; the flawed spot price passes the deviation tolerance check, letting `reinvestReward` add liquidity into the manipulated pool (asset loss) or inflating collateral/health-factor via `convertStrategyToUnderlying`, potentially draining Notional V3.
**Escalation:** False-negative price check feeds collateral valuation, so pool manipulation can inflate account health factor and enable protocol-wide drain rather than a single vault loss.
**Insight:** When reimplementing a third-party AMM's math, diff your call against the upstream SDK's actual argument construction (here `_poolDerivatives`), not just the formula signature; array-length-dependent invariants silently break for pools with more tokens than your test fixture.
