diff --git a/contracts/contracts/strategies/balancer/BaseAuraStrategy.sol b/contracts/contracts/strategies/balancer/BaseAuraStrategy.sol index a2e2115ef7..57c3c49a9e 100644 --- a/contracts/contracts/strategies/balancer/BaseAuraStrategy.sol +++ b/contracts/contracts/strategies/balancer/BaseAuraStrategy.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; * @title OETH Base Balancer Abstract Strategy * @author Origin Protocol Inc */ + import { BaseBalancerStrategy } from "./BaseBalancerStrategy.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IRateProvider } from "../../interfaces/balancer/IRateProvider.sol"; @@ -89,7 +90,9 @@ abstract contract BaseAuraStrategy is BaseBalancerStrategy { * the Aura rewards pool to this strategy contract. */ function _lpWithdrawAll() internal virtual override { - uint256 bptBalance = IERC4626(auraRewardPoolAddress).balanceOf( + // Get all the strategy's BPTs in Aura + // maxRedeem is implemented as balanceOf(address) in Aura + uint256 bptBalance = IERC4626(auraRewardPoolAddress).maxRedeem( address(this) ); @@ -123,7 +126,8 @@ abstract contract BaseAuraStrategy is BaseBalancerStrategy { { balancerPoolTokens = IERC20(platformAddress).balanceOf(address(this)) + - IERC4626(auraRewardPoolAddress).balanceOf(address(this)); + // maxRedeem is implemented as balanceOf(address) in Aura + IERC4626(auraRewardPoolAddress).maxRedeem(address(this)); } function _approveBase() internal virtual override { diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 913a946521..be41f40c06 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -184,6 +184,7 @@ const defaultFixture = deployments.createFixture(async () => { LUSDMetaStrategy, oethHarvester, oethDripper, + oethZapper, swapper, mockSwapper, swapper1Inch, @@ -297,6 +298,8 @@ const defaultFixture = deployments.createFixture(async () => { oethDripperProxy.address ); + oethZapper = await ethers.getContract("OETHZapper"); + // Replace OracelRouter to disable staleness const dMockOracleRouterNoStale = await deployWithConfirmation( "MockOracleRouterNoStale" @@ -558,6 +561,7 @@ const defaultFixture = deployments.createFixture(async () => { ConvexEthMetaStrategy, oethDripper, oethHarvester, + oethZapper, swapper, mockSwapper, swapper1Inch, @@ -834,18 +838,20 @@ async function convexVaultFixture() { /** * Configure a Vault with the balancerREthStrategy */ -function balancerREthFixtureSetup() { +function balancerREthFixtureSetup(config = { defaultStrategy: true }) { return deployments.createFixture(async () => { const fixture = await defaultFixture(); const { oethVault, timelock, weth, reth, balancerREthStrategy, josh } = fixture; - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(reth.address, balancerREthStrategy.address); - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(weth.address, balancerREthStrategy.address); + if (config.defaultStrategy) { + await oethVault + .connect(timelock) + .setAssetDefaultStrategy(reth.address, balancerREthStrategy.address); + await oethVault + .connect(timelock) + .setAssetDefaultStrategy(weth.address, balancerREthStrategy.address); + } fixture.rEthBPT = await ethers.getContractAt( "IERC20Metadata", @@ -854,12 +860,30 @@ function balancerREthFixtureSetup() { ); fixture.balancerREthPID = balancer_rETH_WETH_PID; + fixture.auraPool = await ethers.getContractAt( + "IERC4626", + addresses.mainnet.rETH_WETH_AuraRewards + ); + fixture.balancerVault = await ethers.getContractAt( "IBalancerVault", addresses.mainnet.balancerVault, josh ); + // Get some rETH from most loaded contracts/wallets + await impersonateAndFundAddress( + addresses.mainnet.rETH, + [ + "0xCc9EE9483f662091a1de4795249E24aC0aC2630f", + "0xC6424e862f1462281B0a5FAc078e4b63006bDEBF", + "0x7d6149aD9A573A6E2Ca6eBf7D4897c1B766841B4", + "0x7C5aaA2a20b01df027aD032f7A768aC015E77b86", + "0x1BeE69b7dFFfA4E2d53C2a2Df135C388AD25dCD2", + ], + josh.getAddress() + ); + return fixture; }); } diff --git a/contracts/test/strategies/balancerMetaStablePool.fork-test.js b/contracts/test/strategies/balancerMetaStablePool.fork-test.js index 9418b05938..ce18c8db65 100644 --- a/contracts/test/strategies/balancerMetaStablePool.fork-test.js +++ b/contracts/test/strategies/balancerMetaStablePool.fork-test.js @@ -1,5 +1,5 @@ const { expect } = require("chai"); -const { formatUnits } = require("ethers/lib/utils"); +const { formatUnits, parseUnits } = require("ethers").utils; const { BigNumber } = require("ethers"); const addresses = require("../../utils/addresses"); @@ -14,7 +14,12 @@ const { const log = require("../../utils/logger")("test:fork:strategy:balancer"); -const balancerREthFixture = balancerREthFixtureSetup(); +const balancerREthFixture = balancerREthFixtureSetup({ + defaultStrategy: true, +}); +const noDefaultBalancerREthFixture = balancerREthFixtureSetup({ + defaultStrategy: false, +}); const balancerWstEthFixture = balancerWstEthFixtureSetup(); forkOnlyDescribe( @@ -25,9 +30,6 @@ forkOnlyDescribe( // this.retries(3); let fixture; - beforeEach(async () => { - fixture = await balancerREthFixture(); - }); after(async () => { // This is needed to revert fixtures @@ -38,6 +40,9 @@ forkOnlyDescribe( }); describe("Post deployment", () => { + beforeEach(async () => { + fixture = await balancerREthFixture(); + }); it("Should have the correct initial state", async function () { const { balancerREthStrategy, oethVault } = fixture; @@ -88,13 +93,7 @@ forkOnlyDescribe( describe("Deposit", function () { beforeEach(async () => { - const { timelock, reth, weth, oethVault } = fixture; - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(reth.address, addresses.zero); - await oethVault - .connect(timelock) - .setAssetDefaultStrategy(weth.address, addresses.zero); + fixture = await noDefaultBalancerREthFixture(); }); it("Should deposit 5 WETH and 5 rETH in Balancer MetaStablePool strategy", async function () { const { reth, rEthBPT, weth } = fixture; @@ -172,6 +171,7 @@ forkOnlyDescribe( describe("Withdraw", function () { beforeEach(async () => { + fixture = await noDefaultBalancerREthFixture(); const { balancerREthStrategy, oethVault, strategist, reth, weth } = fixture; @@ -295,7 +295,218 @@ forkOnlyDescribe( it("Should be able to withdraw with higher withdrawal slippage", async function () {}); }); + describe("Large withdraw", function () { + const depositAmount = 30000; + let depositAmountUnits, oethVaultSigner; + beforeEach(async () => { + fixture = await noDefaultBalancerREthFixture(); + const { + balancerREthStrategy, + balancerREthPID, + balancerVault, + josh, + oethVault, + oethZapper, + strategist, + reth, + weth, + } = fixture; + + oethVaultSigner = await impersonateAndFundContract(oethVault.address); + + await getPoolBalances(balancerVault, balancerREthPID); + + // Mint 100k oETH using WETH + depositAmountUnits = oethUnits(depositAmount.toString()); + await oethZapper.connect(josh).deposit({ value: depositAmountUnits }); + + // Mint 100k of oETH using RETH + await reth.connect(josh).approve(oethVault.address, depositAmountUnits); + await oethVault.connect(josh).mint(reth.address, depositAmountUnits, 0); + + await oethVault + .connect(strategist) + .depositToStrategy( + balancerREthStrategy.address, + [weth.address, reth.address], + [depositAmountUnits, depositAmountUnits] + ); + + log( + `Vault deposited ${depositAmount} WETH and ${depositAmount} RETH to Balancer strategy` + ); + }); + it(`withdraw all ${depositAmount} of both assets together using withdrawAll`, async () => { + const { balancerREthStrategy, oethVault } = fixture; + + const stratValueBefore = await oethVault.totalValue(); + log(`Vault total value before: ${formatUnits(stratValueBefore)}`); + + // Withdraw all + await balancerREthStrategy.connect(oethVaultSigner).withdrawAll(); + log(`Vault withdraws all WETH and RETH`); + + const stratValueAfter = await oethVault.totalValue(); + log(`Vault total value after: ${formatUnits(stratValueAfter)}`); + + const diff = stratValueBefore.sub(stratValueAfter); + const baseUnits = depositAmountUnits.mul(2); + const diffPercent = diff.mul(100000000).div(baseUnits); + log( + `Vault's ETH value change: ${formatUnits(diff)} ETH ${formatUnits( + diffPercent, + 6 + )}%` + ); + }); + it(`withdraw close to ${depositAmount} of both assets using multi asset withdraw`, async () => { + const { + auraPool, + balancerREthStrategy, + rEthBPT, + oethVault, + reth, + weth, + } = fixture; + + const withdrawAmount = 29950; + const withdrawAmountUnits = oethUnits(withdrawAmount.toString(), 18); + + const stratValueBefore = await oethVault.totalValue(); + log(`Vault total value before: ${formatUnits(stratValueBefore)}`); + + // Withdraw all + // prettier-ignore + await balancerREthStrategy + .connect(oethVaultSigner)["withdraw(address,address[],uint256[])"]( + oethVault.address, + [weth.address, reth.address], + [withdrawAmountUnits, withdrawAmountUnits] + ); + log( + `Vault withdraws ${withdrawAmount} WETH and ${withdrawAmount} RETH together` + ); + + const bptAfterReth = await auraPool.balanceOf( + balancerREthStrategy.address + ); + log(`Aura BPTs after withdraw: ${formatUnits(bptAfterReth)}`); + log( + `Strategy BPTs after withdraw: ${formatUnits( + await rEthBPT.balanceOf(balancerREthStrategy.address) + )}` + ); + + const stratValueAfter = await oethVault.totalValue(); + log(`Vault total value after: ${formatUnits(stratValueAfter)}`); + + const diff = stratValueBefore.sub(stratValueAfter); + const baseUnits = withdrawAmountUnits.mul(2); + const diffPercent = diff.mul(100000000).div(baseUnits); + log( + `Vault's ETH value change: ${formatUnits(diff)} ETH ${formatUnits( + diffPercent, + 6 + )}%` + ); + }); + it(`withdraw ${depositAmount} of each asset in separate calls`, async () => { + const { + balancerREthStrategy, + rEthBPT, + oethVault, + timelock, + reth, + weth, + auraPool, + } = fixture; + + const stratValueBefore = await oethVault.totalValue(); + log(`Vault total value before: ${formatUnits(stratValueBefore)}`); + + const bptBefore = await auraPool.balanceOf( + balancerREthStrategy.address + ); + log(`Aura BPTs before: ${formatUnits(bptBefore)}`); + + const withdrawAmount = 29800; + const withdrawAmountUnits = oethUnits(withdrawAmount.toString(), 18); + + await balancerREthStrategy + .connect(timelock) + .setMaxWithdrawalSlippage(parseUnits("1", 16)); // 1% + + // Withdraw WETH + // prettier-ignore + await balancerREthStrategy + .connect(oethVaultSigner)["withdraw(address,address,uint256)"]( + oethVault.address, + weth.address, + withdrawAmountUnits + ); + + log(`Vault withdraws ${withdrawAmount} WETH`); + + const stratValueAfterWeth = await oethVault.totalValue(); + log( + `Vault total value after WETH withdraw: ${formatUnits( + stratValueAfterWeth + )}` + ); + const bptAfterWeth = await auraPool.balanceOf( + balancerREthStrategy.address + ); + log(`Aura BPTs after WETH withdraw: ${formatUnits(bptAfterWeth)}`); + log( + `Strategy BPTs after WETH withdraw: ${formatUnits( + await rEthBPT.balanceOf(balancerREthStrategy.address) + )}` + ); + + // Withdraw RETH + // prettier-ignore + await balancerREthStrategy + .connect(oethVaultSigner)["withdraw(address,address,uint256)"]( + oethVault.address, + reth.address, + withdrawAmountUnits + ); + + log(`Vault withdraws ${withdrawAmount} RETH`); + + const bptAfterReth = await auraPool.balanceOf( + balancerREthStrategy.address + ); + log(`Aura BPTs after RETH withdraw: ${formatUnits(bptAfterReth)}`); + log( + `Strategy BPTs after RETH withdraw: ${formatUnits( + await rEthBPT.balanceOf(balancerREthStrategy.address) + )}` + ); + + const stratValueAfterReth = await oethVault.totalValue(); + log( + `Vault total value after RETH withdraw: ${formatUnits( + stratValueAfterReth + )}` + ); + + const diff = stratValueBefore.sub(stratValueAfterReth); + const baseUnits = withdrawAmountUnits.mul(2); + const diffPercent = diff.mul(100000000).div(baseUnits); + log( + `Vault's ETH value change: ${formatUnits(diff)} ETH ${formatUnits( + diffPercent, + 6 + )}%` + ); + }); + }); + describe("Harvest rewards", function () { + beforeEach(async () => { + fixture = await balancerREthFixture(); + }); it("Should be able to collect reward tokens", async function () { const { josh, balancerREthStrategy, oethHarvester } = fixture;