Skip to content

Commit

Permalink
add support to withdraw the liquidity when balancer pool is in recove…
Browse files Browse the repository at this point in the history
…ry mode
  • Loading branch information
sparrowDom committed Nov 8, 2023
1 parent 4ac3998 commit 3f1b9f8
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 1 deletion.
2 changes: 2 additions & 0 deletions contracts/contracts/interfaces/balancer/IBalancerPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ interface IBalancerPool {
external
view
returns (IRateProvider[] memory providers);

function inRecoveryMode() external view returns (bool);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ pragma solidity ^0.8.0;
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { BaseAuraStrategy, BaseBalancerStrategy } from "./BaseAuraStrategy.sol";
import { IBalancerVault } from "../../interfaces/balancer/IBalancerVault.sol";
import { IBalancerPool } from "../../interfaces/balancer/IBalancerPool.sol";
import { IERC20, InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { StableMath } from "../../utils/StableMath.sol";

contract BalancerMetaPoolStrategy is BaseAuraStrategy {
using SafeERC20 for IERC20;
using StableMath for uint256;

// Special ExitKind for all Balancer pools, used in Recovery Mode.
uint256 constant RECOVERY_MODE_EXIT_KIND = 255;

/* For Meta stable pools the enum value should be "2" as it is defined
* in the IBalancerVault. From the Metastable pool codebase:
*
Expand Down Expand Up @@ -421,6 +426,18 @@ contract BalancerMetaPoolStrategy is BaseAuraStrategy {
* Is only executable by the OToken's Vault or the Governor.
*/
function withdrawAll() external override onlyVaultOrGovernor nonReentrant {
_withdrawAll(false);
}

function recoveryModeWithdrawAll()
external
onlyVaultOrGovernor
nonReentrant
{
_withdrawAll(true);
}

function _withdrawAll(bool isRecoveryModeWithdrawal) internal {
// STEP 1 - Withdraw all Balancer Pool Tokens (BPT) from Aura to this strategy contract

_lpWithdrawAll();
Expand All @@ -441,12 +458,27 @@ contract BalancerMetaPoolStrategy is BaseAuraStrategy {
* It is ok to pass an empty minAmountsOut since tilting the pool in any direction
* when doing a proportional exit can only be beneficial to the strategy. Since
* it will receive more of the underlying tokens for the BPT traded in.
*
* Important when `isRecoveryModeWithdrawal` is true then a special recovery mode exit
* kind is used for a much simpler and more gas efficient exit of the pool.
*/
bytes memory userData = abi.encode(
balancerExactBptInTokensOutIndex,
isRecoveryModeWithdrawal
? RECOVERY_MODE_EXIT_KIND
: balancerExactBptInTokensOutIndex,
BPTtoWithdraw
);

if (isRecoveryModeWithdrawal) {
/* Older Balancer pools don't support this functionality (e.g. rETH/WETH). In that case the
* transaction will just fail as it should.
*/
require(
IBalancerPool(platformAddress).inRecoveryMode(),
"Pool not in recovery mode"
);
}

IBalancerVault.ExitPoolRequest memory request = IBalancerVault
.ExitPoolRequest(poolAssets, minAmountsOut, userData, false);

Expand Down
13 changes: 13 additions & 0 deletions contracts/test/fixture/_fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,19 @@ async function balancerFrxETHwstETHeETHFixture(
josh
);

/* balancer Gnosis safe authorized account
* Use this Dube query to get relevant transactions:
- https://dune.com/queries/3184026
*/
const authorizerAddress = "0xa29f61256e948f3fb707b4b3b138c5ccb9ef9888";
const recoveryModeSigner = await impersonateAndFund(authorizerAddress);

fixture.enableRecoveryMode = async () => {
await fixture.sfrxETHwstETHrEthBPT
.connect(recoveryModeSigner)
.enableRecoveryMode();
};

await setERC20TokenBalance(josh.address, reth, "1000000", hre);
await setERC20TokenBalance(josh.address, frxETH, "1000000", hre);
await setERC20TokenBalance(josh.address, stETH, "1000000", hre);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,59 @@ describe("ForkTest: Balancer ComposableStablePool sfrxETH/wstETH/rETH Strategy",
expect(rethBalanceDiff).to.be.gte(await units("15", reth), 1);
expect(frxEthBalanceDiff).to.be.gte(await units("15", frxETH), 1);
});

it("Should be able to withdraw all of pool liquidity in recovery mode", async function () {
const {
oethVault,
stETH,
frxETH,
reth,
balancerSfrxWstRETHStrategy,
enableRecoveryMode,
} = fixture;

const stEthBalanceBefore = await balancerSfrxWstRETHStrategy[
"checkBalance(address)"
](stETH.address);
const rethBalanceBefore = await balancerSfrxWstRETHStrategy[
"checkBalance(address)"
](reth.address);
const frxEthBalanceBefore = await balancerSfrxWstRETHStrategy[
"checkBalance(address)"
](frxETH.address);

const oethVaultSigner = await impersonateAndFund(oethVault.address);

await expect(
balancerSfrxWstRETHStrategy
.connect(oethVaultSigner)
.recoveryModeWithdrawAll()
).to.be.revertedWith("Pool not in recovery mode");

await enableRecoveryMode();

await balancerSfrxWstRETHStrategy
.connect(oethVaultSigner)
.recoveryModeWithdrawAll();

const stEthBalanceDiff = stEthBalanceBefore.sub(
await balancerSfrxWstRETHStrategy["checkBalance(address)"](
stETH.address
)
);
const rethBalanceDiff = rethBalanceBefore.sub(
await balancerSfrxWstRETHStrategy["checkBalance(address)"](reth.address)
);
const frxEthBalanceDiff = frxEthBalanceBefore.sub(
await balancerSfrxWstRETHStrategy["checkBalance(address)"](
frxETH.address
)
);

expect(stEthBalanceDiff).to.be.gte(await units("15", stETH), 1);
expect(rethBalanceDiff).to.be.gte(await units("15", reth), 1);
expect(frxEthBalanceDiff).to.be.gte(await units("15", frxETH), 1);
});
});

describe("Large withdraw", function () {
Expand Down
10 changes: 10 additions & 0 deletions contracts/test/strategies/balancerMetaStablePool.fork-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,16 @@ describe("ForkTest: Balancer MetaStablePool rETH/WETH Strategy", function () {
expect(stEthBalanceDiff).to.be.gte(await units("15", reth), 1);
});

it("Should fail withdrawing all of pool liquidity in recovery mode (pool doesn't support it)", async function () {
const { oethVault, balancerREthStrategy } = fixture;

const oethVaultSigner = await impersonateAndFund(oethVault.address);

await expect(
balancerREthStrategy.connect(oethVaultSigner).recoveryModeWithdrawAll()
).to.be.reverted;
});

it("Should be able to withdraw with higher withdrawal deviation", async function () {});
});

Expand Down

0 comments on commit 3f1b9f8

Please sign in to comment.