# Two token vault will be broken if it comprises tokens with different decimals

Notional's two-token Balancer vault mis-scales spot price for tokens with different decimals, making _checkPriceLimit always revert and blocking reward reinvestment and vault settlement.

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

**Target:** Balancer two-token vault math library (Stable2TokenOracleMath)
**Root cause:** _getSpotPrice normalizes balances to 1e18 for stable math but the reverse scaleFactor divides by the ratio of scale factors, leaving spotPrice denominated in token decimals (1e6 or 1e30) instead of 1e18, while the oracle price is always 1e18.
**Steps:**
1. Configure a two-token vault with DAI (18 dec) primary and USDC (6 dec) secondary; primaryScaleFactor=1e18, secondaryScaleFactor=1e30.
2. Call _getSpotPrice with primaryBalance=100e18, secondaryBalance=100e6; scaled balances both become 100e18 and StableMath._calcSpotPrice returns 1e18.
3. Reverse scaling: scaleFactor = 1e30*1e18/1e18 = 1e30; spotPrice = 1e18*1e18/1e30 = 1e6.
4. Compare against oraclePrice=1e18 in _checkPriceLimit; discrepancy causes revert.
5. Repeat with USDC primary/DAI secondary to get spotPrice=1e30, also reverting.
**How found:** Code review of the spot price scaling logic; noticed reverse scaleFactor uses ratio of scale factors rather than normalizing to 1e18, and confirmed by hand-computing USDC/DAI and DAI/USDC cases and comparing to the always-1e18 oracle price.
**Insight:** When a function normalizes inputs to a common precision for math, verify the output is re-normalized to the same precision the caller expects; here the reverse scale used a scale-factor ratio that only cancels when both tokens share decimals, hiding the bug in same-decimal pools like wstETH/WETH.
