Skip to content

Commit

Permalink
Introduce PoolRecoveryEnabler (#2013)
Browse files Browse the repository at this point in the history
* Add PoolRecoveryEnabler

* Add tests

* Apply suggestions from code review

Co-authored-by: EndymionJkb <[email protected]>

* lint

* Rename contract and function

* Add docs

* Apply suggestions from code review

Co-authored-by: EndymionJkb <[email protected]>

* Apply suggestions from code review

Co-authored-by: Tom French <[email protected]>

Co-authored-by: Jeffrey Bennett <[email protected]>
Co-authored-by: Tom French <[email protected]>
  • Loading branch information
3 people authored Nov 17, 2022
1 parent 751f00b commit d701551
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pkg/pool-utils/contracts/BasePoolAuthorization.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import "@balancer-labs/v2-solidity-utils/contracts/helpers/Authentication.sol";
abstract contract BasePoolAuthorization is Authentication {
address private immutable _owner;

address private constant _DELEGATE_OWNER = 0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B;
address internal constant _DELEGATE_OWNER = 0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B;

constructor(address owner) {
_owner = owner;
Expand Down
124 changes: 124 additions & 0 deletions pkg/standalone-utils/contracts/PoolRecoveryHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// 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/IBasePoolFactory.sol";
import "@balancer-labs/v2-interfaces/contracts/pool-utils/IRateProviderPool.sol";
import "@balancer-labs/v2-interfaces/contracts/pool-utils/IRecoveryMode.sol";

import "@balancer-labs/v2-solidity-utils/contracts/helpers/SingletonAuthentication.sol";
import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/EnumerableSet.sol";

/**
* @dev This contract allows anyone to check a given Pool's rate providers and put the Pool into recovery mode
* if any are reverting on `getRate`. This allows LPs to exit promptly, and also helps off-chain mechanisms
* identify failed pools and prevent further traffic from being routed to them (since in this state swap operations
* would fail).
*/
contract PoolRecoveryHelper is SingletonAuthentication {
using EnumerableSet for EnumerableSet.AddressSet;

EnumerableSet.AddressSet private _factories;

constructor(IVault vault, address[] memory initialFactories) SingletonAuthentication(vault) {
for (uint256 i = 0; i < initialFactories.length; ++i) {
require(_factories.add(initialFactories[i]), "Duplicate initial factory");
}
}

/**
* @notice Adds a Pool Factory to the helper. Only Pools created from factories added via this function can be
* passed to `enableRecoveryMode()`.
*/
function addPoolFactory(address factory) external authenticate {
require(_factories.add(factory), "Duplicate factory");
}

/**
* @notice Removes a Pool Factory from the helper.
*/
function removePoolFactory(address factory) external authenticate {
require(_factories.remove(factory), "Non-existent factory");
}

/**
* @notice Returns the total number of Pool Factories.
*/
function getFactoryCount() external view returns (uint256) {
return _factories.length();
}

/**
* @notice Returns the address of a Pool Factory at an index between 0 and the return value of `getFactoryCount()`.
*/
function getFactoryAtIndex(uint256 index) external view returns (IBasePoolFactory) {
return IBasePoolFactory(_factories.at(index));
}

/**
* @notice Returns true if the Pool has been created from a known factory.
*/
function isPoolFromKnownFactory(address pool) public view returns (bool) {
uint256 totalFactories = _factories.length();
for (uint256 i = 0; i < totalFactories; ++i) {
IBasePoolFactory factory = IBasePoolFactory(_factories.unchecked_at(i));

if (factory.isPoolFromFactory(pool)) {
return true;
}
}

return false;
}

/**
* @notice Enables Recovery Mode in a Pool, provided some of its rate providers are failing (i.e. `getRate()`
* reverts).
*
* Pools that are in Recovery Mode can be exited by LPs via the special Recovery Mode Exit, which avoids any complex
* computations and does not call into any external contracts, which makes it a very dependable way to retrieve the
* underlying tokens.
*
* However, while Recovery Mode is enabled the Pool pays no protocol fees. Additionally, any protocol fees
* accrued before enabling Recovery Mode will be forfeited.
*
* The Pool must have been created via a known Pool Factory contract.
*/
function enableRecoveryMode(address pool) external {
// We require that the Pools come from known factories as a sanity check since this function is permissionless.
// This ensures we're actually calling legitimate Pools, and that they support both the IRateProviderPool and
// IRecoveryMode interfaces.
require(isPoolFromKnownFactory(pool), "Pool is not from known factory");

// The Pool will be placed in recovery mode if any of its rate providers reverts.
IRateProvider[] memory rateProviders = IRateProviderPool(pool).getRateProviders();
for (uint256 i = 0; i < rateProviders.length; ++i) {
if (rateProviders[i] != IRateProvider(0)) {
try rateProviders[i].getRate() {
// On success, we simply keep processing rate providers
continue;
} catch {
IRecoveryMode(pool).enableRecoveryMode();
return;
}
}
}

// If no rate providers revert, we then revert to both signal that calling this function performs no state
// changes, and to help prevent these accidental wasteful calls.
revert("Pool's rate providers do not revert");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

import "@balancer-labs/v2-interfaces/contracts/pool-utils/IRateProviderPool.sol";
import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v2-pool-utils/contracts/BasePoolAuthorization.sol";
import "@balancer-labs/v2-pool-utils/contracts/RecoveryMode.sol";

contract MockRecoveryRateProviderPool is IRateProviderPool, BasePoolAuthorization, RecoveryMode {
IVault private immutable _vault;
bool private _recoveryMode;

IRateProvider[] private _rateProviders;

constructor(IVault vault, IRateProvider[] memory rateProviders)
Authentication(bytes32(uint256(address(this))))
BasePoolAuthorization(_DELEGATE_OWNER)
{
_vault = vault;
_rateProviders = rateProviders;
}

// IRateProviderPool

function getRateProviders() external view override returns (IRateProvider[] memory) {
return _rateProviders;
}

// BasePoolAuthorization

function _getAuthorizer() internal view override returns (IAuthorizer) {
return _vault.getAuthorizer();
}

// Recovery Mode

function inRecoveryMode() public view override returns (bool) {
return _recoveryMode;
}

function _setRecoveryMode(bool enabled) internal override {
_recoveryMode = enabled;
}

function _doRecoveryModeExit(
uint256[] memory,
uint256,
bytes memory
) internal override returns (uint256, uint256[] memory) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// 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/vault/IVault.sol";
import "@balancer-labs/v2-pool-utils/contracts/factories/BasePoolFactory.sol";
import "./MockRecoveryRateProviderPool.sol";

contract MockRecoveryRateProviderPoolFactory is BasePoolFactory {
constructor(IVault _vault, IProtocolFeePercentagesProvider protocolFeeProvider)
BasePoolFactory(_vault, protocolFeeProvider, type(MockRecoveryRateProviderPool).creationCode)
{
// solhint-disable-previous-line no-empty-blocks
}

function create(IRateProvider[] memory rateProviders) external returns (address) {
return _create(abi.encode(getVault(), rateProviders));
}
}
41 changes: 41 additions & 0 deletions pkg/standalone-utils/contracts/test/MockRevertingRateProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;

import "@balancer-labs/v2-interfaces/contracts/pool-utils/IRateProvider.sol";
import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol";

contract MockRevertingRateProvider is IRateProvider {
uint256 private _rate;

bool private _revertOnGetRate;

constructor() {
_rate = FixedPoint.ONE;
_revertOnGetRate = false;
}

function getRate() external view override returns (uint256) {
if (_revertOnGetRate) {
revert("getRate revert");
}

return _rate;
}

function setRevertOnGetRate(bool revertOnGetRate) external {
_revertOnGetRate = revertOnGetRate;
}
}
Loading

0 comments on commit d701551

Please sign in to comment.