diff --git a/contracts/interfaces/external/IStableSwapStEth.sol b/contracts/interfaces/external/IStableSwapPool.sol similarity index 92% rename from contracts/interfaces/external/IStableSwapStEth.sol rename to contracts/interfaces/external/IStableSwapPool.sol index ee5ea3ca0..7611871a3 100644 --- a/contracts/interfaces/external/IStableSwapStEth.sol +++ b/contracts/interfaces/external/IStableSwapPool.sol @@ -15,9 +15,10 @@ pragma solidity 0.6.10; /** - * Curve StableSwap pool for stETH. + * Curve StableSwap ERC20 <-> ERC20 pool. */ -interface IStableSwapStEth { +interface IStableSwapPool { + function exchange( int128 i, int128 j, diff --git a/contracts/mocks/external/CurveStEthStableswapMock.sol b/contracts/mocks/external/CurveStableswapMock.sol similarity index 69% rename from contracts/mocks/external/CurveStEthStableswapMock.sol rename to contracts/mocks/external/CurveStableswapMock.sol index 7b697e0c3..51506952b 100644 --- a/contracts/mocks/external/CurveStEthStableswapMock.sol +++ b/contracts/mocks/external/CurveStableswapMock.sol @@ -24,8 +24,10 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.s import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -// Minimal Curve Eth/StEth Stableswap Pool -contract CurveStEthStableswapMock is ReentrancyGuard { +// Minimal Curve Stableswap Pool +contract CurveStableswapMock is ReentrancyGuard { + + address public constant ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; using SafeERC20 for IERC20; using SafeMath for uint256; @@ -35,13 +37,20 @@ contract CurveStEthStableswapMock is ReentrancyGuard { address[] tokens; constructor(address[] memory _tokens) public { - require(_tokens[1] != address(0)); + for (uint i = 0; i < _tokens.length; i++) { + require(_tokens[i] != address(0)); + } tokens = _tokens; } function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount) payable external nonReentrant returns (uint256) { - require(_amounts[0] == msg.value, "Eth sent should equal amount"); - IERC20(tokens[1]).safeTransferFrom(msg.sender, address(this), _amounts[1]); + for (uint i = 0; i < _amounts.length; i++) { + if (tokens[i] == ETH_TOKEN_ADDRESS) { + require(_amounts[i] == msg.value, "Eth sent should equal amount"); + continue; + } + IERC20(tokens[i]).safeTransferFrom(msg.sender, address(this), _amounts[i]); + } return _min_mint_amount; } @@ -56,18 +65,19 @@ contract CurveStEthStableswapMock is ReentrancyGuard { function exchange(int128 _i, int128 _j, uint256 _dx, uint256 _min_dy) payable external nonReentrant returns (uint256) { require(_i != _j); require(_dx == _min_dy); - if (_i == 0 && _j == 1) { - // The caller has sent eth receive stETH - require(_dx == msg.value); - IERC20(tokens[1]).safeTransfer(msg.sender, _dx); - } else if (_j == 0 && _i == 1) { - // The caller has sent stETH to receive ETH - IERC20(tokens[1]).safeTransferFrom(msg.sender, address(this), _dx); - Address.sendValue(msg.sender, _dx); + + if (tokens[uint256(_i)] == ETH_TOKEN_ADDRESS) { + require(_dx == msg.value); + } else { + IERC20(tokens[uint256(_i)]).transferFrom(msg.sender, address(this), _dx); + } + + if (tokens[uint256(_j)] == ETH_TOKEN_ADDRESS) { + Address.sendValue(payable(msg.sender), _min_dy); } else { - revert("Invalid index values"); + IERC20(tokens[uint256(_j)]).transfer(msg.sender, _min_dy); } - return _dx; + return _min_dy; } /** diff --git a/contracts/protocol/integration/exchange/CurveExchangeAdapter.sol b/contracts/protocol/integration/exchange/CurveExchangeAdapter.sol new file mode 100644 index 000000000..38be10111 --- /dev/null +++ b/contracts/protocol/integration/exchange/CurveExchangeAdapter.sol @@ -0,0 +1,178 @@ +/* + Copyright 2022 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 { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { IStableSwapPool } from "../../../interfaces/external/IStableSwapPool.sol"; +import { IWETH } from "../../../interfaces/external/IWETH.sol"; +import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol"; + +/** + * @title CurveExchangeAdapter + * @author FlattestWhite + * + * Exchange adapter for Curve pools for ERC20 <-> ERC20 + * exchange contracts. This contract assumes that all tokens + * being traded are ERC20 tokens. + * + * This contract is intended to be used by trade modules to rebalance + * SetTokens that hold ERC20 tokens as part of its components. + */ +contract CurveExchangeAdapter { + + using SafeMath for uint256; + using PreciseUnitMath for uint256; + + /* ========= State Variables ========= */ + + // Address of ERC20 tokenA + IERC20 immutable public tokenA; + // Address of ERC20 tokenB + IERC20 immutable public tokenB; + // Index of tokenA + int128 immutable public tokenAIndex; + // Index of tokenB + int128 immutable public tokenBIndex; + // Address of Curve tokenA/tokenB stableswap pool. + IStableSwapPool immutable public stableswap; + + /* ========= Constructor ========== */ + + /** + * Set state variables + * + * @param _tokenA Address of tokenA + * @param _tokenB Address of tokenB + * @param _tokenAIndex Index of tokenA in stableswap pool + * @param _tokenBIndex Index of tokenB in stableswap pool + * @param _stableswap Address of Curve Stableswap pool + */ + constructor( + IERC20 _tokenA, + IERC20 _tokenB, + int128 _tokenAIndex, + int128 _tokenBIndex, + IStableSwapPool _stableswap + ) + public + { + require(_stableswap.coins(uint256(_tokenAIndex)) == address(_tokenA), "Stableswap pool has invalid index for tokenA"); + require(_stableswap.coins(uint256(_tokenBIndex)) == address(_tokenB), "Stableswap pool has invalid index for tokenB"); + + tokenA = _tokenA; + tokenB = _tokenB; + tokenAIndex = _tokenAIndex; + tokenBIndex = _tokenBIndex; + stableswap = _stableswap; + + _tokenA.approve(address(_stableswap), PreciseUnitMath.maxUint256()); + _tokenB.approve(address(_stableswap), PreciseUnitMath.maxUint256()); + } + + /* ============ External Getter Functions ============ */ + + /** + * Calculate Curve trade encoded calldata. To be invoked on the SetToken. + * + * @param _sourceToken The input token. + * @param _destinationToken The output token. + * @param _destinationAddress The address where the proceeds of the output is sent to. + * @param _sourceQuantity Amount of input token. + * @param _minDestinationQuantity The minimum amount of output token to be received. + * + * @return address Target contract address + * @return uint256 Call value + * @return bytes Trade calldata + */ + function getTradeCalldata( + address _sourceToken, + address _destinationToken, + address _destinationAddress, + uint256 _sourceQuantity, + uint256 _minDestinationQuantity, + bytes memory /* data */ + ) + external + view + returns (address, uint256, bytes memory) + { + require(_sourceToken != _destinationToken, "_sourceToken must not be the same as _destinationToken"); + require(_sourceToken == address(tokenA) || _sourceToken == address(tokenB), "Invalid sourceToken"); + require(_destinationToken == address(tokenA) || _destinationToken == address(tokenB), "Invalid destinationToken"); + + bytes memory callData = abi.encodeWithSignature("trade(address,address,uint256,uint256,address)", + _sourceToken, + _destinationToken, + _sourceQuantity, + _minDestinationQuantity, + _destinationAddress + ); + return (address(this), 0, callData); + } + + /* ============ External Functions ============ */ + + /** + * Invokes an exchange on Curve Stableswap pool. To be invoked on the SetToken. + * + * @param _sourceToken The input token. + * @param _destinationToken The output token. + * @param _sourceQuantity Amount of input token. + * @param _minDestinationQuantity The minimum amount of output token to be received. + * @param _destinationAddress The address where the proceeds of the output is sent to. + */ + function trade( + address _sourceToken, + address _destinationToken, + uint256 _sourceQuantity, + uint256 _minDestinationQuantity, + address _destinationAddress + ) external { + require(_sourceToken != _destinationToken, "_sourceToken must not be the same as _destinationToken"); + if (_sourceToken == address(tokenA) && _destinationToken == address(tokenB)) { + // Transfers sourceToken + IERC20(_sourceToken).transferFrom(msg.sender, address(this), _sourceQuantity); + + // Exchange sourceToken for destinationToken + uint256 amountOut = stableswap.exchange(tokenAIndex, tokenBIndex, _sourceQuantity, _minDestinationQuantity); + + // Transfer destinationToken to destinationAddress + IERC20(_destinationToken).transfer(_destinationAddress, amountOut); + } else if (_sourceToken == address(tokenB) && _destinationToken == address(tokenA)) { + // Transfers sourceToken + IERC20(_sourceToken).transferFrom(msg.sender, address(this), _sourceQuantity); + + // Exchange sourceToken for destinationToken + uint256 amountOut = stableswap.exchange(tokenBIndex, tokenAIndex, _sourceQuantity, _minDestinationQuantity) ; + + // Transfer destinationToken to destinationAddress + IERC20(_destinationToken).transfer(_destinationAddress, amountOut); + } else { + revert("Invalid _sourceToken or _destinationToken or both"); + } + } + + /** + * Returns the address to approve source tokens to for trading. In this case, the address of this contract. + * + * @return address Address of the contract to approve tokens to. + */ + function getSpender() external view returns (address) { + return address(this); + } +} diff --git a/contracts/protocol/integration/exchange/CurveStEthExchangeAdapter.sol b/contracts/protocol/integration/exchange/CurveStEthExchangeAdapter.sol index e3ceebe08..d35e8ab69 100644 --- a/contracts/protocol/integration/exchange/CurveStEthExchangeAdapter.sol +++ b/contracts/protocol/integration/exchange/CurveStEthExchangeAdapter.sol @@ -18,7 +18,7 @@ pragma experimental "ABIEncoderV2"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { IStableSwapStEth } from "../../../interfaces/external/IStableSwapStEth.sol"; +import { IStableSwapPool } from "../../../interfaces/external/IStableSwapPool.sol"; import { IWETH } from "../../../interfaces/external/IWETH.sol"; import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol"; @@ -46,7 +46,7 @@ contract CurveStEthExchangeAdapter { // Address of stETH token. IERC20 immutable public stETH; // Address of Curve Eth/StEth stableswap pool. - IStableSwapStEth immutable public stableswap; + IStableSwapPool immutable public stableswap; // Index for ETH for Curve stableswap pool. int128 internal constant ETH_INDEX = 0; // Index for stETH for Curve stableswap pool. @@ -64,7 +64,7 @@ contract CurveStEthExchangeAdapter { constructor( IWETH _weth, IERC20 _stETH, - IStableSwapStEth _stableswap + IStableSwapPool _stableswap ) public { diff --git a/hardhat.config.ts b/hardhat.config.ts index 3ac61f90d..8559b001a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,5 +1,5 @@ require("dotenv").config(); -require('hardhat-contract-sizer'); +require("hardhat-contract-sizer"); import chalk from "chalk"; import { HardhatUserConfig } from "hardhat/config"; @@ -12,7 +12,7 @@ import "./tasks"; const forkingConfig = { url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_TOKEN}`, - blockNumber: 12198000, + blockNumber: 14792479, }; const mochaConfig = { diff --git a/test/integration/curveExchangeALM.spec.ts b/test/integration/curveExchangeALM.spec.ts new file mode 100644 index 000000000..8ef1c5448 --- /dev/null +++ b/test/integration/curveExchangeALM.spec.ts @@ -0,0 +1,552 @@ +import "module-alias/register"; + +import { BigNumber } from "ethers"; + +import { Address } from "@utils/types"; +import { Account, ForkedTokens } from "@utils/test/types"; +import DeployHelper from "@utils/deploys"; +import { ether } from "@utils/index"; +import { + cacheBeforeEach, + getAaveV2Fixture, + getAccounts, + getCurveFixture, + getForkedTokens, + getRandomAccount, + getSystemFixture, + getWaffleExpect, + initializeForkedTokens, +} from "@utils/test/index"; + +import { AaveV2AToken, AaveV2VariableDebtToken } from "@utils/contracts/aaveV2"; +import { AaveV2Fixture, CurveFixture, SystemFixture } from "@utils/fixtures"; +import { + AaveLeverageModule, + CurveExchangeAdapter, + CurveStableswapMock, + SetToken, + DebtIssuanceModuleV2, + ManagerIssuanceHookMock, +} from "@utils/contracts"; + +import { IERC20 } from "@typechain/IERC20"; +import { EMPTY_BYTES, MAX_UINT_256, ZERO, ADDRESS_ZERO } from "@utils/constants"; + +const expect = getWaffleExpect(); +/** + * Tests the icETH rebalance flow. + * + * The icETH product is a composite product composed of: + * 1. stETH + * 2. WETH + */ +describe("CurveExchangeAdapter AaveLeverageModule integration [ @forked-mainnet ]", () => { + let owner: Account; + let manager: Account; + + let deployer: DeployHelper; + + let adapter: CurveExchangeAdapter; + let adapterName: string; + + let setup: SystemFixture; + let aaveSetup: AaveV2Fixture; + let curveSetup: CurveFixture; + let stableswap: CurveStableswapMock; + let aaveLeverageModule: AaveLeverageModule; + let tokens: ForkedTokens; + + let weth: IERC20; + let steth: IERC20; + let debtIssuanceModule: DebtIssuanceModuleV2; + let mockPreIssuanceHook: ManagerIssuanceHookMock; + + let astETH: AaveV2AToken; + let variableDebtWETH: AaveV2VariableDebtToken; + + let setToken: SetToken; + let issueQuantity: BigNumber; + + cacheBeforeEach(async () => { + [owner, manager] = await getAccounts(); + + deployer = new DeployHelper(owner.wallet); + + setup = getSystemFixture(owner.address); + await setup.initialize(); + + // Setup ForkedTokens + await initializeForkedTokens(deployer); + tokens = getForkedTokens(); + weth = tokens.weth; + steth = tokens.steth; + + // Setup Curve + curveSetup = getCurveFixture(owner.address); + stableswap = await curveSetup.getForkedWethStethCurveStableswapPool(); + + adapter = await deployer.adapters.deployCurveExchangeAdapter( + weth.address, + steth.address, + BigNumber.from(0), + BigNumber.from(1), + stableswap.address, + ); + adapterName = "CurveExchangeAdapter"; + + // Setup Aave with WETH:stETH at 1.01:1 price. + aaveSetup = getAaveV2Fixture(owner.address); + await aaveSetup.initialize(weth.address, steth.address, "commons", ether(1.02)); + + // Configure borrow rate for stETH like WETH (see Aave fixture) + const oneRay = BigNumber.from(10).pow(27); + await aaveSetup.setMarketBorrowRate(steth.address, oneRay.mul(3).div(100)); + + // Deploy DebtIssuanceModuleV2 + debtIssuanceModule = await deployer.modules.deployDebtIssuanceModuleV2( + setup.controller.address, + ); + + await setup.controller.addModule(debtIssuanceModule.address); + + // Deploy mock issuance hook to pass as arg in DebtIssuance module initialization + mockPreIssuanceHook = await deployer.mocks.deployManagerIssuanceHookMock(); + + // Create liquidity + const ape = await getRandomAccount(); // The wallet adding initial liquidity + await weth.transfer(ape.address, ether(10000)); + await weth.connect(ape.wallet).approve(aaveSetup.lendingPool.address, MAX_UINT_256); + await aaveSetup.lendingPool + .connect(ape.wallet) + .deposit(weth.address, ether(10000), ape.address, ZERO); + + await steth.transfer(ape.address, ether(10000)); + await steth.connect(ape.wallet).approve(aaveSetup.lendingPool.address, MAX_UINT_256); + await aaveSetup.lendingPool + .connect(ape.wallet) + .deposit(steth.address, ether(10000), ape.address, ZERO); + + variableDebtWETH = aaveSetup.wethReserveTokens.variableDebtToken; + + // Alias astETH to dai in Aave Setup (stETH passed in as dai's position in aaveSetup.initialize); + astETH = aaveSetup.daiReserveTokens.aToken; + + // Deploy AaveLeverageModule + const aaveV2Library = await deployer.libraries.deployAaveV2(); + aaveLeverageModule = await deployer.modules.deployAaveLeverageModule( + setup.controller.address, + aaveSetup.lendingPoolAddressesProvider.address, + "contracts/protocol/integration/lib/AaveV2.sol:AaveV2", + aaveV2Library.address, + ); + await setup.controller.addModule(aaveLeverageModule.address); + + // Add DebtIssuanceModule as valid integration for AaveLeverageModule + await setup.integrationRegistry.addIntegration( + aaveLeverageModule.address, + "DefaultIssuanceModule", + debtIssuanceModule.address, + ); + + // Add Curve adapter as valid integration for AaveLeverageModule + await setup.integrationRegistry.addIntegration( + aaveLeverageModule.address, + adapterName, + adapter.address, + ); + }); + + async function initialize() { + // Create Set token + setToken = await setup.createSetToken( + [astETH.address], + [ether(2)], + [debtIssuanceModule.address, aaveLeverageModule.address], + manager.address, + ); + + // Fund owner with stETH + await tokens.steth.transfer(owner.address, ether(1100)); + // Fund owner with WETH + await tokens.weth.transfer(owner.address, ether(1100)); + + // stETH has balance rounding errors that crash DebtIssuanceModuleV2 with: + // "Invalid transfer in. Results in undercollateralization" + // > transfer quantity = 1000000000000000000 + // > stETH balanceOf after transfer = 999999999999999999 + // Transfer steth to set token to overcollaterize the position by exactly the rounding error + // Transferring 2 results in steth.balanceOf(setToken) == 1 + await tokens.steth.transfer(setToken.address, 2); + + // Approve tokens to DebtIssuanceModule and AaveLendingPool + await astETH.connect(owner.wallet).approve(debtIssuanceModule.address, MAX_UINT_256); + + await weth.connect(owner.wallet).approve(debtIssuanceModule.address, MAX_UINT_256); + + await steth.connect(owner.wallet).approve(debtIssuanceModule.address, MAX_UINT_256); + + await weth.connect(owner.wallet).approve(aaveSetup.lendingPool.address, MAX_UINT_256); + + await steth.connect(owner.wallet).approve(aaveSetup.lendingPool.address, MAX_UINT_256); + + // Initialize debIssuance module + await debtIssuanceModule.connect(manager.wallet).initialize( + setToken.address, + ether(0.1), + ether(0), // No issue fee + ether(0), // No redeem fee + owner.address, + mockPreIssuanceHook.address, + ); + + // Initialize SetToken on AaveModule + await aaveLeverageModule.updateAllowedSetToken(setToken.address, true); + + await aaveLeverageModule.connect(manager.wallet).initialize( + setToken.address, + [steth.address, weth.address], // Collateral Assets + [weth.address, steth.address], // Borrow Assets + ); + + // Mint astETH + await aaveSetup.lendingPool + .connect(owner.wallet) + .deposit(steth.address, ether(2), owner.address, ZERO); + + // Issue + issueQuantity = ether(1); + + await debtIssuanceModule + .connect(owner.wallet) + .issue(setToken.address, issueQuantity, owner.address); + } + + describe("#lever", async () => { + // Deposit stETH as collateral in Aave (minting astETH) + // Borrow WETH using deposited stETH as collateral + // Swap WETH to stETH using the Curve stETH pool + // Depost stETH as collateral in Aave (minting additional astETH); + context("using CurveExchangeAdapter to trade stETH for WETH", async () => { + let subjectBorrowAsset: Address; + let subjectCollateralAsset: Address; + let subjectBorrowQuantityUnits: BigNumber; + let subjectMinReceiveQuantityUnits: BigNumber; + let subjectAdapterName: string; + let subjectSetToken: Address; + let subjectData: string; + let subjectCaller: Account; + + cacheBeforeEach(initialize); + + beforeEach(async () => { + subjectBorrowAsset = weth.address; + subjectCollateralAsset = steth.address; + subjectBorrowQuantityUnits = ether(1); + subjectMinReceiveQuantityUnits = ether(0.9); + subjectAdapterName = adapterName; + subjectSetToken = setToken.address; + + const tradeCalldata = await adapter.getTradeCalldata( + subjectBorrowAsset, + subjectCollateralAsset, + subjectSetToken, + subjectBorrowQuantityUnits, + subjectMinReceiveQuantityUnits, + EMPTY_BYTES, + ); + subjectData = tradeCalldata[2]; + subjectCaller = manager; + }); + + async function subject(): Promise { + return aaveLeverageModule + .connect(subjectCaller.wallet) + .lever( + subjectSetToken, + subjectBorrowAsset, + subjectCollateralAsset, + subjectBorrowQuantityUnits, + subjectMinReceiveQuantityUnits, + subjectAdapterName, + subjectData, + ); + } + + it("should update the collateral position on the SetToken correctly", async () => { + const initialPositions = await setToken.getPositions(); + + await subject(); + + // astETH position is increased + const currentPositions = await setToken.getPositions(); + const newFirstPosition = currentPositions[0]; + + const expectedFirstPositionUnitMin = initialPositions[0].unit.add( + subjectMinReceiveQuantityUnits, + ); + // We expect to receive more steth than we borrow for weth due to the exchange rate. + // Therefore, if we borrow 1 WETH, we expect to get 1.02 STETH which gives us an extra + // 1.02 astETH. Technically the exchange rate at this block number is something closer + // to 1.0145 which vaguely matches the actual value we receive below. + const expectedFirstPositionUnitMax = initialPositions[0].unit.add( + subjectBorrowQuantityUnits.add(ether(0.02)), + ); + + expect(initialPositions.length).to.eq(1); + expect(currentPositions.length).to.eq(2); // added a new borrow position + expect(newFirstPosition.component).to.eq(astETH.address); + expect(newFirstPosition.positionState).to.eq(0); // Default + + // Min is: 2900000000000000000 + // Max is: 3020000000000000000 + // Actual value is: 3014553114053842195 + expect(newFirstPosition.unit).to.be.gt(expectedFirstPositionUnitMin); + expect(newFirstPosition.unit).to.be.lt(expectedFirstPositionUnitMax); + + expect(newFirstPosition.module).to.eq(ADDRESS_ZERO); + }); + + it("should update the borrow position on the SetToken correctly", async () => { + const initialPositions = await setToken.getPositions(); + + await subject(); + + const currentPositions = await setToken.getPositions(); + const newSecondPosition = (await setToken.getPositions())[1]; + + const expectedSecondPositionUnit = (await variableDebtWETH.balanceOf(setToken.address)).mul( + -1, + ); + + expect(initialPositions.length).to.eq(1); + expect(currentPositions.length).to.eq(2); + expect(newSecondPosition.component.toLowerCase()).to.eq(weth.address.toLowerCase()); + expect(newSecondPosition.positionState).to.eq(1); // External + expect(newSecondPosition.unit).to.eq(expectedSecondPositionUnit); + expect(newSecondPosition.module).to.eq(aaveLeverageModule.address); + }); + + it("should transfer the correct components to the Stableswap", async () => { + // We're Swapping WETH for stETH + const oldSourceTokenBalance = await weth.balanceOf(stableswap.address); + + await subject(); + const totalSourceQuantity = subjectBorrowQuantityUnits; + const expectedSourceTokenBalance = oldSourceTokenBalance.add(totalSourceQuantity); + const newSourceTokenBalance = await weth.balanceOf(stableswap.address); + expect(newSourceTokenBalance).to.eq(expectedSourceTokenBalance); + }); + + it("should transfer the correct components from the exchange", async () => { + const oldDestinationTokenBalance = await steth.balanceOf(stableswap.address); + + await subject(); + const minDestinationQuantity = subjectMinReceiveQuantityUnits; + // If we borrowed 1 WETH and exchanged for ~1.02 STETH from the exchange, + // then we expect the balance of steth on the exchange to also decrease + // by that same amount + const maxDestinationQuantity = subjectBorrowQuantityUnits.add(ether(0.02)); + + // Will be at most: oldBalance - minReceived + // Will be at least: oldBalance - borrowQuantity + const expectedMaxDestinationTokenBalance = oldDestinationTokenBalance.sub( + minDestinationQuantity, + ); + const expectedMinDestinationTokenBalance = oldDestinationTokenBalance.sub( + maxDestinationQuantity, + ); + + const newDestinationTokenBalance = await steth.balanceOf(stableswap.address); + + expect(newDestinationTokenBalance).to.be.gt(expectedMinDestinationTokenBalance); + expect(newDestinationTokenBalance).to.be.lt(expectedMaxDestinationTokenBalance); + }); + + it("should NOT leave any WETH or stETH in the trade adapter", async () => { + const initialWETHAdapterBalance = await weth.balanceOf(adapter.address); + const initialSTETHAdapterBalance = await steth.balanceOf(adapter.address); + + await subject(); + + const finalWETHAdapterBalance = await weth.balanceOf(adapter.address); + const finalSTETHAdapterBalance = await steth.balanceOf(adapter.address); + + expect(initialWETHAdapterBalance).eq(ZERO); + expect(initialSTETHAdapterBalance).eq(ZERO); + + expect(finalWETHAdapterBalance).eq(ZERO); + expect(finalSTETHAdapterBalance).eq(ZERO); + }); + }); + }); + + describe("#delever", async () => { + context("using CurveExchangeAdapter to trade stETH for WETH", async () => { + let subjectRepayAsset: Address; + let subjectCollateralAsset: Address; + let subjectRedeemQuantityUnits: BigNumber; + let subjectMinRepayQuantityUnits: BigNumber; + let subjectAdapterName: string; + let subjectSetToken: Address; + let subjectData: string; + let subjectCaller: Account; + + cacheBeforeEach(initialize); + + // Lever up before delevering + cacheBeforeEach(async () => { + const tradeCalldata = await adapter.getTradeCalldata( + weth.address, + steth.address, + setToken.address, + ether(1), + ether(0.9), + EMPTY_BYTES, + ); + + const data = tradeCalldata[2]; + + await aaveLeverageModule + .connect(manager.wallet) + .lever( + setToken.address, + weth.address, + steth.address, + ether(1), + ether(0.9), + adapterName, + data, + ); + }); + + beforeEach(async () => { + subjectSetToken = setToken.address; + subjectCollateralAsset = steth.address; + subjectRepayAsset = weth.address; + subjectRedeemQuantityUnits = ether(1.0158); + subjectMinRepayQuantityUnits = ether(1); + subjectAdapterName = adapterName; + + const tradeCalldata = await adapter.getTradeCalldata( + subjectCollateralAsset, + subjectRepayAsset, + subjectSetToken, + subjectRedeemQuantityUnits, + subjectMinRepayQuantityUnits, + EMPTY_BYTES, + ); + subjectData = tradeCalldata[2]; + subjectCaller = manager; + }); + + async function subject(): Promise { + return aaveLeverageModule + .connect(subjectCaller.wallet) + .delever( + subjectSetToken, + subjectCollateralAsset, + subjectRepayAsset, + subjectRedeemQuantityUnits, + subjectMinRepayQuantityUnits, + subjectAdapterName, + subjectData, + ); + } + + it("should update the collateral position on the SetToken correctly", async () => { + const initialPositions = await setToken.getPositions(); + + await subject(); + + const currentPositions = await setToken.getPositions(); + const newFirstPosition = (await setToken.getPositions())[0]; + + // Get expected aTokens burnt + const expectedFirstPositionUnit = initialPositions[0].unit.sub(subjectRedeemQuantityUnits); + + expect(initialPositions.length).to.eq(2); + expect(currentPositions.length).to.eq(2); + expect(newFirstPosition.component).to.eq(astETH.address); + expect(newFirstPosition.positionState).to.eq(0); // Default + + expect(newFirstPosition.unit).to.eq(expectedFirstPositionUnit); + + expect(newFirstPosition.module).to.eq(ADDRESS_ZERO); + }); + + it("should update the borrow position on the SetToken correctly", async () => { + const initialPositions = await setToken.getPositions(); + + await subject(); + + const currentPositions = await setToken.getPositions(); + const newSecondPosition = currentPositions[1]; + + const expectedSecondPositionUnit = (await variableDebtWETH.balanceOf(setToken.address)).mul( + -1, + ); + + expect(initialPositions.length).to.eq(2); + expect(currentPositions.length).to.eq(2); + expect(newSecondPosition.component.toLowerCase()).to.eq(weth.address.toLowerCase()); + expect(newSecondPosition.positionState).to.eq(0); // Pay everything back + + // Due to exchange rates in Curve Pool, there's a tiny bit of WETH left in the set token when redeeming + // with the current parameters. Actual WETH left in the Set = 428_176_647_407_742. So around 0.0004 ETH + expect(newSecondPosition.unit.div(ether(0.0001))).to.closeTo(expectedSecondPositionUnit, 4); + expect(newSecondPosition.module).to.eq(ADDRESS_ZERO); + }); + + it("should transfer the correct components to the Stableswap", async () => { + // We're swapping stETH for ETH + const oldDestinationTokenBalance = await steth.balanceOf(stableswap.address); + + await subject(); + + const expectedDestinationTokenBalance = oldDestinationTokenBalance.add( + subjectRedeemQuantityUnits, + ); + + const newDestinationTokenBalance = await steth.balanceOf(stableswap.address); + + // Accomodate rounding error of 1 when reading stETH balance + expect(newDestinationTokenBalance).to.be.closeTo(expectedDestinationTokenBalance, 1); + }); + + it("should transfer the correct components from the exchange", async () => { + const oldSourceTokenBalance = await weth.balanceOf(stableswap.address); + + await subject(); + + const minSourceQuantity = subjectMinRepayQuantityUnits; + const maxSourceQuantity = subjectRedeemQuantityUnits; + + // Will be at least: oldBalance + redeemQuantity + // Will be at most: oldBalance + minRepayQuantity + const expectedMaxSourceTokenBalance = oldSourceTokenBalance.sub(minSourceQuantity); + const expectedMinSourceTokenBalance = oldSourceTokenBalance.sub(maxSourceQuantity); + + const newSourceTokenBalance = await weth.balanceOf(stableswap.address); + + expect(newSourceTokenBalance).to.be.gt(expectedMinSourceTokenBalance); + expect(newSourceTokenBalance).to.be.lt(expectedMaxSourceTokenBalance); + }); + + it("should NOT leave any WETH or stETH in the trade adapter", async () => { + const initialWETHAdapterBalance = await weth.balanceOf(adapter.address); + const initialSTETHAdapterBalance = await steth.balanceOf(adapter.address); + + await subject(); + + const finalWETHAdapterBalance = await weth.balanceOf(adapter.address); + const finalSTETHAdapterBalance = await steth.balanceOf(adapter.address); + + expect(initialWETHAdapterBalance).eq(ZERO); + expect(initialSTETHAdapterBalance).eq(ZERO); + + expect(finalWETHAdapterBalance).eq(ZERO); + expect(finalSTETHAdapterBalance).eq(ZERO); + }); + }); + }); +}); diff --git a/test/integration/curveStEthALM.spec.ts b/test/integration/curveStEthALM.spec.ts index 7cd0563c8..549da1293 100644 --- a/test/integration/curveStEthALM.spec.ts +++ b/test/integration/curveStEthALM.spec.ts @@ -19,15 +19,12 @@ import { getEthBalance, } from "@utils/test/index"; -import { - AaveV2AToken, - AaveV2VariableDebtToken, -} from "@utils/contracts/aaveV2"; +import { AaveV2AToken, AaveV2VariableDebtToken } from "@utils/contracts/aaveV2"; import { AaveV2Fixture, CurveFixture, SystemFixture } from "@utils/fixtures"; import { AaveLeverageModule, CurveStEthExchangeAdapter, - CurveStEthStableswapMock, + CurveStableswapMock, SetToken, DebtIssuanceModuleV2, ManagerIssuanceHookMock, @@ -57,7 +54,7 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai let setup: SystemFixture; let aaveSetup: AaveV2Fixture; let curveSetup: CurveFixture; - let stableswap: CurveStEthStableswapMock; + let stableswap: CurveStableswapMock; let aaveLeverageModule: AaveLeverageModule; let tokens: ForkedTokens; @@ -107,7 +104,7 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai // Deploy DebtIssuanceModuleV2 debtIssuanceModule = await deployer.modules.deployDebtIssuanceModuleV2( - setup.controller.address + setup.controller.address, ); await setup.controller.addModule(debtIssuanceModule.address); @@ -116,31 +113,24 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai mockPreIssuanceHook = await deployer.mocks.deployManagerIssuanceHookMock(); // Create liquidity - const ape = await getRandomAccount(); // The wallet adding initial liquidity + const ape = await getRandomAccount(); // The wallet adding initial liquidity await weth.transfer(ape.address, ether(50)); await weth.connect(ape.wallet).approve(aaveSetup.lendingPool.address, ether(50)); - await aaveSetup.lendingPool.connect(ape.wallet).deposit( - weth.address, - ether(50), - ape.address, - ZERO - ); + await aaveSetup.lendingPool + .connect(ape.wallet) + .deposit(weth.address, ether(50), ape.address, ZERO); await steth.transfer(ape.address, ether(50000)); await steth.connect(ape.wallet).approve(aaveSetup.lendingPool.address, ether(50000)); - await aaveSetup.lendingPool.connect(ape.wallet).deposit( - steth.address, - ether(50), - ape.address, - ZERO - ); + await aaveSetup.lendingPool + .connect(ape.wallet) + .deposit(steth.address, ether(50), ape.address, ZERO); variableDebtWETH = aaveSetup.wethReserveTokens.variableDebtToken; // Alias astETH to dai in Aave Setup (stETH passed in as dai's position in aaveSetup.initialize); astETH = aaveSetup.daiReserveTokens.aToken; - // Deploy AaveLeverageModule const aaveV2Library = await deployer.libraries.deployAaveV2(); aaveLeverageModule = await deployer.modules.deployAaveLeverageModule( @@ -155,14 +145,14 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai await setup.integrationRegistry.addIntegration( aaveLeverageModule.address, "DefaultIssuanceModule", - debtIssuanceModule.address + debtIssuanceModule.address, ); // Add Curve adapter as valid integration for AaveLeverageModule await setup.integrationRegistry.addIntegration( aaveLeverageModule.address, adapterName, - adapter.address + adapter.address, ); }); @@ -187,46 +177,35 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai await tokens.steth.transfer(setToken.address, 2); // Approve tokens to DebtIssuanceModule and AaveLendingPool - await astETH - .connect(owner.wallet) - .approve(debtIssuanceModule.address, MAX_UINT_256); + await astETH.connect(owner.wallet).approve(debtIssuanceModule.address, MAX_UINT_256); - await weth - .connect(owner.wallet) - .approve(debtIssuanceModule.address, MAX_UINT_256); + await weth.connect(owner.wallet).approve(debtIssuanceModule.address, MAX_UINT_256); - await steth - .connect(owner.wallet) - .approve(debtIssuanceModule.address, MAX_UINT_256); + await steth.connect(owner.wallet).approve(debtIssuanceModule.address, MAX_UINT_256); - await steth - .connect(owner.wallet) - .approve(aaveSetup.lendingPool.address, MAX_UINT_256); + await steth.connect(owner.wallet).approve(aaveSetup.lendingPool.address, MAX_UINT_256); // Initialize debIssuance module await debtIssuanceModule.connect(manager.wallet).initialize( setToken.address, - ether(.1), + ether(0.1), ether(0), // No issue fee ether(0), // No redeem fee owner.address, - mockPreIssuanceHook.address + mockPreIssuanceHook.address, ); // Initialize SetToken on AaveModule await aaveLeverageModule.updateAllowedSetToken(setToken.address, true); - await aaveLeverageModule - .connect(manager.wallet) - .initialize( - setToken.address, - [steth.address, weth.address], // Collateral Assets - [weth.address, steth.address ] // Borrow Assets - ); + await aaveLeverageModule.connect(manager.wallet).initialize( + setToken.address, + [steth.address, weth.address], // Collateral Assets + [weth.address, steth.address], // Borrow Assets + ); // Mint astETH - await aaveSetup - .lendingPool + await aaveSetup.lendingPool .connect(owner.wallet) .deposit(steth.address, ether(10000), owner.address, ZERO); @@ -259,7 +238,7 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai subjectBorrowAsset = weth.address; subjectCollateralAsset = steth.address; subjectBorrowQuantityUnits = ether(1); - subjectMinReceiveQuantityUnits = ether(.9); + subjectMinReceiveQuantityUnits = ether(0.9); subjectAdapterName = adapterName; subjectSetToken = setToken.address; @@ -298,17 +277,25 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai const currentPositions = await setToken.getPositions(); const newFirstPosition = (await setToken.getPositions())[0]; - const expectedFirstPositionUnitMin = initialPositions[0].unit.add(subjectMinReceiveQuantityUnits); - const expectedFirstPositionUnitMax = initialPositions[0].unit.add(subjectBorrowQuantityUnits); + const expectedFirstPositionUnitMin = initialPositions[0].unit.add( + subjectMinReceiveQuantityUnits, + ); + // We expect to receive more steth than we borrow for weth due to the exchange rate. + // Therefore, if we borrow 1 WETH, we expect to get 1.02 STETH which gives us an extra + // 1.02 astETH. Technically the exchange rate at this block number is something closer + // to 1.0145 which vaguely matches the actual value we receive below. + const expectedFirstPositionUnitMax = initialPositions[0].unit.add( + subjectBorrowQuantityUnits.add(ether(0.02)), + ); expect(initialPositions.length).to.eq(1); - expect(currentPositions.length).to.eq(2); // added a new borrow position + expect(currentPositions.length).to.eq(2); // added a new borrow position expect(newFirstPosition.component).to.eq(astETH.address); expect(newFirstPosition.positionState).to.eq(0); // Default // Min is: 2900000000000000000 // Max is: 3000000000000000000 - // Actual value is: "2952447391113503240" + // Actual value is: "3010488084692366762" expect(newFirstPosition.unit).to.be.gt(expectedFirstPositionUnitMin); expect(newFirstPosition.unit).to.be.lt(expectedFirstPositionUnitMax); @@ -323,7 +310,9 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai const currentPositions = await setToken.getPositions(); const newSecondPosition = (await setToken.getPositions())[1]; - const expectedSecondPositionUnit = (await variableDebtWETH.balanceOf(setToken.address)).mul(-1); + const expectedSecondPositionUnit = (await variableDebtWETH.balanceOf(setToken.address)).mul( + -1, + ); expect(initialPositions.length).to.eq(1); expect(currentPositions.length).to.eq(2); @@ -349,12 +338,19 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai await subject(); const minDestinationQuantity = subjectMinReceiveQuantityUnits; - const maxDestinationQuantity = subjectBorrowQuantityUnits; + // If we borrowed 1 WETH and exchanged for ~1.02 STETH from the exchange, + // then we expect the balance of steth on the exchange to also decrease + // by that same amount + const maxDestinationQuantity = subjectBorrowQuantityUnits.add(ether(0.02)); // Will be at most: oldBalance - minReceived // Will be at least: oldBalance - borrowQuantity - const expectedMaxDestinationTokenBalance = oldDestinationTokenBalance.sub(minDestinationQuantity); - const expectedMinDestinationTokenBalance = oldDestinationTokenBalance.sub(maxDestinationQuantity); + const expectedMaxDestinationTokenBalance = oldDestinationTokenBalance.sub( + minDestinationQuantity, + ); + const expectedMinDestinationTokenBalance = oldDestinationTokenBalance.sub( + maxDestinationQuantity, + ); const newDestinationTokenBalance = await steth.balanceOf(stableswap.address); expect(newDestinationTokenBalance).to.be.gt(expectedMinDestinationTokenBalance); @@ -403,7 +399,7 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai steth.address, setToken.address, ether(1), - ether(.9), + ether(0.9), EMPTY_BYTES, ); @@ -416,7 +412,7 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai weth.address, steth.address, ether(1), - ether(.9), + ether(0.9), adapterName, data, ); @@ -427,7 +423,7 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai subjectCollateralAsset = steth.address; subjectRepayAsset = weth.address; subjectRedeemQuantityUnits = ether(1); - subjectMinRepayQuantityUnits = ether(.9); + subjectMinRepayQuantityUnits = ether(0.9); subjectAdapterName = adapterName; const tradeCalldata = await adapter.getTradeCalldata( @@ -485,7 +481,9 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai const currentPositions = await setToken.getPositions(); const newSecondPosition = (await setToken.getPositions())[1]; - const expectedSecondPositionUnit = (await variableDebtWETH.balanceOf(setToken.address)).mul(-1); + const expectedSecondPositionUnit = (await variableDebtWETH.balanceOf(setToken.address)).mul( + -1, + ); expect(initialPositions.length).to.eq(2); expect(currentPositions.length).to.eq(2); @@ -504,7 +502,9 @@ describe("CurveStEthExchangeAdapter AaveLeverageModule integration [ @forked-mai await subject(); - const expectedDestinationTokenBalance = oldDestinationTokenBalance.add(subjectRedeemQuantityUnits); + const expectedDestinationTokenBalance = oldDestinationTokenBalance.add( + subjectRedeemQuantityUnits, + ); const newDestinationTokenBalance = await steth.balanceOf(stableswap.address); diff --git a/test/integration/sushiswapExchangeTradeModule.spec.ts b/test/integration/sushiswapExchangeTradeModule.spec.ts index 66cd3819a..be2ebf666 100644 --- a/test/integration/sushiswapExchangeTradeModule.spec.ts +++ b/test/integration/sushiswapExchangeTradeModule.spec.ts @@ -60,7 +60,7 @@ describe("SushiSwap TradeModule Integration [ @forked-mainnet ]", () => { setup = getSystemFixture(owner.address); await setup.initialize(); - wbtcRate = ether(29); + wbtcRate = ether(14); sushiswapSetup = getUniswapFixture(owner.address); sushiswapRouter = sushiswapSetup.getForkedSushiswapRouter(); diff --git a/test/integration/uniswapV2ExchangeTradeModule.spec.ts b/test/integration/uniswapV2ExchangeTradeModule.spec.ts index 1920b113b..e7f1bda61 100644 --- a/test/integration/uniswapV2ExchangeTradeModule.spec.ts +++ b/test/integration/uniswapV2ExchangeTradeModule.spec.ts @@ -60,7 +60,7 @@ describe("UniswapExchangeV2 TradeModule Integration [ @forked-mainnet ]", () => setup = getSystemFixture(owner.address); await setup.initialize(); - wbtcRate = ether(29); + wbtcRate = ether(14); uniswapSetup = getUniswapFixture(owner.address); uniswapRouter = uniswapSetup.getForkedUniswapRouter(); diff --git a/test/protocol/integration/exchange/curveExchangeAdapter.spec.ts b/test/protocol/integration/exchange/curveExchangeAdapter.spec.ts new file mode 100644 index 000000000..ae5ee973f --- /dev/null +++ b/test/protocol/integration/exchange/curveExchangeAdapter.spec.ts @@ -0,0 +1,395 @@ +import "module-alias/register"; + +import { BigNumber, ContractTransaction } from "ethers"; + +import { Address } from "@utils/types"; +import { Account } from "@utils/test/types"; +import { EMPTY_BYTES, MAX_UINT_256 } from "@utils/constants"; +import DeployHelper from "@utils/deploys"; +import { ether } from "@utils/index"; +import { + addSnapshotBeforeRestoreAfterEach, + getAccounts, + getSystemFixture, + getWaffleExpect, +} from "@utils/test/index"; + +import { SystemFixture } from "@utils/fixtures"; +import { CurveExchangeAdapter, CurveStableswapMock } from "@utils/contracts"; + +import { StandardTokenMock } from "@typechain/StandardTokenMock"; + +const expect = getWaffleExpect(); + +describe("CurveExchangeAdapter", () => { + let owner: Account; + let whale: Account; + let mockSetToken: Account; + let stEth: StandardTokenMock; + let deployer: DeployHelper; + let setup: SystemFixture; + + let stableswap: CurveStableswapMock; + let adapter: CurveExchangeAdapter; + + before(async () => { + [owner, whale, mockSetToken] = await getAccounts(); + + deployer = new DeployHelper(owner.wallet); + setup = getSystemFixture(owner.address); + await setup.initialize(); + + stEth = await deployer.mocks.deployTokenMock(owner.address); + await setup.weth.connect(whale.wallet).deposit({ value: ether(100) }); + await stEth.connect(whale.wallet).mint(whale.address, ether(100)); + + stableswap = await deployer.mocks.deployCurveStableswapMock([ + setup.weth.address, + stEth.address, + ]); + + adapter = await deployer.adapters.deployCurveExchangeAdapter( + setup.weth.address, + stEth.address, + BigNumber.from(0), + BigNumber.from(1), + stableswap.address, + ); + + await stEth.connect(owner.wallet).approve(adapter.address, MAX_UINT_256); + await setup.weth.connect(owner.wallet).approve(adapter.address, MAX_UINT_256); + + await stEth.connect(whale.wallet).approve(stableswap.address, MAX_UINT_256); + await setup.weth.connect(whale.wallet).approve(stableswap.address, MAX_UINT_256); + await stableswap.connect(whale.wallet).add_liquidity([ether(100), ether(100)], ether(1)); + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#constructor", async () => { + let subjectWeth: Address; + let subjectSteth: Address; + let subjectWethIndex: BigNumber; + let subjectStethIndex: BigNumber; + let subjectExchangeAddress: Address; + + beforeEach(async () => { + subjectWeth = setup.weth.address; + subjectSteth = stEth.address; + subjectWethIndex = BigNumber.from(0); + subjectStethIndex = BigNumber.from(1); + subjectExchangeAddress = stableswap.address; + }); + + async function subject(): Promise { + return await deployer.adapters.deployCurveExchangeAdapter( + subjectWeth, + subjectSteth, + subjectWethIndex, + subjectStethIndex, + subjectExchangeAddress, + ); + } + it("should have the correct weth address", async () => { + const adapter = await subject(); + expect(await adapter.tokenA()).to.eq(subjectWeth); + }); + + it("should have the correct steth address", async () => { + const adapter = await subject(); + expect(await adapter.tokenB()).to.eq(subjectSteth); + }); + + it("should have the correct weth index", async () => { + const adapter = await subject(); + expect(await adapter.tokenAIndex()).to.eq(0); + }); + + it("should have the correct steth index", async () => { + const adapter = await subject(); + expect(await adapter.tokenBIndex()).to.eq(1); + }); + + it("should have the correct exchange address", async () => { + const adapter = await subject(); + expect(await adapter.stableswap()).to.eq(subjectExchangeAddress); + }); + + context("when incorrect tokenAIndex is passed in", async () => { + beforeEach(async () => { + subjectWeth = setup.weth.address; + subjectSteth = stEth.address; + subjectWethIndex = BigNumber.from(1); + subjectStethIndex = BigNumber.from(1); + subjectExchangeAddress = stableswap.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Stableswap pool has invalid index for tokenA"); + }); + }); + + context("when incorrect tokenBIndex is passed in", async () => { + beforeEach(async () => { + subjectWeth = setup.weth.address; + subjectSteth = stEth.address; + subjectWethIndex = BigNumber.from(0); + subjectStethIndex = BigNumber.from(0); + subjectExchangeAddress = stableswap.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Stableswap pool has invalid index for tokenB"); + }); + }); + }); + + describe("#getSpender", async () => { + async function subject(): Promise { + return await adapter.getSpender(); + } + + it("should return the correct spender address", async () => { + const spender = await subject(); + + expect(spender).to.eq(adapter.address); + }); + }); + + describe("#getTradeCalldata", async () => { + let subjectMockSetToken: Address; + let subjectSourceToken: Address; + let subjectDestinationToken: Address; + let subjectSourceQuantity: BigNumber; + let subjectMinDestinationQuantity: BigNumber; + + async function subject(): Promise<[string, BigNumber, string]> { + return await adapter.getTradeCalldata( + subjectSourceToken, + subjectDestinationToken, + subjectMockSetToken, + subjectSourceQuantity, + subjectMinDestinationQuantity, + EMPTY_BYTES, + ); + } + + context("when buying stETH with weth", async () => { + beforeEach(async () => { + subjectSourceToken = setup.weth.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = stEth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should return calldata", async () => { + const expectedCalldata = await adapter.interface.encodeFunctionData("trade", [ + subjectSourceToken, + subjectDestinationToken, + subjectSourceQuantity, + subjectMinDestinationQuantity, + subjectMockSetToken, + ]); + const callData = await subject(); + expect(callData[0]).to.eq(adapter.address); + expect(callData[1]).to.eq(0); + expect(JSON.stringify(callData[2])).to.eq(JSON.stringify(expectedCalldata)); + }); + }); + + context("when buying weth with stETH", async () => { + beforeEach(async () => { + subjectSourceToken = stEth.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = setup.weth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should return calldata", async () => { + const expectedCalldata = await adapter.interface.encodeFunctionData("trade", [ + subjectSourceToken, + subjectDestinationToken, + subjectSourceQuantity, + subjectMinDestinationQuantity, + subjectMockSetToken, + ]); + const callData = await subject(); + expect(callData[0]).to.eq(adapter.address); + expect(callData[1]).to.eq(0); + expect(JSON.stringify(callData[2])).to.eq(JSON.stringify(expectedCalldata)); + }); + }); + + context("when sourceToken and destinationToken are the same", async () => { + beforeEach(async () => { + subjectSourceToken = stEth.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = stEth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "_sourceToken must not be the same as _destinationToken", + ); + }); + }); + + context("when an invalid sourceToken is passed in", async () => { + beforeEach(async () => { + subjectSourceToken = setup.dai.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = setup.weth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Invalid sourceToken"); + }); + }); + + context("when an invalid destinationToken is passed in", async () => { + beforeEach(async () => { + subjectSourceToken = stEth.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = setup.dai.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("Invalid destinationToken"); + }); + }); + }); + + describe("#trade", async () => { + let subjectMockSetToken: Address; + let subjectSourceToken: Address; + let subjectDestinationToken: Address; + let subjectSourceQuantity: BigNumber; + let subjectMinDestinationQuantity: BigNumber; + + async function subject(): Promise { + return await adapter + .connect(owner.wallet) + .trade( + subjectSourceToken, + subjectDestinationToken, + subjectSourceQuantity, + subjectMinDestinationQuantity, + subjectMockSetToken, + ); + } + + context("when trading steth for weth", async () => { + beforeEach(async () => { + subjectSourceToken = stEth.address; + subjectSourceQuantity = ether(25); + subjectDestinationToken = setup.weth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should succeed", async () => { + const previousStEthBalance = await stEth.balanceOf(owner.address); + const previousWethBalance = await setup.weth.balanceOf(subjectMockSetToken); + expect(previousStEthBalance).to.eq(ether(1000000000)); + expect(previousWethBalance).to.eq(0); + + await subject(); + + const afterStEthBalance = await stEth.balanceOf(owner.address); + const afterWethBalance = await setup.weth.balanceOf(subjectMockSetToken); + expect(afterStEthBalance).to.eq(previousStEthBalance.sub(subjectSourceQuantity)); + expect(afterWethBalance).to.eq(previousWethBalance.add(subjectMinDestinationQuantity)); + }); + }); + + context("when trading weth for steth", async () => { + beforeEach(async () => { + subjectSourceToken = setup.weth.address; + subjectSourceQuantity = ether(25); + subjectDestinationToken = stEth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should succeed", async () => { + const previousWethBalance = await setup.weth.balanceOf(owner.address); + const previousStEthBalance = await stEth.balanceOf(subjectMockSetToken); + expect(previousWethBalance).to.eq(ether(5000)); + expect(previousStEthBalance).to.eq(0); + + await subject(); + + const afterWethBalance = await setup.weth.balanceOf(owner.address); + const afterStEthBalance = await stEth.balanceOf(subjectMockSetToken); + expect(afterWethBalance).to.eq(previousWethBalance.sub(subjectSourceQuantity)); + expect(afterStEthBalance).to.eq(previousStEthBalance.add(subjectMinDestinationQuantity)); + }); + }); + + context("when sourceToken and destinationToken are the same", async () => { + beforeEach(async () => { + subjectSourceToken = stEth.address; + subjectSourceQuantity = ether(25); + subjectDestinationToken = stEth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "_sourceToken must not be the same as _destinationToken", + ); + }); + }); + + context("when an invalid sourceToken is passed in", async () => { + beforeEach(async () => { + subjectSourceToken = setup.dai.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = setup.weth.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Invalid _sourceToken or _destinationToken or both", + ); + }); + }); + + context("when an invalid destinationToken is passed in", async () => { + beforeEach(async () => { + subjectSourceToken = stEth.address; + subjectSourceQuantity = BigNumber.from(100000000); + subjectDestinationToken = setup.dai.address; + subjectMinDestinationQuantity = ether(25); + + subjectMockSetToken = mockSetToken.address; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith( + "Invalid _sourceToken or _destinationToken or both", + ); + }); + }); + }); +}); diff --git a/test/protocol/integration/exchange/curveStEthExchangeAdapter.spec.ts b/test/protocol/integration/exchange/curveStEthExchangeAdapter.spec.ts index 4e0f3fd82..db1ce0f5e 100644 --- a/test/protocol/integration/exchange/curveStEthExchangeAdapter.spec.ts +++ b/test/protocol/integration/exchange/curveStEthExchangeAdapter.spec.ts @@ -15,7 +15,7 @@ import { } from "@utils/test/index"; import { SystemFixture } from "@utils/fixtures"; -import { CurveStEthExchangeAdapter, CurveStEthStableswapMock } from "@utils/contracts"; +import { CurveStEthExchangeAdapter, CurveStableswapMock } from "@utils/contracts"; import { StandardTokenMock } from "@typechain/StandardTokenMock"; @@ -29,7 +29,7 @@ describe("CurveStEthExchangeAdapter", () => { let deployer: DeployHelper; let setup: SystemFixture; - let stableswap: CurveStEthStableswapMock; + let stableswap: CurveStableswapMock; let adapter: CurveStEthExchangeAdapter; before(async () => { @@ -42,7 +42,7 @@ describe("CurveStEthExchangeAdapter", () => { stEth = await deployer.mocks.deployTokenMock(owner.address); await stEth.connect(whale.wallet).mint(whale.address, ether(100)); - stableswap = await deployer.mocks.deployCurveStEthStableswapMock([ETH_ADDRESS, stEth.address]); + stableswap = await deployer.mocks.deployCurveStableswapMock([ETH_ADDRESS, stEth.address]); adapter = await deployer.adapters.deployCurveStEthExchangeAdapter( setup.weth.address, @@ -101,7 +101,7 @@ describe("CurveStEthExchangeAdapter", () => { context("when stableswap pool does not support ETH in index 0", async () => { beforeEach(async () => { - const mock = await deployer.mocks.deployCurveStEthStableswapMock([stEth.address, stEth.address]); + const mock = await deployer.mocks.deployCurveStableswapMock([stEth.address, stEth.address]); subjectExchangeAddress = mock.address; }); @@ -112,7 +112,7 @@ describe("CurveStEthExchangeAdapter", () => { context("when stableswap pool does not support stETH in index 1", async () => { beforeEach(async () => { - const mock = await deployer.mocks.deployCurveStEthStableswapMock([ETH_ADDRESS, ETH_ADDRESS]); + const mock = await deployer.mocks.deployCurveStableswapMock([ETH_ADDRESS, ETH_ADDRESS]); subjectExchangeAddress = mock.address; }); diff --git a/utils/contracts/curve.ts b/utils/contracts/curve.ts index 1ce66e95d..0986d8c2a 100644 --- a/utils/contracts/curve.ts +++ b/utils/contracts/curve.ts @@ -1,7 +1,7 @@ // External Curve Contracts export { CurveDeposit } from "../../typechain/CurveDeposit"; export { CurvePoolERC20 } from "../../typechain/CurvePoolERC20"; -export { CurveStEthStableswapMock } from "../../typechain/CurveStEthStableswapMock"; +export { CurveStableswapMock } from "../../typechain/CurveStableswapMock"; export { CRVToken } from "../../typechain/CRVToken"; export { GaugeController } from "../../typechain/GaugeController"; export { LiquidityGauge } from "../../typechain/LiquidityGauge"; diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index ea0f05978..834d19007 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -27,8 +27,9 @@ export { ComptrollerMock } from "../../typechain/ComptrollerMock"; export { ContractCallerMock } from "../../typechain/ContractCallerMock"; export { Controller } from "../../typechain/Controller"; export { CurveStakingAdapter } from "../../typechain/CurveStakingAdapter"; +export { CurveExchangeAdapter } from "../../typechain/CurveExchangeAdapter"; export { CurveStEthExchangeAdapter } from "../../typechain/CurveStEthExchangeAdapter"; -export { CurveStEthStableswapMock } from "../../typechain/CurveStEthStableswapMock"; +export { CurveStableswapMock } from "../../typechain/CurveStableswapMock"; export { CustomOracleNavIssuanceModule } from "../../typechain/CustomOracleNavIssuanceModule"; export { CustomSetValuerMock } from "../../typechain/CustomSetValuerMock"; export { DebtIssuanceMock } from "../../typechain/DebtIssuanceMock"; diff --git a/utils/deploys/dependencies.ts b/utils/deploys/dependencies.ts index f04108821..1da9147c3 100644 --- a/utils/deploys/dependencies.ts +++ b/utils/deploys/dependencies.ts @@ -177,6 +177,9 @@ export default { CURVE_ETH_STETH_EXCHANGE: { 1: "0xDC24316b9AE028F1497c275EB9192a3Ea0f67022", }, + CURVE_WETH_STETH_EXCHANGE: { + 1: "0x828b154032950C8ff7CF8085D841723Db2696056", + }, ZERO_EX_EXCHANGE: { 1: "0xDef1C0ded9bec7F1a1670819833240f027b25EfF", }, @@ -194,7 +197,7 @@ export default { USDC_WHALE: "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503", DAI_WHALE: "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503", - WETH_WHALE: "0x94B0A3d511b6EcDb17eBF877278Ab030acb0A878", + WETH_WHALE: "0x2f0b23f53734252bda2277357e97e1517d6b042a", WBTC_WHALE: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", STETH_WHALE: "0xdc24316b9ae028f1497c275eb9192a3ea0f67022", diff --git a/utils/deploys/deployAdapters.ts b/utils/deploys/deployAdapters.ts index 95cf0f645..72af8e9d1 100644 --- a/utils/deploys/deployAdapters.ts +++ b/utils/deploys/deployAdapters.ts @@ -1,10 +1,11 @@ -import { Signer } from "ethers"; +import { BigNumber, Signer } from "ethers"; import { AaveGovernanceV2Adapter, AaveV2WrapV2Adapter, BalancerV1IndexExchangeAdapter, CompoundLikeGovernanceAdapter, + CurveExchangeAdapter, CurveStakingAdapter, CurveStEthExchangeAdapter, KyberExchangeAdapter, @@ -34,6 +35,7 @@ import { AaveGovernanceV2Adapter__factory } from "../../typechain/factories/Aave import { AaveV2WrapV2Adapter__factory } from "../../typechain/factories/AaveV2WrapV2Adapter__factory"; import { BalancerV1IndexExchangeAdapter__factory } from "../../typechain/factories/BalancerV1IndexExchangeAdapter__factory"; import { CompoundLikeGovernanceAdapter__factory } from "../../typechain/factories/CompoundLikeGovernanceAdapter__factory"; +import { CurveExchangeAdapter__factory } from "../../typechain/factories/CurveExchangeAdapter__factory"; import { CurveStakingAdapter__factory } from "../../typechain/factories/CurveStakingAdapter__factory"; import { CurveStEthExchangeAdapter__factory } from "../../typechain/factories/CurveStEthExchangeAdapter__factory"; import { KyberExchangeAdapter__factory } from "../../typechain/factories/KyberExchangeAdapter__factory"; @@ -64,19 +66,21 @@ export default class DeployAdapters { this._deployerSigner = deployerSigner; } - public async deployKyberExchangeAdapter(kyberNetworkProxy: Address): Promise { + public async deployKyberExchangeAdapter( + kyberNetworkProxy: Address, + ): Promise { return await new KyberExchangeAdapter__factory(this._deployerSigner).deploy(kyberNetworkProxy); } public async deployOneInchExchangeAdapter( approveAddress: Address, exchangeAddress: Address, - swapFunctionSignature: Bytes + swapFunctionSignature: Bytes, ): Promise { return await new OneInchExchangeAdapter__factory(this._deployerSigner).deploy( approveAddress, exchangeAddress, - swapFunctionSignature + swapFunctionSignature, ); } @@ -84,101 +88,174 @@ export default class DeployAdapters { return await new UniswapV2AmmAdapter__factory(this._deployerSigner).deploy(uniswapV2Router); } - public async deployUniswapV2ExchangeAdapter(uniswapV2Router: Address): Promise { - return await new UniswapV2ExchangeAdapter__factory(this._deployerSigner).deploy(uniswapV2Router); + public async deployUniswapV2ExchangeAdapter( + uniswapV2Router: Address, + ): Promise { + return await new UniswapV2ExchangeAdapter__factory(this._deployerSigner).deploy( + uniswapV2Router, + ); } - public async deployUniswapV2TransferFeeExchangeAdapter(uniswapV2Router: Address): Promise { - return await new UniswapV2TransferFeeExchangeAdapter__factory(this._deployerSigner).deploy(uniswapV2Router); + public async deployUniswapV2TransferFeeExchangeAdapter( + uniswapV2Router: Address, + ): Promise { + return await new UniswapV2TransferFeeExchangeAdapter__factory(this._deployerSigner).deploy( + uniswapV2Router, + ); } - public async deployUniswapV2ExchangeAdapterV2(uniswapV2Router: Address): Promise { - return await new UniswapV2ExchangeAdapterV2__factory(this._deployerSigner).deploy(uniswapV2Router); + public async deployUniswapV2ExchangeAdapterV2( + uniswapV2Router: Address, + ): Promise { + return await new UniswapV2ExchangeAdapterV2__factory(this._deployerSigner).deploy( + uniswapV2Router, + ); } - public async deployUniswapV2IndexExchangeAdapter(uniswapV2Router: Address): Promise { - return await new UniswapV2IndexExchangeAdapter__factory(this._deployerSigner).deploy(uniswapV2Router); + public async deployUniswapV2IndexExchangeAdapter( + uniswapV2Router: Address, + ): Promise { + return await new UniswapV2IndexExchangeAdapter__factory(this._deployerSigner).deploy( + uniswapV2Router, + ); } - public async deployAaveGovernanceV2Adapter(aaveGovernanceV2: Address, aaveToken: Address): Promise { - return await new AaveGovernanceV2Adapter__factory(this._deployerSigner).deploy(aaveGovernanceV2, aaveToken); + public async deployAaveGovernanceV2Adapter( + aaveGovernanceV2: Address, + aaveToken: Address, + ): Promise { + return await new AaveGovernanceV2Adapter__factory(this._deployerSigner).deploy( + aaveGovernanceV2, + aaveToken, + ); } public async deployCompClaimAdapter(comptrollerAddress: Address): Promise { return await new CompClaimAdapter__factory(this._deployerSigner).deploy(comptrollerAddress); } - public async deployBalancerV1IndexExchangeAdapter(balancerProxy: Address): Promise { - return await new BalancerV1IndexExchangeAdapter__factory(this._deployerSigner).deploy(balancerProxy); + public async deployBalancerV1IndexExchangeAdapter( + balancerProxy: Address, + ): Promise { + return await new BalancerV1IndexExchangeAdapter__factory(this._deployerSigner).deploy( + balancerProxy, + ); } - public async deployCompoundLikeGovernanceAdapter(governanceAlpha: Address, governanceToken: Address): Promise { - return await new CompoundLikeGovernanceAdapter__factory(this._deployerSigner).deploy(governanceAlpha, governanceToken); + public async deployCompoundLikeGovernanceAdapter( + governanceAlpha: Address, + governanceToken: Address, + ): Promise { + return await new CompoundLikeGovernanceAdapter__factory(this._deployerSigner).deploy( + governanceAlpha, + governanceToken, + ); } - public async deployCompoundBravoGovernanceAdapter(governorBravo: Address, governanceToken: Address): Promise { - return await new CompoundBravoGovernanceAdapter__factory(this._deployerSigner).deploy(governorBravo, governanceToken); + public async deployCompoundBravoGovernanceAdapter( + governorBravo: Address, + governanceToken: Address, + ): Promise { + return await new CompoundBravoGovernanceAdapter__factory(this._deployerSigner).deploy( + governorBravo, + governanceToken, + ); } public async deployCurveStakingAdapter(gaugeController: Address): Promise { return await new CurveStakingAdapter__factory(this._deployerSigner).deploy(gaugeController); } - public async deployRgtMigrationWrapAdapter(pegExchanger: Address): Promise { + public async deployRgtMigrationWrapAdapter( + pegExchanger: Address, + ): Promise { return await new RgtMigrationWrapAdapter__factory(this._deployerSigner).deploy(pegExchanger); } public async deployUniswapPairPriceAdapter( controller: Address, uniswapFactory: Address, - uniswapPools: Address[] + uniswapPools: Address[], ): Promise { - return await new UniswapPairPriceAdapter__factory(this._deployerSigner).deploy(controller, uniswapFactory, uniswapPools); + return await new UniswapPairPriceAdapter__factory(this._deployerSigner).deploy( + controller, + uniswapFactory, + uniswapPools, + ); } - public async getUniswapPairPriceAdapter(uniswapAdapterAddress: Address): Promise { - return await new UniswapPairPriceAdapter__factory(this._deployerSigner).attach(uniswapAdapterAddress); + public async getUniswapPairPriceAdapter( + uniswapAdapterAddress: Address, + ): Promise { + return await new UniswapPairPriceAdapter__factory(this._deployerSigner).attach( + uniswapAdapterAddress, + ); } - public async deployUniswapV3IndexExchangeAdapter(router: Address): Promise { + public async deployUniswapV3IndexExchangeAdapter( + router: Address, + ): Promise { return await new UniswapV3IndexExchangeAdapter__factory(this._deployerSigner).deploy(router); } - public async deployZeroExApiAdapter(zeroExAddress: Address, wethAddress: Address): Promise { - return await new ZeroExApiAdapter__factory(this._deployerSigner).deploy(zeroExAddress, wethAddress); + public async deployZeroExApiAdapter( + zeroExAddress: Address, + wethAddress: Address, + ): Promise { + return await new ZeroExApiAdapter__factory(this._deployerSigner).deploy( + zeroExAddress, + wethAddress, + ); } - public async deploySnapshotGovernanceAdapter(delegateRegistry: Address): Promise { - return await new SnapshotGovernanceAdapter__factory(this._deployerSigner).deploy(delegateRegistry); + public async deploySnapshotGovernanceAdapter( + delegateRegistry: Address, + ): Promise { + return await new SnapshotGovernanceAdapter__factory(this._deployerSigner).deploy( + delegateRegistry, + ); } public async deploySynthetixExchangeAdapter( synthetixExchangerAddress: Address, ): Promise { return await new SynthetixExchangeAdapter__factory(this._deployerSigner).deploy( - synthetixExchangerAddress + synthetixExchangerAddress, ); } - public async deployUniswapV3ExchangeAdapter(swapRouter: Address): Promise { + public async deployUniswapV3ExchangeAdapter( + swapRouter: Address, + ): Promise { return await new UniswapV3ExchangeAdapter__factory(this._deployerSigner).deploy(swapRouter); } - public async deployUniswapV3ExchangeAdapterV2(swapRouter: Address): Promise { + public async deployUniswapV3ExchangeAdapterV2( + swapRouter: Address, + ): Promise { return await new UniswapV3ExchangeAdapterV2__factory(this._deployerSigner).deploy(swapRouter); } - public async deployKyberV3IndexExchangeAdapter(dmmRouter: Address, dmmFactory: Address): Promise { - return await new KyberV3IndexExchangeAdapter__factory(this._deployerSigner).deploy(dmmRouter, dmmFactory); + public async deployKyberV3IndexExchangeAdapter( + dmmRouter: Address, + dmmFactory: Address, + ): Promise { + return await new KyberV3IndexExchangeAdapter__factory(this._deployerSigner).deploy( + dmmRouter, + dmmFactory, + ); } - public async deployCompoundWrapV2Adapter(libraryName: string, libraryAddress: Address): Promise { + public async deployCompoundWrapV2Adapter( + libraryName: string, + libraryAddress: Address, + ): Promise { return await new CompoundWrapV2Adapter__factory( // @ts-ignore { [libraryName]: libraryAddress, }, - this._deployerSigner + this._deployerSigner, ).deploy(); } @@ -190,7 +267,31 @@ export default class DeployAdapters { return await new AaveV2WrapV2Adapter__factory(this._deployerSigner).deploy(lendingPool); } - public async deployCurveStEthExchangeAdapter(weth: Address, steth: Address, exchange: Address): Promise { - return await new CurveStEthExchangeAdapter__factory(this._deployerSigner).deploy(weth, steth, exchange); + public async deployCurveExchangeAdapter( + tokenA: Address, + tokenB: Address, + tokenAIndex: BigNumber, + tokenbBIndex: BigNumber, + exchange: Address, + ): Promise { + return await new CurveExchangeAdapter__factory(this._deployerSigner).deploy( + tokenA, + tokenB, + tokenAIndex, + tokenbBIndex, + exchange, + ); + } + + public async deployCurveStEthExchangeAdapter( + weth: Address, + steth: Address, + exchange: Address, + ): Promise { + return await new CurveStEthExchangeAdapter__factory(this._deployerSigner).deploy( + weth, + steth, + exchange, + ); } } diff --git a/utils/deploys/deployExternal.ts b/utils/deploys/deployExternal.ts index 891235f05..1d0221682 100644 --- a/utils/deploys/deployExternal.ts +++ b/utils/deploys/deployExternal.ts @@ -18,7 +18,7 @@ import { } from "./../contracts/compound"; import { WETH9, - DelegateRegistry + DelegateRegistry, } from "./../contracts"; import { Address } from "./../types"; @@ -40,7 +40,7 @@ import { WhitePaperInterestRateModel__factory } from "../../typechain/factories/ import { CurveDeposit, CurvePoolERC20, - CurveStEthStableswapMock, + CurveStableswapMock, CRVToken, GaugeController, LiquidityGauge, @@ -50,7 +50,7 @@ import { } from "../contracts/curve"; import { CurvePoolERC20__factory } from "../../typechain/factories/CurvePoolERC20__factory"; -import { CurveStEthStableswapMock__factory } from "../../typechain/factories/CurveStEthStableswapMock__factory"; +import { CurveStableswapMock__factory } from "../../typechain/factories/CurveStableswapMock__factory"; import { Stableswap__factory } from "../../typechain/factories/Stableswap__factory"; import { CurveDeposit__factory } from "../../typechain/factories/CurveDeposit__factory"; import { CRVToken__factory } from "../../typechain/factories/CRVToken__factory"; @@ -494,13 +494,20 @@ export default class DeployExternalContracts { return await new CurvePoolERC20__factory(this._deployerSigner).deploy(_name, _symbol, _decimals, _supply); } - public async getCurveStEthStableswapMock(): Promise { - return await CurveStEthStableswapMock__factory.connect( + public async getCurveStEthStableswapMock(): Promise { + return await CurveStableswapMock__factory.connect( dependencies.CURVE_ETH_STETH_EXCHANGE[1], this._deployerSigner ); } + public async getCurveWethStethStableswapMock(): Promise { + return await CurveStableswapMock__factory.connect( + dependencies.CURVE_WETH_STETH_EXCHANGE[1], + this._deployerSigner + ); + } + public async deployStableswap( _coins: [string, string, string, string], _underlying_coins: [string, string, string, string], diff --git a/utils/deploys/deployMocks.ts b/utils/deploys/deployMocks.ts index 09356fcf2..ea2884c88 100644 --- a/utils/deploys/deployMocks.ts +++ b/utils/deploys/deployMocks.ts @@ -12,7 +12,7 @@ import { ContractCallerMock, CompoundMock, ComptrollerMock, - CurveStEthStableswapMock, + CurveStableswapMock, CustomSetValuerMock, DebtIssuanceMock, DebtModuleMock, @@ -73,7 +73,7 @@ import { ClaimAdapterMock__factory } from "../../typechain/factories/ClaimAdapte import { CompoundMock__factory } from "../../typechain/factories/CompoundMock__factory"; import { ComptrollerMock__factory } from "../../typechain/factories/ComptrollerMock__factory"; import { ContractCallerMock__factory } from "../../typechain/factories/ContractCallerMock__factory"; -import { CurveStEthStableswapMock__factory } from "../../typechain/factories/CurveStEthStableswapMock__factory"; +import { CurveStableswapMock__factory } from "../../typechain/factories/CurveStableswapMock__factory"; import { CustomSetValuerMock__factory } from "../../typechain/factories/CustomSetValuerMock__factory"; import { DebtIssuanceMock__factory } from "../../typechain/factories/DebtIssuanceMock__factory"; import { DebtModuleMock__factory } from "../../typechain/factories/DebtModuleMock__factory"; @@ -183,8 +183,8 @@ export default class DeployMocks { return await new GovernanceAdapterMock__factory(this._deployerSigner).deploy(initialProposalId); } - public async deployCurveStEthStableswapMock(coins: Address[]): Promise { - return await new CurveStEthStableswapMock__factory(this._deployerSigner).deploy(coins); + public async deployCurveStableswapMock(coins: Address[]): Promise { + return await new CurveStableswapMock__factory(this._deployerSigner).deploy(coins); } public async deployOneInchExchangeMock( diff --git a/utils/fixtures/curveFixture.ts b/utils/fixtures/curveFixture.ts index e41d9b8b1..64329283b 100644 --- a/utils/fixtures/curveFixture.ts +++ b/utils/fixtures/curveFixture.ts @@ -5,7 +5,7 @@ import { Signer } from "ethers"; import { CurvePoolERC20 } from "../../typechain/CurvePoolERC20"; import { Stableswap } from "../../typechain/Stableswap"; import { CurveDeposit } from "../../typechain/CurveDeposit"; -import { CurveStEthStableswapMock } from "../../typechain/CurveStEthStableswapMock"; +import { CurveStableswapMock } from "../../typechain/CurveStableswapMock"; import { CRVToken } from "../../typechain/CRVToken"; import { GaugeController } from "../../typechain/GaugeController"; import { Minter } from "../../typechain/Minter"; @@ -99,7 +99,11 @@ export class CurveFixture { return gauge; } - public async getForkedCurveStEthStableswapPool(): Promise { + public async getForkedCurveStEthStableswapPool(): Promise { return await this._deployer.external.getCurveStEthStableswapMock(); } + + public async getForkedWethStethCurveStableswapPool(): Promise { + return await this._deployer.external.getCurveWethStethStableswapMock(); + } }