# Oracle manipulation via missing balancer vault read-only reentrancy check

Opal's BPTOracle reads Balancer Vault and pool data without ensureNotInVaultContext, enabling read-only reentrancy to skew BPT prices and misprice swaps/shares.

- Payout: Contest reward
- Severity: high
- Program: Opal
- Credited: cuthalion0x
- Disclosed: 2024-03-30
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_opal_feb2024.pdf#3.2.1
- Library page: https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.2.1
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:oracle-manipulation vuln:reentrancy

**Target:** BPTOracle.sol BPT pricing functions (Balancer pool oracle)
**Root cause:** BPTOracle queries Vault.getPoolTokens() and pool totalSupply()/getRate() without calling VaultReentrancyLib.ensureNotInVaultContext(); the library is imported but never invoked, so reads during a pool join/exit return mismatched data.
**Steps:**
1. Attacker initiates a Balancer pool join/exit that enters Vault context
2. During the callback, call Opal's BPTOracle.getPoolValuation()/BptPrice* functions
3. Oracle reads Vault.getPoolTokens() and pool totalSupply()/getRate() at inconsistent state
4. Computed BPT price is skewed; use it in Opal swap/share issuance to extract value
**How found:** Recognized the known Balancer read-only reentrancy advisory pattern, then code-read BPTOracle.sol and noticed VaultReentrancyLib was imported/referenced at L39 but ensureNotInVaultContext() was never actually called in any pricing function.
**Exploitation:** Attacker triggers a Balancer pool join/exit to enter Vault context, then reenters Opal's BPT oracle during the callback so it reads mismatched Vault token balances vs pool BPT supply, producing an inaccurate BPT price used for swap limits or share issuance.
**Insight:** When a codebase imports a vendor's reentrancy-guard library, grep for actual invocations of the guard function—imports and `using` directives are not enforcement; check every function that mixes Vault state with pool ERC20 state.
