`BalancerPairOracle` can be manipulated using read-only reentrancy
`BalancerPairOracle.getPrice` makes an external call to `BalancerVault.getPoolTokens` without checking the Balancer Vault's reentrancy guard. As a result, the oracle can be trivially manipulated to liquidate user positions prematurely.\\nIn February, the Balancer team disclosed a read-only reentrancy vulnerability in the Balancer Vault. The detailed disclosure can be found here. In short, all Balancer pools are susceptible to manipulation of their external queries, and all integrations must now take an extra step of precaution when consuming data. Via reentrancy, an attacker can force token balances and BPT supply to be out of sync, creating very inaccurate BPT prices.\\nSome protocols, such as Sentiment, remained unaware of this issue for a few months and were later hacked as a result.\\n`BalancerPairOracle.getPrice` makes a price calculation of the form `f(balances) / pool.totalSupply()`, so it is clearly vulnerable to synchronization issues between the two data points. A rough outline of the attack might look like this:\\n```\\nAttackerContract.flashLoan() ->\\n    // Borrow lots of tokens and trigger a callback.\\n    SomeProtocol.flashLoan() ->\\n        AttackerContract.exploit()\\n\\nAttackerContract.exploit() ->\\n    // Join a Balancer Pool using the borrowed tokens and send some ETH along with the call.\\n    BalancerVault.joinPool() ->\\n        // The Vault will return the excess ETH to the sender, which will reenter this contract.\\n        // At this point in the execution, the BPT supply has been updated but the token balances have not.\\n        AttackerContract.receive()\\n\\nAttackerContract.receive() ->\\n    // Liquidate a position using the same Balancer Pool as collateral.\\n    BlueBerryBank.liquidate() ->\\n        // Call to the oracle to check the price.\\n        BalancerPairOracle.getPrice() ->\\n            // Query the token balances. At this point in the execution, these have not been updated (see above).\\n            // So, the balances are still the same as before the start of the large pool join.\\n            BalancerVaul.getPoolTokens()\\n\\n            // Query the BPT supply. At this point in the execution, the supply has already been updated (see above).\\n            // So, it includes the latest large pool join, and as such the BPT supply has grown by a large amount.\\n            BalancerPool.getTotalSupply()\\n\\n            // Now the price is computed using both balances and supply, and the result is much smaller than it should be.\\n            price = f(balances) / pool.totalSupply()\\n\\n        // The position is liquidated under false pretenses.\\n```\\n
The Balancer team recommends utilizing their official library to safeguard queries such as `Vault.getPoolTokens`. However, the library makes a state-modifying call to the Balancer Vault, so it is not suitable for `view` functions such as `BalancerPairOracle.getPrice`. There are then two options:\\nInvoke the library somewhere else. Perhaps insert a hook into critical system functions like `BlueBerryBank.liquidate`.\\nAdapt a slightly different read-only solution that checks the Balancer Vault's reentrancy guard without actually entering.
Users choosing Balancer pool positions (such as Aura vaults) as collateral can be prematurely liquidated due to unreliable price data.
```\\nAttackerContract.flashLoan() ->\\n    // Borrow lots of tokens and trigger a callback.\\n    SomeProtocol.flashLoan() ->\\n        AttackerContract.exploit()\\n\\nAttackerContract.exploit() ->\\n    // Join a Balancer Pool using the borrowed tokens and send some ETH along with the call.\\n    BalancerVault.joinPool() ->\\n        // The Vault will return the excess ETH to the sender, which will reenter this contract.\\n        // At this point in the execution, the BPT supply has been updated but the token balances have not.\\n        AttackerContract.receive()\\n\\nAttackerContract.receive() ->\\n    // Liquidate a position using the same Balancer Pool as collateral.\\n    BlueBerryBank.liquidate() ->\\n        // Call to the oracle to check the price.\\n        BalancerPairOracle.getPrice() ->\\n            // Query the token balances. At this point in the execution, these have not been updated (see above).\\n            // So, the balances are still the same as before the start of the large pool join.\\n            BalancerVaul.getPoolTokens()\\n\\n            // Query the BPT supply. At this point in the execution, the supply has already been updated (see above).\\n            // So, it includes the latest large pool join, and as such the BPT supply has grown by a large amount.\\n            BalancerPool.getTotalSupply()\\n\\n            // Now the price is computed using both balances and supply, and the result is much smaller than it should be.\\n            price = f(balances) / pool.totalSupply()\\n\\n        // The position is liquidated under false pretenses.\\n```\\n