Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weighted Pool: ongoing reentrancy protection #2330

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "@balancer-labs/v2-solidity-utils/contracts/helpers/ERC20Helpers.sol";

Expand Down
12 changes: 12 additions & 0 deletions pkg/pool-utils/contracts/RecoveryMode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol";

import "./BasePoolAuthorization.sol";
import "./lib/VaultReentrancyLib.sol";

/**
* @notice Handle storage and state changes for pools that support "Recovery Mode".
Expand Down Expand Up @@ -85,13 +86,24 @@ abstract contract RecoveryMode is IRecoveryMode, BasePoolAuthorization {
* @notice Disable recovery mode, which disables the special safe exit path for LPs.
* @dev Protocol fees are not paid while in Recovery Mode, so it should only remain active for as long as strictly
* necessary.
*
* This function will revert when called within a Vault context (i.e. in the middle of a join or an exit).
*
* Disabling recovery mode in derived contracts often triggers code that depends on the invariant or total supply,
* which may be calculated incorrectly in the middle of a join or an exit, because the state of the pool could be
* out of sync with the state of the Vault. The call to `ensureNotInVaultContext` in `VaultReentrancyLib` ensures
* this is safe to call.
*
* See https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345 for reference.
*/
function disableRecoveryMode() external override authenticate {
// Some derived contracts respond to disabling recovery mode with state changes (e.g., related to protocol fees,
// or otherwise ensuring that enabling and disabling recovery mode has no ill effects on LPs). When called
// outside of recovery mode, these state changes might lead to unexpected behavior.
_ensureInRecoveryMode();

VaultReentrancyLib.ensureNotInVaultContext(_getVault());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be accompanied by a test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side comment (maybe for a follow-up PR): we are doing this here and in ProtocolFeeCache (which is derived from RecoveryMode) without a modifier.

Wouldn't it be more consistent to define the modifier here, and use it in both contracts? That way the code would be the same both in pools and in these auxiliary contracts.

Copy link
Collaborator Author

@EndymionJkb EndymionJkb Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; I thought of that, but it was spread over separate PRs, so I did them individually. Will do a follow-on to address that. (Perhaps I have too little faith in git merge :)


_setRecoveryMode(false);

emit RecoveryModeStateChanged(false);
Expand Down
11 changes: 11 additions & 0 deletions pkg/pool-weighted/contracts/BaseWeightedPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ abstract contract BaseWeightedPool is BaseMinimalSwapInfoPool {

/**
* @dev Returns the current value of the invariant.
* **IMPORTANT NOTE**: calling this function within a Vault context (i.e. in the middle of a join or an exit) is
* potentially unsafe, since the returned value is manipulable. It is up to the caller to ensure safety.
*
* Calculating the invariant requires the state of the pool to be in sync with the state of the Vault.
* That condition may not be true in the middle of a join or an exit.
*
* To call this function safely, attempt to trigger the reentrancy guard in the Vault by calling a non-reentrant
* function before calling `getInvariant`. That will make the transaction revert in an unsafe context.
* (See `VaultReentrancyLib.ensureNotInVaultContext` in pool-utils.)
*
* See https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345 for reference.
*/
function getInvariant() public view returns (uint256) {
(, uint256[] memory balances, ) = getVault().getPoolTokens(getPoolId());
Expand Down
14 changes: 13 additions & 1 deletion pkg/pool-weighted/contracts/WeightedPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,20 @@ contract WeightedPool is BaseWeightedPool, WeightedPoolProtocolFees {
* even if they don't yet exist, they will effectively be included in any Pool operation that involves BPT.
*
* In the vast majority of cases, this function should be used instead of `totalSupply()`.
*
* **IMPORTANT NOTE**: calling this function within a Vault context (i.e. in the middle of a join or an exit) is
* potentially unsafe, since the returned value is manipulable. It is up to the caller to ensure safety.
*
* This is because this function calculates the invariant, which requires the state of the pool to be in sync
* with the state of the Vault. That condition may not be true in the middle of a join or an exit.
*
* To call this function safely, attempt to trigger the reentrancy guard in the Vault by calling a non-reentrant
* function before calling `getActualSupply`. That will make the transaction revert in an unsafe context.
* (See `VaultReentrancyLib.ensureNotInVaultContext` in pool-utils.)
*
* See https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345 for reference.
*/
function getActualSupply() public view returns (uint256) {
function getActualSupply() external view returns (uint256) {
uint256 supply = totalSupply();

(uint256 protocolFeesToBeMinted, ) = _getPreJoinExitProtocolFees(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "@balancer-labs/v2-interfaces/contracts/pool-utils/IRateProviderPool.sol";
import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
Expand Down
Loading