# Rounding differences when computing the invariant

Notional's spot-price check rounds the StableMath invariant up while Balancer Composable Pools round down, so manipulated pools may pass the oracle check and trades execute at bad prices.

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

**Target:** Balancer leveraged-vault spot price / StableMath invariant
**Root cause:** Notional's StableMath._calculateInvariant is called with roundUp=true, but Balancer's Composable Pool StableMath always rounds down (Vyper truncation), producing a different invariant and thus a different computed spot price.
**Steps:**
1. Read Notional BalancerSpotPrice._calculateStableMathSpotPrice: calls StableMath._calculateInvariant(ampParam, balances, true).
2. Compare with Balancer ComposableStablePool StableMath._calculateInvariant which always rounds down.
3. Note the invariant feeds the spot price used to detect pool manipulation before vault actions like reinvest rewards.
4. Show that the rounding discrepancy yields a mismatched spot price, so a manipulated pool can pass the check and the trade executes against it.
**How found:** Code-reading comparison: reviewers noticed Notional's StableMath retains the legacy roundUp parameter and passes true, while Balancer's rewritten ComposableStablePool StableMath was changed to always round down to match Vyper truncation, flagging the divergence.
**Exploitation:** Precondition: attacker manipulates a Balancer Composable Pool's balances. Because Notional's invariant rounds up while Balancer's rounds down, the spot price Notional computes diverges from the pool's actual price; the manipulation check can fail to flag the pool, and the vault's reinvest/trade executes against the manipulated pool, causing asset loss.
**Insight:** When a protocol reimplements or vendors a DEX's math library, diff it against the current on-chain version of that DEX—upstream rewrites (e.g. Vyper-matching truncation) silently change rounding, and any oracle/manipulation check built on the stale math will diverge.
