# ERC721WrapperBase.getSqrtRatioX96() missing decimal adjustment causes wrong collat- eral valuation

ERC721WrapperBase.getSqrtRatioX96() omitted token decimal adjustment, making oracle-derived sqrtPrice ~1e6x off for USDC/WETH, corrupting LP collateral valuation.

- Payout: Contest reward
- Severity: high
- Program: VII Finance
- Credited: Trynax
- Disclosed: 2026-03-07
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_vii_finance_jan2026.pdf#3.1.2
- Library page: https://bugbountylibrary.com/r/cantina%3Avii_finance_jan2026%3A3.1.2
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:business-logic vuln:oracle-manipulation

**Target:** ERC721WrapperBase.getSqrtRatioX96() / Uniswap V3 LP collateral valuation
**Root cause:** Oracle quotes are normalized to 18-decimal USD, so dividing them discards original token decimals; the resulting sqrtRatioX96 is in value space, not Uniswap raw-wei coordinate space expected by TickMath/LiquidityAmounts.
**Steps:**
1. Read ERC721WrapperBase.getSqrtRatioX96(): `sqrtRatioX96 = sqrt(token0UnitValue * 2^96 / token1UnitValue) << 48`
2. Note oracle.getQuote normalizes both to 18-dec USD, losing token decimals
3. Trace sqrtRatioX96 into UniswapV3Wrapper.calculateValueOfTokenId -> previewUnwrap -> UniswapPositionValueHelper.principal -> LiquidityAmounts.getAmountsForLiquidity
4. Compare against TickMath.getSqrtPriceAtTick bounds which use raw wei units
5. For USDC(6)/WETH(18) at $2000, current formula yields sqrtPrice ~1e6x smaller than pool slot0
6. Run PoC: `forge test --match-contract SqrtRatioDecimalBugTest -vvv --fork-url <RPC>`; observe Error: 999555x
7. Fix: multiply by unit1/unit0 inside sqrt: `sqrt(token0UnitValue * unit1 * 2^96 / (token1UnitValue * unit0)) << 48`
**How found:** Code review of getSqrtRatioX96 noticed oracle outputs are normalized to 18-dec USD, then traced the value into Uniswap tick math which expects raw wei units; recognized the decimal factor was dropped and confirmed with a fork test comparing to pool slot0.
**Exploitation:** Borrowers deposit LP NFT collateral in a different-decimal pool (e.g. USDC/WETH); getAmountsForLiquidity misjudges price as out of range and returns 100% USDC / 0% WETH, so calculateValueOfTokenId returns garbage, letting borrowers overborrow or be unfairly liquidated.
**Insight:** When bridging an oracle price into Uniswap TickMath/LiquidityAmounts, verify the sqrtPriceX96 is in raw token-wei coordinates; oracle-normalized USD ratios silently drop decimals, and same-decimal test pairs (USDC/USDT) hide the factor.
