diff --git a/contracts/protocol/modules/UniswapYieldStrategy.sol b/contracts/protocol/modules/UniswapYieldStrategy.sol deleted file mode 100644 index 7721b7a6e..000000000 --- a/contracts/protocol/modules/UniswapYieldStrategy.sol +++ /dev/null @@ -1,443 +0,0 @@ -/* - Copyright 2020 Set Labs Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - SPDX-License-Identifier: Apache License, Version 2.0 -*/ - -pragma solidity 0.6.10; -pragma experimental "ABIEncoderV2"; - -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { Math } from "@openzeppelin/contracts/math/Math.sol"; -import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; - -import { IController } from "../../interfaces/IController.sol"; -import { Invoke } from "../lib/Invoke.sol"; -import { ISetToken } from "../../interfaces/ISetToken.sol"; -import { IStakingRewards } from "../../interfaces/external/IStakingRewards.sol"; -import { IUniswapV2Pair } from "../../interfaces/external/IUniswapV2Pair.sol"; -import { IUniswapV2Router } from "../../interfaces/external/IUniswapV2Router.sol"; -import { ModuleBase } from "../lib/ModuleBase.sol"; -import { Position } from "../lib/Position.sol"; -import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol"; - - -contract UniswapYieldStrategy is ModuleBase, ReentrancyGuard { - using Position for ISetToken; - using Invoke for ISetToken; - using SafeMath for uint256; - using SafeCast for uint256; - using SafeCast for int256; - - /* ============ State Variables ============ */ - - IUniswapV2Router public uniswapRouter; - IUniswapV2Pair public lpToken; - IERC20 public assetOne; - IERC20 public assetTwo; - IERC20 public uni; - address public feeRecipient; - IStakingRewards public rewarder; - ISetToken public setToken; - uint256 public reservePercentage; // Precise percentage (e.g. 10^16 = 1%) - uint256 public slippageTolerance; // Precise percentage - uint256 public rewardFee; // Precise percentage - uint256 public withdrawalFee; // Precise percentage - uint256 public assetOneBaseUnit; - uint256 public assetTwoBaseUnit; - uint256 public lpTokenBaseUnit; - - /* ============ Constructor ============ */ - - constructor( - IController _controller, - IUniswapV2Router _uniswapRouter, - IUniswapV2Pair _lpToken, - IERC20 _assetOne, - IERC20 _assetTwo, - IERC20 _uni, - IStakingRewards _rewarder, - address _feeRecipient - ) - public - ModuleBase(_controller) - { - controller = _controller; - uniswapRouter = _uniswapRouter; - lpToken = _lpToken; - assetOne = _assetOne; - assetTwo = _assetTwo; - uni = _uni; - rewarder = _rewarder; - feeRecipient = _feeRecipient; - - uint256 tokenOneDecimals = ERC20(address(_assetOne)).decimals(); - assetOneBaseUnit = 10 ** tokenOneDecimals; - uint256 tokenTwoDecimals = ERC20(address(_assetTwo)).decimals(); - assetTwoBaseUnit = 10 ** tokenTwoDecimals; - uint256 lpTokenDecimals = ERC20(address(_lpToken)).decimals(); - lpTokenBaseUnit = 10 ** lpTokenDecimals; - } - - /* ============ External Functions ============ */ - - function engage() external nonReentrant { - _engage(); - } - - function disengage() external nonReentrant { - _rebalance(0); - - uint256 lpTokenQuantity = _calculateDisengageLPQuantity(); - - _unstake(lpTokenQuantity); - - _approveAndRemoveLiquidity(lpTokenQuantity); - - _updatePositions(); - } - - function reap() external nonReentrant { - _handleReward(); - - _engage(); - } - - function rebalance() external nonReentrant { - _rebalance(0); - - _updatePositions(); - } - - function rebalanceSome(uint256 _sellTokenQuantity) external nonReentrant { - _rebalance(_sellTokenQuantity); - - _updatePositions(); - } - - function unstakeAndRedeem(uint256 _setTokenQuantity) external nonReentrant { - require(setToken.balanceOf(msg.sender) >= _setTokenQuantity, "User must have sufficient SetToken"); - - setToken.burn(msg.sender, _setTokenQuantity); - - uint256 lpTokenUnit = setToken.getExternalPositionRealUnit(address(lpToken), address(this)).toUint256(); - - uint256 userLPBalance = lpTokenUnit.preciseMul(_setTokenQuantity); - - _unstake(userLPBalance); - - uint256 lpFees = userLPBalance.preciseMul(withdrawalFee); - setToken.invokeTransfer(address(lpToken), msg.sender, userLPBalance.sub(lpFees)); - setToken.invokeTransfer(address(lpToken), feeRecipient, lpFees); - - uint256 assetOneUnit = setToken.getDefaultPositionRealUnit(address(assetOne)).toUint256(); - uint256 assetOneNotional = assetOneUnit.preciseMul(_setTokenQuantity); - uint256 assetOneFee = assetOneNotional.preciseMul(withdrawalFee); - setToken.invokeTransfer(address(assetOne), msg.sender, assetOneNotional.sub(assetOneFee)); - setToken.invokeTransfer(address(assetOne), feeRecipient, assetOneFee); - - uint256 assetTwoUnit = setToken.getDefaultPositionRealUnit(address(assetTwo)).toUint256(); - uint256 assetTwoNotional = assetTwoUnit.preciseMul(_setTokenQuantity); - uint256 assetTwoFee = assetTwoNotional.preciseMul(withdrawalFee); - setToken.invokeTransfer(address(assetTwo), msg.sender, assetTwoNotional.sub(assetTwoFee)); - setToken.invokeTransfer(address(assetTwo), feeRecipient, assetTwoFee); - } - - function initialize( - ISetToken _setToken, - uint256 _reservePercentage, - uint256 _slippageTolerance, - uint256 _rewardFee, - uint256 _withdrawalFee - ) - external - onlySetManager(_setToken, msg.sender) - { - require(address(setToken) == address(0), "May only be called once"); - - setToken = _setToken; - reservePercentage = _reservePercentage; - slippageTolerance = _slippageTolerance; - rewardFee = _rewardFee; - withdrawalFee = _withdrawalFee; - - _setToken.initializeModule(); - } - - function removeModule() external override { - require(msg.sender == address(setToken), "Caller must be SetToken"); - - uint256 lpBalance = rewarder.balanceOf(address(setToken)); - - _unstake(lpBalance); - - _approveAndRemoveLiquidity(lpBalance); - - _updatePositions(); - } - - /* ============ Internal Functions ============ */ - - function _engage() internal { - _rebalance(0); - - (uint256 assetOneQuantity, uint256 assetTwoQuantity) = _calculateEngageQuantities(); - - uint256 lpBalance = _approveAndAddLiquidity(assetOneQuantity, assetTwoQuantity); - - _approveAndStake(lpBalance); - - _updatePositions(); - } - - // Rebalances reserve assets to achieve a 50/50 value split - // If a sellTokenQuantity is provided, then use this value - function _rebalance(uint256 _sellTokenQuantity) internal { - address assetToSell; - address assetToBuy; - uint256 quantityToSell; - uint256 minimumBuyToken; - - uint256 assetOneToTwoPrice = controller.getPriceOracle().getPrice(address(assetOne), address(assetTwo)); - - uint256 balanceAssetOne = assetOne.balanceOf(address(setToken)); - uint256 balanceAssetTwo = assetTwo.balanceOf(address(setToken)); - - // Convert Asset Two to One adjust for decimal differences - uint256 valueAssetTwoDenomOne = balanceAssetTwo.preciseDiv(assetOneToTwoPrice).mul(assetOneBaseUnit).div(assetTwoBaseUnit); - - if (balanceAssetOne > valueAssetTwoDenomOne) { - assetToSell = address(assetOne); - assetToBuy = address(assetTwo); - quantityToSell = balanceAssetOne.sub(valueAssetTwoDenomOne).div(2); - - // Base unit calculations are to normalize the values for different decimals - minimumBuyToken = quantityToSell.preciseMul(assetOneToTwoPrice).mul(assetTwoBaseUnit).div(assetOneBaseUnit); - } else { - assetToSell = address(assetTwo); - assetToBuy = address(assetOne); - quantityToSell = valueAssetTwoDenomOne - .sub(balanceAssetOne).div(2).preciseMul(assetOneToTwoPrice) - .mul(assetTwoBaseUnit).div(assetOneBaseUnit); - minimumBuyToken = quantityToSell.preciseDiv(assetOneToTwoPrice).mul(assetOneBaseUnit).div(assetTwoBaseUnit); - } - - if (_sellTokenQuantity > 0) { - require(_sellTokenQuantity <= quantityToSell, "Delta must be less than max"); - minimumBuyToken = minimumBuyToken.preciseMul(_sellTokenQuantity).preciseDiv(quantityToSell); - quantityToSell = _sellTokenQuantity; - } - - // Reduce the expected receive quantity - minimumBuyToken = minimumBuyToken - .preciseMul(PreciseUnitMath.preciseUnit().sub(slippageTolerance)); - - setToken.invokeApprove(assetToSell, address(uniswapRouter), quantityToSell); - if (quantityToSell > 0) { - _invokeUniswapTrade(assetToSell, assetToBuy, quantityToSell, minimumBuyToken); - } - } - - function _approveAndAddLiquidity(uint256 _assetOneQuantity, uint256 _assetTwoQuantity) internal returns (uint256) { - setToken.invokeApprove(address(assetOne), address(uniswapRouter), _assetOneQuantity); - setToken.invokeApprove(address(assetTwo), address(uniswapRouter), _assetTwoQuantity); - - bytes memory addLiquidityBytes = abi.encodeWithSignature( - "addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256)", - assetOne, - assetTwo, - _assetOneQuantity, - _assetTwoQuantity, - 1, - 1, - address(setToken), - now.add(60) // Valid for one minute - ); - - setToken.invoke(address(uniswapRouter), 0, addLiquidityBytes); - - return lpToken.balanceOf(address(setToken)); - } - - function _approveAndRemoveLiquidity(uint256 _liquidityQuantity) internal { - setToken.invokeApprove(address(lpToken), address(uniswapRouter), _liquidityQuantity); - - bytes memory removeLiquidityBytes = abi.encodeWithSignature( - "removeLiquidity(address,address,uint256,uint256,uint256,address,uint256)", - assetOne, - assetTwo, - _liquidityQuantity, - 1, - 1, - address(setToken), - now.add(60) // Valid for one minute - ); - - setToken.invoke(address(uniswapRouter), 0, removeLiquidityBytes); - } - - function _approveAndStake(uint256 _lpTokenQuantity) internal { - setToken.invokeApprove(address(lpToken), address(rewarder), _lpTokenQuantity); - bytes memory stakeBytes = abi.encodeWithSignature("stake(uint256)", _lpTokenQuantity); - - setToken.invoke(address(rewarder), 0, stakeBytes); - } - - function _unstake(uint256 _lpTokenQuantity) internal { - bytes memory unstakeBytes = abi.encodeWithSignature("withdraw(uint256)", _lpTokenQuantity); - - setToken.invoke(address(rewarder), 0, unstakeBytes); - } - - function _handleReward() internal { - setToken.invoke(address(rewarder), 0, abi.encodeWithSignature("getReward()")); - - uint256 uniBalance = uni.balanceOf(address(setToken)); - uint256 assetOneBalance = assetOne.balanceOf(address(setToken)); - - setToken.invokeApprove(address(uni), address(uniswapRouter), uniBalance); - _invokeUniswapTrade(address(uni), address(assetOne), uniBalance, 1); - - uint256 postTradeAssetOneBalance = assetOne.balanceOf(address(setToken)); - uint256 fee = postTradeAssetOneBalance.sub(assetOneBalance).preciseMul(rewardFee); - - setToken.strictInvokeTransfer(address(assetOne), feeRecipient, fee); - } - - function _invokeUniswapTrade( - address _sellToken, - address _buyToken, - uint256 _amountIn, - uint256 _amountOutMin - ) - internal - { - address[] memory path = new address[](2); - path[0] = _sellToken; - path[1] = _buyToken; - - bytes memory tradeBytes = abi.encodeWithSignature( - "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", - _amountIn, - _amountOutMin, - path, - address(setToken), - now.add(180) - ); - - setToken.invoke(address(uniswapRouter), 0, tradeBytes); - } - - function _calculateEngageQuantities() internal view returns(uint256 tokenAQuantity, uint256 tokenBQuantity) { - ( - uint256 desiredAssetOne, - uint256 desiredAssetTwo, - uint256 assetOneOnSetToken, - uint256 assetTwoOnSetToken - ) = _getDesiredSingleAssetReserve(); - - require(assetOneOnSetToken > desiredAssetOne && assetTwoOnSetToken > desiredAssetTwo, "SetToken assets must be > desired"); - - return ( - assetOneOnSetToken.sub(desiredAssetOne), - assetTwoOnSetToken.sub(desiredAssetTwo) - ); - } - - function _calculateDisengageLPQuantity() internal view returns(uint256 _lpTokenQuantity) { - (uint256 assetOneToLPRate, uint256 assetTwoToLPRate) = _getLPReserveExchangeRate(); - - ( - uint256 desiredOne, - uint256 desiredTwo, - uint256 assetOneOnSetToken, - uint256 assetTwoOnSetToken - ) = _getDesiredSingleAssetReserve(); - - require(assetOneOnSetToken < desiredOne && assetTwoOnSetToken < desiredTwo, "SetToken assets must be < desired"); - - // LP Rates already account for decimals - uint256 minLPForOneToRedeem = desiredOne.sub(assetOneOnSetToken).preciseDiv(assetOneToLPRate); - uint256 minLPForTwoToRedeem = desiredTwo.sub(assetTwoOnSetToken).preciseDiv(assetTwoToLPRate); - - return Math.max(minLPForOneToRedeem, minLPForTwoToRedeem); - } - - // Returns desiredOneReserve, desiredTwoReserve, tokenOne and tokenTwo balances - function _getDesiredSingleAssetReserve() - internal - view - returns(uint256, uint256, uint256, uint256) - { - (uint256 assetOneReserve, uint256 assetTwoReserve) = _getTotalLPReserves(); - uint256 balanceAssetOne = assetOne.balanceOf(address(setToken)); - uint256 balanceAssetTwo = assetTwo.balanceOf(address(setToken)); - - uint256 desiredOneReserve = assetOneReserve.add(balanceAssetOne).preciseMul(reservePercentage); - uint256 desiredTwoReserve = assetTwoReserve.add(balanceAssetTwo).preciseMul(reservePercentage); - - return(desiredOneReserve, desiredTwoReserve, balanceAssetOne, balanceAssetTwo); - } - - // Returns assetAToLPRate and assetBToLPRate - function _getLPReserveExchangeRate() internal view returns (uint256, uint256) { - (uint reserve0, uint reserve1) = _getReservesSafe(); - uint256 totalSupply = lpToken.totalSupply(); - return( - reserve0.preciseDiv(totalSupply), - reserve1.preciseDiv(totalSupply) - ); - } - - // Returns assetOneReserve and assetTwoReserve - function _getTotalLPReserves() internal view returns (uint256, uint256) { - (uint reserve0, uint reserve1) = _getReservesSafe(); - uint256 totalSupply = lpToken.totalSupply(); - uint256 lpTokenBalance = rewarder.balanceOf(address(setToken)); - return( - reserve0.mul(lpTokenBalance).div(totalSupply), - reserve1.mul(lpTokenBalance).div(totalSupply) - ); - } - - function _updatePositions() internal { - uint256 totalSupply = setToken.totalSupply(); - uint256 assetOneBalance = assetOne.balanceOf(address(setToken)); - uint256 assetTwoBalance = assetTwo.balanceOf(address(setToken)); - uint256 lpBalance = rewarder.balanceOf(address(setToken)); - - // Doesn't check to make sure unit is different, and no check for any LP token on Set - setToken.editDefaultPosition(address(assetOne), Position.getDefaultPositionUnit(totalSupply, assetOneBalance)); - setToken.editDefaultPosition(address(assetTwo), Position.getDefaultPositionUnit(totalSupply, assetTwoBalance)); - setToken.editExternalPosition( - address(lpToken), - address(this), - Position.getDefaultPositionUnit(totalSupply, lpBalance).toInt256(), - "" - ); - } - - // Code pulled to sort from UniswapV2Library - // https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol - function _getReservesSafe() internal view returns(uint256, uint256) { - address firstAsset = address(assetOne) < address(assetTwo) ? address(assetOne) : address(assetTwo); - (uint reserve0, uint reserve1,) = lpToken.getReserves(); - return address(assetOne) == firstAsset ? (reserve0, reserve1) : (reserve1, reserve0); - } -} \ No newline at end of file diff --git a/test/protocol/modules/uniswapYieldStrategy.spec.ts b/test/protocol/modules/uniswapYieldStrategy.spec.ts deleted file mode 100644 index f9d57843c..000000000 --- a/test/protocol/modules/uniswapYieldStrategy.spec.ts +++ /dev/null @@ -1,715 +0,0 @@ -import "module-alias/register"; -import { BigNumber } from "@ethersproject/bignumber"; - -import { Address, NAVIssuanceSettings } from "@utils/types"; -import { Account } from "@utils/test/types"; -import { GodModeMock, UniswapPairPriceAdapter, UniswapYieldStrategy, SetToken } from "@utils/contracts"; -import { MAX_UINT_256, ONE_HOUR_IN_SECONDS } from "@utils/constants"; -import DeployHelper from "@utils/deploys"; -import { - calculateRebalanceFlows, - calculateRebalanceQuantity, - calculateTokensInReserve, - ether, - min, - preciseMul, - preciseDiv, -} from "@utils/index"; -import { - addSnapshotBeforeRestoreAfterEach, - getAccounts, - getSystemFixture, - getWaffleExpect, - getUniswapFixture, - increaseTimeAsync, -} from "@utils/test/index"; -import { SystemFixture, UniswapFixture } from "@utils/fixtures"; -import { ContractTransaction } from "ethers"; -import { ADDRESS_ZERO, ZERO } from "@utils/constants"; - -const expect = getWaffleExpect(); - -/* - * Due to one off nature of module and flaky coverage tests, tests for disengage and unstakeAndRedeem - * are commented out. The module has successfully run in production from three weeks so feel confident - * of it's correctness. - */ - -describe("UniswapYieldStrategy", () => { - let owner: Account; - let manager: Account; - let feeRecipient: Account; - - let deployer: DeployHelper; - let setup: SystemFixture; - let uniswapSetup: UniswapFixture; - let uniswapPriceAdapter: UniswapPairPriceAdapter; - - let setToken: SetToken; - let yieldStrategy: UniswapYieldStrategy; - let godModule: GodModeMock; - - const reservePercentage = ether(.01); - const slippageTolerance = ether(.02); - const rewardFee = ether(.05); - const withdrawalFee = ether(.01); - - before(async () => { - [ - owner, - manager, - feeRecipient, - ] = await getAccounts(); - - deployer = new DeployHelper(owner.wallet); - setup = getSystemFixture(owner.address); - uniswapSetup = getUniswapFixture(owner.address); - - await setup.initialize(); - await uniswapSetup.initialize( - owner, - setup.weth.address, - setup.wbtc.address, - setup.dai.address - ); - - uniswapPriceAdapter = await deployer.adapters.deployUniswapPairPriceAdapter( - setup.controller.address, - uniswapSetup.factory.address, - [uniswapSetup.wethDaiPool.address, uniswapSetup.wethWbtcPool.address] - ); - - await setup.controller.addResource(uniswapPriceAdapter.address, BigNumber.from(3)); - await setup.priceOracle.addAdapter(uniswapPriceAdapter.address); - - yieldStrategy = await deployer.modules.deployUniswapYieldStrategy( - setup.controller.address, - uniswapSetup.router.address, - uniswapSetup.wethDaiPool.address, - setup.weth.address, - setup.dai.address, - uniswapSetup.uni.address, - uniswapSetup.wethDaiStakingRewards.address, - feeRecipient.address - ); - - godModule = await deployer.mocks.deployGodModeMock(setup.controller.address); - - await setup.controller.addModule(godModule.address); - await setup.controller.addModule(yieldStrategy.address); - - setToken = await setup.createSetToken( - [setup.weth.address, setup.dai.address], - [ether(1), preciseMul(setup.component1Price, ether(.99))], - [setup.navIssuanceModule.address, setup.issuanceModule.address, yieldStrategy.address, godModule.address], - manager.address - ); - - const navIssueSettings: NAVIssuanceSettings = { - managerIssuanceHook: ADDRESS_ZERO, - managerRedemptionHook: ADDRESS_ZERO, - reserveAssets: [setup.weth.address, setup.dai.address], - feeRecipient: feeRecipient.address, - managerFees: [ZERO, ether(.02)], - maxManagerFee: ether(.02), - premiumPercentage: ether(.01), - maxPremiumPercentage: ether(.01), - minSetTokenSupply: ether(1000), - }; - await setup.navIssuanceModule.connect(manager.wallet).initialize( - setToken.address, - navIssueSettings - ); - await godModule.initialize(setToken.address); - await setup.issuanceModule.connect(manager.wallet).initialize(setToken.address, ADDRESS_ZERO); - - await baseTestSetup(); - }); - - addSnapshotBeforeRestoreAfterEach(); - - describe("#engage", async () => { - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).engage(); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - await subject(); - - const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - setToken, - setup.weth, - setup.dai, - uniswapSetup.wethDaiPool, - uniswapSetup.wethDaiStakingRewards - ); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - expect(wethReserveRatio).to.be.gte(reservePercentage); - expect(daiReserveRatio).to.be.gte(reservePercentage); - expect(postLPBalance).to.be.eq(ZERO); - expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - }); - - it("should update to the correct position units", async () => { - await subject(); - - const wethBalance = await setup.weth.balanceOf(setToken.address); - const daiBalance = await setup.dai.balanceOf(setToken.address); - const lpBalance = await uniswapSetup.wethDaiStakingRewards.balanceOf(setToken.address); - const totalSupply = await setToken.totalSupply(); - - const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - const lpPositionUnit = await setToken.getExternalPositionRealUnit( - uniswapSetup.wethDaiPool.address, - yieldStrategy.address - ); - - expect(wethPositionUnit).to.eq(preciseDiv(wethBalance, totalSupply)); - expect(daiPositionUnit).to.eq(preciseDiv(daiBalance, totalSupply)); - expect(lpPositionUnit).to.eq(preciseDiv(lpBalance, totalSupply)); - }); - - describe("when tokens in Set don't exceed reserve amounts", async () => { - beforeEach(async () => { - await subject(); - - await godModule.transferTokens(setToken.address, setup.dai.address, owner.address, ether(2500)); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("SetToken assets must be > desired"); - }); - }); - }); - - // describe("#disengage", async () => { - // let subjectCaller: Account; - - // beforeEach(async () => { - // await yieldStrategy.connect(manager.wallet).initialize( - // setToken.address, - // reservePercentage, - // slippageTolerance, - // rewardFee, - // withdrawalFee - // ); - - // await yieldStrategy.connect(feeRecipient.wallet).engage(); - // await godModule.transferTokens(setToken.address, setup.dai.address, owner.address, ether(2500)); - - // subjectCaller = manager; - // }); - - // async function subject(): Promise { - // return await yieldStrategy.connect(subjectCaller.wallet).disengage(); - // } - - // it("should have the correct amount of weth and dai in the Set", async () => { - // await subject(); - - // const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - // setToken, - // setup.weth, - // setup.dai, - // uniswapSetup.wethDaiPool, - // uniswapSetup.wethDaiStakingRewards - // ); - - // const postWethBalance = await setup.weth.balanceOf(setToken.address); - // const postDaiBalance = await setup.dai.balanceOf(setToken.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - // const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - // const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - // expect(wethReserveRatio).to.be.gte(reservePercentage); - // expect(daiReserveRatio).to.be.gte(reservePercentage); - // expect(postLPBalance).to.be.eq(ZERO); - // expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - // }); - - // describe("when tokens in Set exceed reserve amounts", async () => { - // beforeEach(async () => { - // await setup.dai.connect(owner.wallet).transfer(setToken.address, ether(2500)); - // }); - - // it("should revert", async () => { - // await expect(subject()).to.be.revertedWith("SetToken assets must be < desired"); - // }); - // }); - // }); - - describe("#reap", async () => { - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - await yieldStrategy.connect(feeRecipient.wallet).engage(); - - await increaseTimeAsync(ONE_HOUR_IN_SECONDS.div(24)); - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).reap(); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - await subject(); - - const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - setToken, - setup.weth, - setup.dai, - uniswapSetup.wethDaiPool, - uniswapSetup.wethDaiStakingRewards - ); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - expect(wethReserveRatio).to.be.gte(reservePercentage); - expect(daiReserveRatio).to.be.gte(reservePercentage); - expect(postLPBalance).to.be.eq(ZERO); - expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - }); - }); - - describe("#rebalance", async () => { - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).rebalance(); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - const [wethRebalanceAmount, expectedReceiveQuantity] = await calculateRebalanceFlows( - setToken, - uniswapSetup.router, - ZERO, - setup.weth, - setup.dai, - await setup.ETH_USD_Oracle.read(), - ); - - const preWethBalance = await setup.weth.balanceOf(setToken.address); - const preDaiBalance = await setup.dai.balanceOf(setToken.address); - - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - - expect(postWethBalance).to.eq(preWethBalance.sub(wethRebalanceAmount)); - expect(postDaiBalance).to.eq(preDaiBalance.add(expectedReceiveQuantity)); - }); - - it("should update to the correct position units", async () => { - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const totalSupply = await setToken.totalSupply(); - - const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - - expect(wethPositionUnit).to.eq(preciseDiv(postWethBalance, totalSupply)); - expect(daiPositionUnit).to.eq(preciseDiv(postDaiBalance, totalSupply)); - }); - }); - - describe("#rebalanceSome", async () => { - let wethRebalanceAmount: BigNumber; - - let subjectUSDDifference: BigNumber; - let subjectCaller: Account; - - beforeEach(async () => { - await yieldStrategy.connect(manager.wallet).initialize( - setToken.address, - reservePercentage, - slippageTolerance, - rewardFee, - withdrawalFee - ); - - [ wethRebalanceAmount, , ] = await calculateRebalanceQuantity( - ZERO, - setToken, - setup.weth, - setup.dai, - await setup.ETH_USD_Oracle.read(), - ); - - subjectUSDDifference = wethRebalanceAmount.div(2); - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).rebalanceSome(subjectUSDDifference); - } - - it("should have the correct amount of weth and dai in the Set", async () => { - const [, expectedReceiveQuantity] = await uniswapSetup.router.getAmountsOut( - subjectUSDDifference, - [setup.weth.address, setup.dai.address] - ); - const preWethBalance = await setup.weth.balanceOf(setToken.address); - const preDaiBalance = await setup.dai.balanceOf(setToken.address); - - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - - expect(postWethBalance).to.eq(preWethBalance.sub(subjectUSDDifference)); - expect(postDaiBalance).to.eq(preDaiBalance.add(expectedReceiveQuantity)); - }); - - it("should update to the correct position units", async () => { - await subject(); - - const postWethBalance = await setup.weth.balanceOf(setToken.address); - const postDaiBalance = await setup.dai.balanceOf(setToken.address); - const totalSupply = await setToken.totalSupply(); - - const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - - expect(wethPositionUnit).to.eq(preciseDiv(postWethBalance, totalSupply)); - expect(daiPositionUnit).to.eq(preciseDiv(postDaiBalance, totalSupply)); - }); - - describe("when rebalancing more than allowed", async () => { - beforeEach(async () => { - subjectUSDDifference = wethRebalanceAmount.add(1); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Delta must be less than max"); - }); - }); - }); - - // describe("#unstakeAndRedeem", async () => { - // let subjectSetTokenQuantity: BigNumber; - // let subjectCaller: Account; - - // beforeEach(async () => { - // await yieldStrategy.connect(manager.wallet).initialize( - // setToken.address, - // reservePercentage, - // slippageTolerance, - // rewardFee, - // withdrawalFee - // ); - - // await yieldStrategy.connect(feeRecipient.wallet).engage(); - - // subjectSetTokenQuantity = ether(100); - // subjectCaller = owner; - // }); - - // async function subject(): Promise { - // return await yieldStrategy.connect(subjectCaller.wallet).unstakeAndRedeem(subjectSetTokenQuantity); - // } - - // it("should have the correct amount of weth and dai in the Set", async () => { - // await subject(); - - // const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - // setToken, - // setup.weth, - // setup.dai, - // uniswapSetup.wethDaiPool, - // uniswapSetup.wethDaiStakingRewards - // ); - - // const postWethBalance = await setup.weth.balanceOf(setToken.address); - // const postDaiBalance = await setup.dai.balanceOf(setToken.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - - // const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - // const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - // expect(wethReserveRatio).to.be.gte(reservePercentage); - // expect(daiReserveRatio).to.be.gte(reservePercentage); - // expect(postLPBalance).to.be.eq(ZERO); - // expect(min(wethReserveRatio, daiReserveRatio)).to.eq(reservePercentage); - // }); - - // it("should send the correct amounts to the redeemer", async () => { - // const preWethBalance = await setup.weth.balanceOf(owner.address); - // const preDaiBalance = await setup.dai.balanceOf(owner.address); - // const preLPBalance = await uniswapSetup.wethDaiPool.balanceOf(owner.address); - - // await subject(); - - // const postWethBalance = await setup.weth.balanceOf(owner.address); - // const postDaiBalance = await setup.dai.balanceOf(owner.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(owner.address); - - // const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - // const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - // const lpPositionUnit = await setToken.getExternalPositionRealUnit(yieldStrategy.address, uniswapSetup.wethDaiPool.address); - - // expect(postWethBalance).to.be.gte( - // preWethBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(wethPositionUnit, PRECISE_UNIT.sub(withdrawalFee)))) - // ); - // expect(postDaiBalance).to.be.gte( - // preDaiBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(daiPositionUnit, PRECISE_UNIT.sub(withdrawalFee)))) - // ); - // expect(postLPBalance).to.be.gte( - // preLPBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(lpPositionUnit, PRECISE_UNIT.sub(withdrawalFee)))) - // ); - // }); - - // it("should send the correct amounts to the feeRecipient", async () => { - // const preWethBalance = await setup.weth.balanceOf(feeRecipient.address); - // const preDaiBalance = await setup.dai.balanceOf(feeRecipient.address); - // const preLPBalance = await uniswapSetup.wethDaiPool.balanceOf(feeRecipient.address); - - // await subject(); - - // const postWethBalance = await setup.weth.balanceOf(feeRecipient.address); - // const postDaiBalance = await setup.dai.balanceOf(feeRecipient.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(feeRecipient.address); - - // const wethPositionUnit = await setToken.getDefaultPositionRealUnit(setup.weth.address); - // const daiPositionUnit = await setToken.getDefaultPositionRealUnit(setup.dai.address); - // const lpPositionUnit = await setToken.getExternalPositionRealUnit(yieldStrategy.address, uniswapSetup.wethDaiPool.address); - - // expect(postWethBalance).to.be.gte( - // preWethBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(wethPositionUnit, withdrawalFee))) - // ); - // expect(postDaiBalance).to.be.gte( - // preDaiBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(daiPositionUnit, withdrawalFee))) - // ); - // expect(postLPBalance).to.be.gte( - // preLPBalance.add(preciseMul(subjectSetTokenQuantity, preciseMul(lpPositionUnit, withdrawalFee))) - // ); - // }); - - // describe("when user tries to redeem more than their share", async () => { - // beforeEach(async () => { - // subjectSetTokenQuantity = ether(2000); - // }); - - // it("should revert", async () => { - // await expect(subject()).to.be.revertedWith("User must have sufficient SetToken"); - // }); - // }); - // }); - - describe("#initialize", async () => { - let subjectSetToken: Address; - let subjectReservePercentage: BigNumber; - let subjectSlippageTolerance: BigNumber; - let subjectRewardFee: BigNumber; - let subjectWithdrawalFee: BigNumber; - let subjectCaller: Account; - - beforeEach(async () => { - subjectSetToken = setToken.address; - subjectReservePercentage = reservePercentage; - subjectSlippageTolerance = slippageTolerance; - subjectRewardFee = rewardFee; - subjectWithdrawalFee = withdrawalFee; - subjectCaller = manager; - }); - - async function subject(): Promise { - return await yieldStrategy.connect(subjectCaller.wallet).initialize( - subjectSetToken, - subjectReservePercentage, - subjectSlippageTolerance, - subjectRewardFee, - subjectWithdrawalFee - ); - } - - it("should set the correct parameters", async () => { - await subject(); - - const receivedSetToken = await yieldStrategy.setToken(); - const reservePercentage = await yieldStrategy.reservePercentage(); - const slippageTolerance = await yieldStrategy.slippageTolerance(); - const rewardFee = await yieldStrategy.rewardFee(); - const withdrawalFee = await yieldStrategy.withdrawalFee(); - - expect(receivedSetToken).to.eq(subjectSetToken); - expect(reservePercentage).to.eq(subjectReservePercentage); - expect(slippageTolerance).to.eq(subjectSlippageTolerance); - expect(rewardFee).to.eq(subjectRewardFee); - expect(withdrawalFee).to.eq(subjectWithdrawalFee); - }); - - it("should enable the Module on the SetToken", async () => { - await subject(); - - const isModuleEnabled = await setToken.isInitializedModule(yieldStrategy.address); - expect(isModuleEnabled).to.be.true; - }); - - describe("when initialize has already been called", async () => { - beforeEach(async () => { - await subject(); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("May only be called once"); - }); - }); - - describe("when caller is not Set manager", async () => { - beforeEach(async () => { - subjectCaller = owner; - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("Must be the SetToken manager"); - }); - }); - }); - - // describe("#removeModule", async () => { - // let subjectModule: Address; - // let subjectCaller: Account; - - // beforeEach(async () => { - // await yieldStrategy.connect(manager.wallet).initialize( - // setToken.address, - // reservePercentage, - // slippageTolerance, - // rewardFee, - // withdrawalFee - // ); - - // await yieldStrategy.connect(feeRecipient.wallet).engage(); - - // subjectModule = yieldStrategy.address; - // subjectCaller = manager; - // }); - - // async function subject(): Promise { - // return await setToken.connect(subjectCaller.wallet).removeModule(subjectModule); - // } - - // async function subjectAttacker(): Promise { - // return await yieldStrategy.connect(subjectCaller.wallet).removeModule(); - // } - - // it("should only have ETH and DAI positions open", async () => { - // await subject(); - - // const [wethInReserve, daiInReserve] = await calculateTokensInReserve( - // setToken, - // setup.weth, - // setup.dai, - // uniswapSetup.wethDaiPool, - // uniswapSetup.wethDaiStakingRewards - // ); - - // const postWethBalance = await setup.weth.balanceOf(setToken.address); - // const postDaiBalance = await setup.dai.balanceOf(setToken.address); - // const postLPBalance = await uniswapSetup.wethDaiPool.balanceOf(setToken.address); - // const postLPStakingBalance = await uniswapSetup.wethDaiStakingRewards.balanceOf(setToken.address); - - // const wethReserveRatio = preciseDiv(postWethBalance, postWethBalance.add(wethInReserve)); - // const daiReserveRatio = preciseDiv(postDaiBalance, postDaiBalance.add(daiInReserve)); - - // expect(wethReserveRatio).to.eq(PRECISE_UNIT); - // expect(daiReserveRatio).to.eq(PRECISE_UNIT); - // expect(postLPBalance).to.be.eq(ZERO); - // expect(postLPStakingBalance).to.be.eq(ZERO); - // }); - - // describe("when caller is not SetToken", async () => { - // beforeEach(async () => { - // subjectCaller = manager; - // }); - - // it("should revert", async () => { - // await expect(subjectAttacker()).to.be.revertedWith("Caller must be SetToken"); - // }); - // }); - // }); - - async function baseTestSetup(): Promise { - await setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, ether(1000)); - await setup.dai.connect(owner.wallet).approve(uniswapSetup.router.address, ether(350000)); - await uniswapSetup.router.addLiquidity( - setup.weth.address, - setup.dai.address, - ether(1000), - ether(230000), - ether(999), - ether(225000), - owner.address, - MAX_UINT_256 - ); - - await setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, ether(2000)); - await uniswapSetup.uni.connect(owner.wallet).approve(uniswapSetup.router.address, ether(400000)); - await uniswapSetup.router.connect(owner.wallet).addLiquidity( - setup.weth.address, - uniswapSetup.uni.address, - ether(2000), - ether(400000), - ether(1485), - ether(173000), - owner.address, - MAX_UINT_256 - ); - - await setup.weth.connect(owner.wallet).approve(setup.issuanceModule.address, ether(1000)); - await setup.dai.connect(owner.wallet).approve(setup.issuanceModule.address, ether(350000)); - await setup.issuanceModule.connect(owner.wallet).issue(setToken.address, ether(1000), owner.address); - } -}); \ No newline at end of file diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index 78832a5db..d6faf8ad3 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -83,7 +83,6 @@ export { UniswapV2Factory } from "../../typechain/UniswapV2Factory"; export { UniswapV2Pair } from "../../typechain/UniswapV2Pair"; export { UniswapV2Router02 } from "../../typechain/UniswapV2Router02"; export { UniswapYieldHook } from "../../typechain/UniswapYieldHook"; -export { UniswapYieldStrategy } from "../../typechain/UniswapYieldStrategy"; export { WETH9 } from "../../typechain/WETH9"; export { WrapAdapterMock } from "../../typechain/WrapAdapterMock"; export { WrapModule } from "../../typechain/WrapModule"; diff --git a/utils/deploys/deployModules.ts b/utils/deploys/deployModules.ts index 46a50b841..1b488159d 100644 --- a/utils/deploys/deployModules.ts +++ b/utils/deploys/deployModules.ts @@ -18,9 +18,8 @@ import { StakingModule, StreamingFeeModule, TradeModule, - UniswapYieldStrategy, UniswapV2ExchangeAdapterV2, - WrapModule + WrapModule } from "../contracts"; import { Address } from "../types"; @@ -40,7 +39,6 @@ import { SingleIndexModule__factory } from "../../typechain/factories/SingleInde import { StakingModule__factory } from "../../typechain/factories/StakingModule__factory"; import { StreamingFeeModule__factory } from "../../typechain/factories/StreamingFeeModule__factory"; import { TradeModule__factory } from "../../typechain/factories/TradeModule__factory"; -import { UniswapYieldStrategy__factory } from "../../typechain/factories/UniswapYieldStrategy__factory"; import { UniswapV2ExchangeAdapterV2__factory } from "../../typechain/factories/UniswapV2ExchangeAdapterV2__factory"; import { WrapModule__factory } from "../../typechain/factories/WrapModule__factory"; @@ -107,32 +105,6 @@ export default class DeployModules { return await new CustomOracleNavIssuanceModule__factory(this._deployerSigner).deploy(controller, weth); } - public async deployUniswapYieldStrategy( - _controller: Address, - _uniswapRouter: Address, - _lpToken: Address, - _assetOne: Address, - _assetTwo: Address, - _uni: Address, - _rewarder: Address, - _feeRecipient: Address - ): Promise { - return await new UniswapYieldStrategy__factory(this._deployerSigner).deploy( - _controller, - _uniswapRouter, - _lpToken, - _assetOne, - _assetTwo, - _uni, - _rewarder, - _feeRecipient - ); - } - - public async getUniswapYieldStrategy(uniswapYieldStrategy: Address): Promise { - return await new UniswapYieldStrategy__factory(this._deployerSigner).attach(uniswapYieldStrategy); - } - public async getNavIssuanceModule(navIssuanceModule: Address): Promise { return await new NavIssuanceModule__factory(this._deployerSigner).attach(navIssuanceModule); } @@ -172,7 +144,7 @@ export default class DeployModules { public async deployBalancerV1ExchangeAdapter(balancerProxy: Address): Promise { return await new BalancerV1ExchangeAdapter__factory(this._deployerSigner).deploy( balancerProxy - ) + ); } public async deployGovernanceModule(controller: Address): Promise {