diff --git a/contracts/exchangeIssuance/FlashMintLeveraged.sol b/contracts/exchangeIssuance/FlashMintLeveraged.sol
new file mode 100644
index 00000000..350cb224
--- /dev/null
+++ b/contracts/exchangeIssuance/FlashMintLeveraged.sol
@@ -0,0 +1,1242 @@
+/*
+ Copyright 2022 Index Cooperative
+
+ 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 { Address } from "@openzeppelin/contracts/utils/Address.sol";
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { Math } from "@openzeppelin/contracts/math/Math.sol";
+import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
+import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
+import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
+
+import { IAToken } from "../interfaces/IAToken.sol";
+import { IAaveLeverageModule } from "../interfaces/IAaveLeverageModule.sol";
+import { IDebtIssuanceModule } from "../interfaces/IDebtIssuanceModule.sol";
+import { IController } from "../interfaces/IController.sol";
+import { ISetToken } from "../interfaces/ISetToken.sol";
+import { IWETH } from "../interfaces/IWETH.sol";
+import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
+import { UniSushiV2Library } from "../../external/contracts/UniSushiV2Library.sol";
+import { FlashLoanReceiverBaseV2 } from "../../external/contracts/aaveV2/FlashLoanReceiverBaseV2.sol";
+import { DEXAdapter } from "./DEXAdapter.sol";
+import {IVault, IFlashLoanRecipient} from "../interfaces/external/balancer-v2/IVault.sol";
+import {IPool} from "../interfaces/IPool.sol";
+
+/**
+ * @title FlashMintLeveraged
+ * @author Index Coop
+ *
+ * Contract for issuing and redeeming a leveraged Set Token
+ * Supports all tokens with one collateral Position in the form of an AToken and one debt position
+ * Both the collateral as well as the debt token have to be available for flashloan from balancer and be
+ * tradeable against each other on Sushi / Quickswap
+ */
+contract FlashMintLeveraged is ReentrancyGuard, IFlashLoanRecipient{
+
+ using DEXAdapter for DEXAdapter.Addresses;
+ using Address for address payable;
+ using SafeMath for uint256;
+ using PreciseUnitMath for uint256;
+ using SafeERC20 for IERC20;
+ using SafeERC20 for ISetToken;
+
+ /* ============ Structs ============ */
+
+ struct LeveragedTokenData {
+ address collateralAToken;
+ address collateralToken;
+ uint256 collateralAmount;
+ address debtToken;
+ uint256 debtAmount;
+ }
+
+
+
+ struct DecodedParams {
+ ISetToken setToken;
+ uint256 setAmount;
+ address originalSender;
+ bool isIssuance;
+ address paymentToken;
+ uint256 limitAmount;
+ LeveragedTokenData leveragedTokenData;
+ DEXAdapter.SwapData collateralAndDebtSwapData;
+ DEXAdapter.SwapData paymentTokenSwapData;
+ }
+
+ /* ============ Constants ============= */
+
+ uint256 constant private MAX_UINT256 = type(uint256).max;
+ uint256 public constant ROUNDING_ERROR_MARGIN = 2;
+
+ /* ============ State Variables ============ */
+
+ IController public immutable setController;
+ IDebtIssuanceModule public immutable debtIssuanceModule;
+ IAaveLeverageModule public immutable aaveLeverageModule;
+ DEXAdapter.Addresses public addresses;
+ IVault public immutable balancerV2Vault;
+ IPool public immutable LENDING_POOL;
+ address private flashLoanBenefactor;
+
+ /* ============ Events ============ */
+
+ event FlashMint(
+ address indexed _recipient, // The recipient address of the issued SetTokens
+ ISetToken indexed _setToken, // The issued SetToken
+ address indexed _inputToken, // The address of the input asset(ERC20/ETH) used to issue the SetTokens
+ uint256 _amountInputToken, // The amount of input tokens used for issuance
+ uint256 _amountSetIssued // The amount of SetTokens received by the recipient
+ );
+
+ event FlashRedeem(
+ address indexed _recipient, // The recipient address which redeemed the SetTokens
+ ISetToken indexed _setToken, // The redeemed SetToken
+ address indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
+ uint256 _amountSetRedeemed, // The amount of SetTokens redeemed for output tokens
+ uint256 _amountOutputToken // The amount of output tokens received by the recipient
+ );
+
+ /* ============ Modifiers ============ */
+
+ modifier onlyBalancerV2Vault() {
+ require(msg.sender == address(balancerV2Vault), "ExchangeIssuance: BalancerV2 Vault ONLY");
+ _;
+ }
+
+ modifier isValidPath(
+ address[] memory _path,
+ address _inputToken,
+ address _outputToken
+ )
+ {
+ if(_inputToken != _outputToken){
+ require(
+ _path[0] == _inputToken || (_inputToken == addresses.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
+ "ExchangeIssuance: INPUT_TOKEN_NOT_IN_PATH"
+ );
+ require(
+ _path[_path.length-1] == _outputToken ||
+ (_outputToken == addresses.weth && _path[_path.length-1] == DEXAdapter.ETH_ADDRESS),
+ "ExchangeIssuance: OUTPUT_TOKEN_NOT_IN_PATH"
+ );
+ }
+ _;
+ }
+
+
+ /* ============ Constructor ============ */
+
+ /**
+ * Sets various contract addresses
+ *
+ * @param _addresses dex adapter addreses
+ * @param _setController SetToken controller used to verify a given token is a set
+ * @param _debtIssuanceModule DebtIssuanceModule used to issue and redeem tokens
+ * @param _aaveLeverageModule AaveLeverageModule to sync before every issuance / redemption
+ * @param _aaveV3Pool Address of address provider for aaves addresses
+ * @param _vault Balancer Vault to flashloan from
+ */
+ constructor(
+ DEXAdapter.Addresses memory _addresses,
+ IController _setController,
+ IDebtIssuanceModule _debtIssuanceModule,
+ IAaveLeverageModule _aaveLeverageModule,
+ address _aaveV3Pool,
+ address _vault
+ )
+ public
+ {
+ setController = _setController;
+ debtIssuanceModule = _debtIssuanceModule;
+ aaveLeverageModule = _aaveLeverageModule;
+ addresses = _addresses;
+ LENDING_POOL = IPool(_aaveV3Pool);
+ balancerV2Vault = IVault(_vault);
+ }
+
+ /* ============ External Functions ============ */
+
+ /**
+ * Returns the collateral / debt token addresses and amounts for a leveraged index
+ *
+ * @param _setToken Address of the SetToken to be issued / redeemed
+ * @param _setAmount Amount of SetTokens to issue / redeem
+ * @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
+ *
+ * @return Struct containing the collateral / debt token addresses and amounts
+ */
+ function getLeveragedTokenData(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ bool _isIssuance
+ )
+ external
+ view
+ returns (LeveragedTokenData memory)
+ {
+ return _getLeveragedTokenData(_setToken, _setAmount, _isIssuance);
+ }
+
+ /**
+ * Runs all the necessary approval functions required for a given ERC20 token.
+ * This function can be called when a new token is added to a SetToken during a
+ * rebalance.
+ *
+ * @param _token Address of the token which needs approval
+ */
+ function approveToken(IERC20 _token) external {
+ _approveToken(_token);
+ }
+
+ /**
+ * Gets the input cost of issuing a given amount of a set token. This
+ * function is not marked view, but should be static called from frontends.
+ * This constraint is due to the need to interact with the Uniswap V3 quoter
+ * contract and call sync on AaveLeverageModule. Note: If the two SwapData
+ * paths contain the same tokens, there will be a slight error introduced
+ * in the result.
+ *
+ * @param _setToken the set token to issue
+ * @param _setAmount amount of set tokens
+ * @param _swapDataDebtForCollateral swap data for the debt to collateral swap
+ * @param _swapDataInputToken swap data for the input token to collateral swap
+ *
+ * @return the amount of input tokens required to perfrom the issuance
+ */
+ function getIssueExactSet(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ DEXAdapter.SwapData memory _swapDataDebtForCollateral,
+ DEXAdapter.SwapData memory _swapDataInputToken
+ )
+ external
+ returns (uint256)
+ {
+ aaveLeverageModule.sync(_setToken);
+ LeveragedTokenData memory issueInfo = _getLeveragedTokenData(_setToken, _setAmount, true);
+ uint256 collateralOwed = issueInfo.collateralAmount.preciseMul(1.0009 ether);
+ uint256 borrowSaleProceeds = DEXAdapter.getAmountOut(addresses, _swapDataDebtForCollateral, issueInfo.debtAmount);
+ collateralOwed = collateralOwed.sub(borrowSaleProceeds);
+ return DEXAdapter.getAmountIn(addresses, _swapDataInputToken, collateralOwed);
+ }
+
+ /**
+ * Gets the proceeds of a redemption of a given amount of a set token. This
+ * function is not marked view, but should be static called from frontends.
+ * This constraint is due to the need to interact with the Uniswap V3 quoter
+ * contract and call sync on AaveLeverageModule. Note: If the two SwapData
+ * paths contain the same tokens, there will be a slight error introduced
+ * in the result.
+ *
+ * @param _setToken the set token to issue
+ * @param _setAmount amount of set tokens
+ * @param _swapDataCollateralForDebt swap data for the collateral to debt swap
+ * @param _swapDataOutputToken swap data for the collateral token to the output token
+ *
+ * @return amount of _outputToken that would be obtained from the redemption
+ */
+ function getRedeemExactSet(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ DEXAdapter.SwapData memory _swapDataCollateralForDebt,
+ DEXAdapter.SwapData memory _swapDataOutputToken
+ )
+ external
+ returns (uint256)
+ {
+ aaveLeverageModule.sync(_setToken);
+ LeveragedTokenData memory redeemInfo = _getLeveragedTokenData(_setToken, _setAmount, false);
+ uint256 debtOwed = redeemInfo.debtAmount.preciseMul(1.0009 ether);
+ uint256 debtPurchaseCost = DEXAdapter.getAmountIn(addresses, _swapDataCollateralForDebt, debtOwed);
+ uint256 extraCollateral = redeemInfo.collateralAmount.sub(debtPurchaseCost);
+ return DEXAdapter.getAmountOut(addresses, _swapDataOutputToken, extraCollateral);
+ }
+
+ /**
+ * Trigger redemption of set token to pay the user with Eth
+ *
+ * @param _setToken Set token to redeem
+ * @param _setAmount Amount to redeem
+ * @param _minAmountOutputToken Minimum amount of ETH to send to the user
+ * @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
+ * @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Eth
+ */
+ function redeemExactSetForETH(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ uint256 _minAmountOutputToken,
+ DEXAdapter.SwapData memory _swapDataCollateralForDebt,
+ DEXAdapter.SwapData memory _swapDataOutputToken
+ )
+ external
+ nonReentrant
+ {
+ _initiateRedemption(
+ _setToken,
+ _setAmount,
+ DEXAdapter.ETH_ADDRESS,
+ _minAmountOutputToken,
+ _swapDataCollateralForDebt,
+ _swapDataOutputToken
+ );
+ }
+
+ /**
+ * Trigger redemption of set token to pay the user with an arbitrary ERC20
+ *
+ * @param _setToken Set token to redeem
+ * @param _setAmount Amount to redeem
+ * @param _outputToken Address of the ERC20 token to send to the user
+ * @param _minAmountOutputToken Minimum amount of output token to send to the user
+ * @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
+ * @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
+ */
+ function redeemExactSetForERC20(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ address _outputToken,
+ uint256 _minAmountOutputToken,
+ DEXAdapter.SwapData memory _swapDataCollateralForDebt,
+ DEXAdapter.SwapData memory _swapDataOutputToken
+ )
+ external
+ nonReentrant
+ {
+ _initiateRedemption(
+ _setToken,
+ _setAmount,
+ _outputToken,
+ _minAmountOutputToken,
+ _swapDataCollateralForDebt,
+ _swapDataOutputToken
+ );
+ }
+
+ /**
+ * Trigger issuance of set token paying with any arbitrary ERC20 token
+ *
+ * @param _setToken Set token to issue
+ * @param _setAmount Amount to issue
+ * @param _inputToken Input token to pay with
+ * @param _maxAmountInputToken Maximum amount of input token to spend
+ * @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
+ * @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
+ */
+ function issueExactSetFromERC20(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ address _inputToken,
+ uint256 _maxAmountInputToken,
+ DEXAdapter.SwapData memory _swapDataDebtForCollateral,
+ DEXAdapter.SwapData memory _swapDataInputToken
+ )
+ external
+ nonReentrant
+ {
+ _initiateIssuance(
+ _setToken,
+ _setAmount,
+ _inputToken,
+ _maxAmountInputToken,
+ _swapDataDebtForCollateral,
+ _swapDataInputToken
+ );
+ }
+
+ /**
+ * Trigger issuance of set token paying with Eth
+ *
+ * @param _setToken Set token to issue
+ * @param _setAmount Amount to issue
+ * @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
+ * @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from eth to collateral token
+ */
+ function issueExactSetFromETH(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ DEXAdapter.SwapData memory _swapDataDebtForCollateral,
+ DEXAdapter.SwapData memory _swapDataInputToken
+ )
+ external
+ payable
+ nonReentrant
+ {
+ _initiateIssuance(
+ _setToken,
+ _setAmount,
+ DEXAdapter.ETH_ADDRESS,
+ msg.value,
+ _swapDataDebtForCollateral,
+ _swapDataInputToken
+ );
+ }
+
+ /**
+ * This is the callback function that will be called by the Balancerv2 Pool after flashloaned tokens have been sent
+ * to this contract.
+ * After exiting this function the Vault enforces that we transfer back the loaned tokens + interest. If that check fails
+ * the whole transaction gets reverted
+ *
+ * @param tokens Addresses of all assets that were borrowed
+ * @param amounts Amounts that were borrowed
+ * @param feeAmounts Interest to be paid on top of borrowed amount
+ * @param userData Encoded bytestring of other parameters from the original contract call to be used downstream
+ *
+ */
+ function receiveFlashLoan(
+ IERC20[] memory tokens,
+ uint256[] memory amounts,
+ uint256[] memory feeAmounts,
+ bytes memory userData
+ )
+ external
+ override
+ onlyBalancerV2Vault
+ {
+
+ DecodedParams memory decodedParams = abi.decode(userData, (DecodedParams));
+ require(flashLoanBenefactor == decodedParams.originalSender, "Flashloan not initiated by this contract");
+
+ if(decodedParams.isIssuance){
+ _performIssuance(address(tokens[0]), amounts[0], feeAmounts[0], decodedParams);
+ } else {
+ _performRedemption(address(tokens[0]), amounts[0], feeAmounts[0], decodedParams);
+ }
+
+ for(uint256 i = 0; i < tokens.length; i++) {
+ tokens[i].safeTransfer(address(balancerV2Vault), amounts[i]+ feeAmounts[i]);
+ }
+
+ }
+
+ /**
+ * Runs all the necessary approval functions required for a list of ERC20 tokens.
+ *
+ * @param _tokens Addresses of the tokens which need approval
+ */
+ function approveTokens(IERC20[] memory _tokens) external {
+ for (uint256 i = 0; i < _tokens.length; i++) {
+ _approveToken(_tokens[i]);
+ }
+ }
+
+ /**
+ * Runs all the necessary approval functions required before issuing
+ * or redeeming a SetToken. This function need to be called only once before the first time
+ * this smart contract is used on any particular SetToken.
+ *
+ * @param _setToken Address of the SetToken being initialized
+ */
+ function approveSetToken(ISetToken _setToken) external {
+ LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, 1 ether, true);
+
+ _approveToken(IERC20(leveragedTokenData.collateralAToken));
+ _approveTokenToLendingPool(IERC20(leveragedTokenData.collateralToken));
+
+ _approveToken(IERC20(leveragedTokenData.debtToken));
+ _approveTokenToLendingPool(IERC20(leveragedTokenData.debtToken));
+ }
+
+ /* ============ Internal Functions ============ */
+
+ /**
+ * Performs all the necessary steps for issuance using the collateral tokens obtained in the flashloan
+ *
+ * @param _collateralToken Address of the underlying collateral token that was loaned
+ * @param _collateralTokenAmountNet Amount of collateral token that was received as flashloan
+ * @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
+ * @param _decodedParams Struct containing token addresses / amounts to perform issuance
+ */
+ function _performIssuance(
+ address _collateralToken,
+ uint256 _collateralTokenAmountNet,
+ uint256 _premium,
+ DecodedParams memory _decodedParams
+ )
+ internal
+ {
+ // Deposit collateral token obtained from flashloan to get the respective aToken position required for issuance
+ _depositCollateralToken(_collateralToken, _collateralTokenAmountNet);
+
+ // Issue set using the aToken returned by deposit step
+ _issueSet(_decodedParams.setToken, _decodedParams.setAmount, _decodedParams.originalSender);
+ // Obtain necessary collateral tokens to repay flashloan
+ uint amountInputTokenSpent = _obtainCollateralTokens(
+ _collateralToken,
+ _collateralTokenAmountNet + _premium,
+ _decodedParams
+ );
+ require(amountInputTokenSpent <= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT INPUT AMOUNT");
+ }
+
+ /**
+ * Performs all the necessary steps for redemption using the debt tokens obtained in the flashloan
+ *
+ * @param _debtToken Address of the debt token that was loaned
+ * @param _debtTokenAmountNet Amount of debt token that was received as flashloan
+ * @param _premium Premium / Interest that has to be returned to the lending pool on top of the loaned amount
+ * @param _decodedParams Struct containing token addresses / amounts to perform redemption
+ */
+ function _performRedemption(
+ address _debtToken,
+ uint256 _debtTokenAmountNet,
+ uint256 _premium,
+ DecodedParams memory _decodedParams
+ )
+ internal
+ {
+ // Redeem set using debt tokens obtained from flashloan
+ _redeemSet(
+ _decodedParams.setToken,
+ _decodedParams.setAmount,
+ _decodedParams.originalSender
+ );
+ // Withdraw underlying collateral token from the aToken position returned by redeem step
+ _withdrawCollateralToken(
+ _decodedParams.leveragedTokenData.collateralToken,
+ _decodedParams.leveragedTokenData.collateralAmount - ROUNDING_ERROR_MARGIN
+ );
+ // Obtain debt tokens required to repay flashloan by swapping the underlying collateral tokens obtained in withdraw step
+ uint256 collateralTokenSpent = _swapCollateralForDebtToken(
+ _debtTokenAmountNet + _premium,
+ _debtToken,
+ _decodedParams.leveragedTokenData.collateralAmount,
+ _decodedParams.leveragedTokenData.collateralToken,
+ _decodedParams.collateralAndDebtSwapData
+ );
+ // Liquidate remaining collateral tokens for the payment token specified by user
+ uint256 amountOutputToken = _liquidateCollateralTokens(
+ collateralTokenSpent,
+ _decodedParams.setToken,
+ _decodedParams.setAmount,
+ _decodedParams.originalSender,
+ _decodedParams.paymentToken,
+ _decodedParams.limitAmount,
+ _decodedParams.leveragedTokenData.collateralToken,
+ _decodedParams.leveragedTokenData.collateralAmount - 2*ROUNDING_ERROR_MARGIN,
+ _decodedParams.paymentTokenSwapData
+ );
+ require(amountOutputToken >= _decodedParams.limitAmount, "ExchangeIssuance: INSUFFICIENT OUTPUT AMOUNT");
+ }
+
+
+ /**
+ * Returns the collateral / debt token addresses and amounts for a leveraged index
+ *
+ * @param _setToken Address of the SetToken to be issued / redeemed
+ * @param _setAmount Amount of SetTokens to issue / redeem
+ * @param _isIssuance Boolean indicating if the SetToken is to be issued or redeemed
+ *
+ * @return Struct containing the collateral / debt token addresses and amounts
+ */
+ function _getLeveragedTokenData(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ bool _isIssuance
+ )
+ internal
+ view
+ returns (LeveragedTokenData memory)
+ {
+ address[] memory components;
+ uint256[] memory equityPositions;
+ uint256[] memory debtPositions;
+
+
+ if(_isIssuance){
+ (components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentIssuanceUnits(_setToken, _setAmount);
+ } else {
+ (components, equityPositions, debtPositions) = debtIssuanceModule.getRequiredComponentRedemptionUnits(_setToken, _setAmount);
+ }
+
+ require(components.length == 2, "ExchangeIssuance: TOO MANY COMPONENTS");
+ require(equityPositions[0] == 0 || equityPositions[1] == 0, "ExchangeIssuance: TOO MANY EQUITY POSITIONS");
+ require(debtPositions[0] == 0 || debtPositions[1] == 0, "ExchangeIssuance: TOO MANY DEBT POSITIONS");
+
+ if(equityPositions[0] > 0){
+ return LeveragedTokenData(
+ components[0],
+ IAToken(components[0]).UNDERLYING_ASSET_ADDRESS(),
+ equityPositions[0] + ROUNDING_ERROR_MARGIN,
+ components[1],
+ debtPositions[1]
+ );
+ } else {
+ return LeveragedTokenData(
+ components[1],
+ IAToken(components[1]).UNDERLYING_ASSET_ADDRESS(),
+ equityPositions[1] + ROUNDING_ERROR_MARGIN,
+ components[0],
+ debtPositions[0]
+ );
+ }
+ }
+
+
+
+ /**
+ * Approves max amount of given token to all exchange routers and the debt issuance module
+ *
+ * @param _token Address of the token to be approved
+ */
+ function _approveToken(IERC20 _token) internal {
+ _safeApprove(_token, address(debtIssuanceModule), MAX_UINT256);
+ }
+
+ /**
+ * Initiates a flashloan call with the correct parameters for issuing set tokens in the callback
+ * Borrows correct amount of collateral token and and forwards encoded memory to controll issuance in the callback.
+ *
+ * @param _setToken Address of the SetToken being initialized
+ * @param _setAmount Amount of the SetToken being initialized
+ * @param _inputToken Address of the input token to pay with
+ * @param _maxAmountInputToken Maximum amount of input token to pay
+ * @param _swapDataDebtForCollateral Data (token addresses and fee levels) to describe the swap path from Debt to collateral token
+ * @param _swapDataInputToken Data (token addresses and fee levels) to describe the swap path from input to collateral token
+ */
+ function _initiateIssuance(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ address _inputToken,
+ uint256 _maxAmountInputToken,
+ DEXAdapter.SwapData memory _swapDataDebtForCollateral,
+ DEXAdapter.SwapData memory _swapDataInputToken
+ )
+ internal
+ {
+ aaveLeverageModule.sync(_setToken);
+ LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, true);
+
+ address[] memory assets = new address[](1);
+ assets[0] = leveragedTokenData.collateralToken;
+ uint[] memory amounts = new uint[](1);
+ amounts[0] = leveragedTokenData.collateralAmount;
+
+ bytes memory params = abi.encode(
+ DecodedParams(
+ _setToken,
+ _setAmount,
+ msg.sender,
+ true,
+ _inputToken,
+ _maxAmountInputToken,
+ leveragedTokenData,
+ _swapDataDebtForCollateral,
+ _swapDataInputToken
+ )
+ );
+
+ _flashloan(assets, amounts, params);
+
+ }
+
+ /**
+ * Initiates a flashloan call with the correct parameters for redeeming set tokens in the callback
+ *
+ * @param _setToken Address of the SetToken to redeem
+ * @param _setAmount Amount of the SetToken to redeem
+ * @param _outputToken Address of token to return to the user
+ * @param _minAmountOutputToken Minimum amount of output token to receive
+ * @param _swapDataCollateralForDebt Data (token path and fee levels) describing the swap from Collateral Token to Debt Token
+ * @param _swapDataOutputToken Data (token path and fee levels) describing the swap from Collateral Token to Output token
+ */
+ function _initiateRedemption(
+ ISetToken _setToken,
+ uint256 _setAmount,
+ address _outputToken,
+ uint256 _minAmountOutputToken,
+ DEXAdapter.SwapData memory _swapDataCollateralForDebt,
+ DEXAdapter.SwapData memory _swapDataOutputToken
+ )
+ internal
+ {
+ aaveLeverageModule.sync(_setToken);
+ LeveragedTokenData memory leveragedTokenData = _getLeveragedTokenData(_setToken, _setAmount, false);
+
+ address[] memory assets = new address[](1);
+ assets[0] = leveragedTokenData.debtToken;
+ uint[] memory amounts = new uint[](1);
+ amounts[0] = leveragedTokenData.debtAmount;
+
+ bytes memory params = abi.encode(
+ DecodedParams(
+ _setToken,
+ _setAmount,
+ msg.sender,
+ false,
+ _outputToken,
+ _minAmountOutputToken,
+ leveragedTokenData,
+ _swapDataCollateralForDebt,
+ _swapDataOutputToken
+ )
+ );
+
+ _flashloan(assets, amounts, params);
+
+ }
+
+ /**
+ * Gets rid of the obtained collateral tokens from redemption by either sending them to the user
+ * directly or converting them to the payment token and sending those out.
+ *
+ * @param _collateralTokenSpent Amount of collateral token spent to obtain the debt token required for redemption
+ * @param _setToken Address of the SetToken to be issued
+ * @param _setAmount Amount of SetTokens to issue
+ * @param _originalSender Address of the user who initiated the redemption
+ * @param _outputToken Address of token to return to the user
+ * @param _collateralToken Address of the collateral token to sell
+ * @param _collateralAmount Amount of collateral token to sell
+ * @param _minAmountOutputToken Minimum amount of output token to return to the user
+ * @param _swapData Struct containing path and fee data for swap
+ *
+ * @return Amount of output token returned to the user
+ */
+ function _liquidateCollateralTokens(
+ uint256 _collateralTokenSpent,
+ ISetToken _setToken,
+ uint256 _setAmount,
+ address _originalSender,
+ address _outputToken,
+ uint256 _minAmountOutputToken,
+ address _collateralToken,
+ uint256 _collateralAmount,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ returns (uint256)
+ {
+ require(_collateralAmount >= _collateralTokenSpent, "ExchangeIssuance: OVERSPENT COLLATERAL TOKEN");
+ uint256 amountToReturn = _collateralAmount.sub(_collateralTokenSpent);
+ uint256 outputAmount;
+ if(_outputToken == DEXAdapter.ETH_ADDRESS){
+ outputAmount = _liquidateCollateralTokensForETH(
+ _collateralToken,
+ amountToReturn,
+ _originalSender,
+ _minAmountOutputToken,
+ _swapData
+ );
+ } else {
+ outputAmount = _liquidateCollateralTokensForERC20(
+ _collateralToken,
+ amountToReturn,
+ _originalSender,
+ IERC20(_outputToken),
+ _minAmountOutputToken,
+ _swapData
+ );
+ }
+ emit FlashMint(_originalSender, _setToken, _outputToken, _setAmount, outputAmount);
+ return outputAmount;
+ }
+
+ /**
+ * Returns the collateralToken directly to the user
+ *
+ * @param _collateralToken Address of the the collateral token
+ * @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
+ * @param _originalSender Address of the original sender to return the tokens to
+ */
+ function _returnCollateralTokensToSender(
+ address _collateralToken,
+ uint256 _collateralRemaining,
+ address _originalSender
+ )
+ internal
+ {
+ IERC20(_collateralToken).transfer(_originalSender, _collateralRemaining);
+ }
+
+ /**
+ * Sells the collateral tokens for the selected output ERC20 and returns that to the user
+ *
+ * @param _collateralToken Address of the collateral token
+ * @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
+ * @param _originalSender Address of the original sender to return the tokens to
+ * @param _outputToken Address of token to return to the user
+ * @param _minAmountOutputToken Minimum amount of output token to return to the user
+ * @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to Output token
+ *
+ * @return Amount of output token returned to the user
+ */
+ function _liquidateCollateralTokensForERC20(
+ address _collateralToken,
+ uint256 _collateralRemaining,
+ address _originalSender,
+ IERC20 _outputToken,
+ uint256 _minAmountOutputToken,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ returns (uint256)
+ {
+ if(address(_outputToken) == _collateralToken){
+ _returnCollateralTokensToSender(_collateralToken, _collateralRemaining, _originalSender);
+ return _collateralRemaining;
+ }
+ uint256 outputTokenAmount = _swapCollateralForOutputToken(
+ _collateralToken,
+ _collateralRemaining,
+ address(_outputToken),
+ _minAmountOutputToken,
+ _swapData
+ );
+ _outputToken.transfer(_originalSender, outputTokenAmount);
+ return outputTokenAmount;
+ }
+
+ /**
+ * Sells the remaining collateral tokens for weth, withdraws that and returns native eth to the user
+ *
+ * @param _collateralToken Address of the collateral token
+ * @param _collateralRemaining Amount of the collateral token remaining after buying required debt tokens
+ * @param _originalSender Address of the original sender to return the eth to
+ * @param _minAmountOutputToken Minimum amount of output token to return to user
+ * @param _swapData Data (token path and fee levels) describing the swap path from Collateral Token to eth
+ *
+ * @return Amount of eth returned to the user
+ */
+ function _liquidateCollateralTokensForETH(
+ address _collateralToken,
+ uint256 _collateralRemaining,
+ address _originalSender,
+ uint256 _minAmountOutputToken,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ isValidPath(_swapData.path, _collateralToken, addresses.weth)
+ returns(uint256)
+ {
+ uint256 ethAmount = _swapCollateralForOutputToken(
+ _collateralToken,
+ _collateralRemaining,
+ addresses.weth,
+ _minAmountOutputToken,
+ _swapData
+ );
+ if (ethAmount > 0) {
+ IWETH(addresses.weth).withdraw(ethAmount);
+ (payable(_originalSender)).sendValue(ethAmount);
+ }
+ return ethAmount;
+ }
+
+ /**
+ * Obtains the tokens necessary to return the flashloan by swapping the debt tokens obtained
+ * from issuance and making up the shortfall using the users funds.
+ *
+ * @param _collateralToken collateral token to obtain
+ * @param _amountRequired Amount of collateralToken required to repay the flashloan
+ * @param _decodedParams Struct containing decoded data from original call passed through via flashloan
+ *
+ * @return Amount of input token spent
+ */
+ function _obtainCollateralTokens(
+ address _collateralToken,
+ uint256 _amountRequired,
+ DecodedParams memory _decodedParams
+ )
+ internal
+ returns (uint256)
+ {
+ uint collateralTokenObtained = _swapDebtForCollateralToken(
+ _collateralToken,
+ _decodedParams.leveragedTokenData.debtToken,
+ _decodedParams.leveragedTokenData.debtAmount,
+ _decodedParams.collateralAndDebtSwapData
+ );
+
+ uint collateralTokenShortfall = _amountRequired.sub(collateralTokenObtained) + ROUNDING_ERROR_MARGIN;
+ uint amountInputToken;
+
+ if(_decodedParams.paymentToken == DEXAdapter.ETH_ADDRESS){
+ amountInputToken = _makeUpShortfallWithETH(
+ _collateralToken,
+ collateralTokenShortfall,
+ _decodedParams.originalSender,
+ _decodedParams.limitAmount,
+ _decodedParams.paymentTokenSwapData
+ );
+ } else {
+ amountInputToken = _makeUpShortfallWithERC20(
+ _collateralToken,
+ collateralTokenShortfall,
+ _decodedParams.originalSender,
+ IERC20(_decodedParams.paymentToken),
+ _decodedParams.limitAmount,
+ _decodedParams.paymentTokenSwapData
+ );
+ }
+ emit FlashRedeem(
+ _decodedParams.originalSender,
+ _decodedParams.setToken,
+ _decodedParams.paymentToken,
+ amountInputToken,
+ _decodedParams.setAmount
+ );
+ return amountInputToken;
+ }
+
+ /**
+ * Issues set token using the previously obtained collateral token
+ * Results in debt token being returned to the contract
+ *
+ * @param _setToken Address of the SetToken to be issued
+ * @param _setAmount Amount of SetTokens to issue
+ * @param _originalSender Adress that initiated the token issuance, which will receive the set tokens
+ */
+ function _issueSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
+ debtIssuanceModule.issue(_setToken, _setAmount, _originalSender);
+ }
+
+ /**
+ * Redeems set token using the previously obtained debt token
+ * Results in collateral token being returned to the contract
+ *
+ * @param _setToken Address of the SetToken to be redeemed
+ * @param _setAmount Amount of SetTokens to redeem
+ * @param _originalSender Adress that initiated the token redemption which is the source of the set tokens to be redeemed
+ */
+ function _redeemSet(ISetToken _setToken, uint256 _setAmount, address _originalSender) internal {
+ _setToken.safeTransferFrom(_originalSender, address(this), _setAmount);
+ debtIssuanceModule.redeem(_setToken, _setAmount, address(this));
+ }
+
+ /**
+ * Transfers the shortfall between the amount of tokens required to return flashloan and what was obtained
+ * from swapping the debt tokens from the users address
+ *
+ * @param _token Address of the token to transfer from user
+ * @param _shortfall Collateral token shortfall required to return the flashloan
+ * @param _originalSender Adress that initiated the token issuance, which is the adresss form which to transfer the tokens
+ */
+ function _transferShortfallFromSender(
+ address _token,
+ uint256 _shortfall,
+ address _originalSender
+ )
+ internal
+ {
+ if(_shortfall>0){
+ IERC20(_token).safeTransferFrom(_originalSender, address(this), _shortfall);
+ }
+ }
+
+ /**
+ * Makes up the collateral token shortfall with user specified ERC20 token
+ *
+ * @param _collateralToken Address of the collateral token
+ * @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
+ * @param _originalSender Address of the original sender to return the tokens to
+ * @param _inputToken Input token to pay with
+ * @param _maxAmountInputToken Maximum amount of input token to spend
+ *
+ * @return Amount of input token spent
+ */
+ function _makeUpShortfallWithERC20(
+ address _collateralToken,
+ uint256 _collateralTokenShortfall,
+ address _originalSender,
+ IERC20 _inputToken,
+ uint256 _maxAmountInputToken,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ returns (uint256)
+ {
+ if(address(_inputToken) == _collateralToken){
+ _transferShortfallFromSender(_collateralToken, _collateralTokenShortfall, _originalSender);
+ return _collateralTokenShortfall;
+ } else {
+ _inputToken.transferFrom(_originalSender, address(this), _maxAmountInputToken);
+ uint256 amountInputToken = _swapInputForCollateralToken(
+ _collateralToken,
+ _collateralTokenShortfall,
+ address(_inputToken),
+ _maxAmountInputToken,
+ _swapData
+ );
+ if(amountInputToken < _maxAmountInputToken){
+ _inputToken.transfer(_originalSender, _maxAmountInputToken.sub(amountInputToken));
+ }
+ return amountInputToken;
+ }
+ }
+
+ /**
+ * Makes up the collateral token shortfall with native eth
+ *
+ * @param _collateralToken Address of the collateral token
+ * @param _collateralTokenShortfall Shortfall of collateral token that was not covered by selling the debt tokens
+ * @param _originalSender Address of the original sender to return the tokens to
+ * @param _maxAmountEth Maximum amount of eth to pay
+ *
+ * @return Amount of eth spent
+ */
+ function _makeUpShortfallWithETH(
+ address _collateralToken,
+ uint256 _collateralTokenShortfall,
+ address _originalSender,
+ uint256 _maxAmountEth,
+ DEXAdapter.SwapData memory _swapData
+
+ )
+ internal
+ returns(uint256)
+ {
+ IWETH(addresses.weth).deposit{value: _maxAmountEth}();
+
+ uint256 amountEth = _swapInputForCollateralToken(
+ _collateralToken,
+ _collateralTokenShortfall,
+ addresses.weth,
+ _maxAmountEth,
+ _swapData
+ );
+
+ if(_maxAmountEth > amountEth){
+ uint256 amountEthReturn = _maxAmountEth.sub(amountEth);
+ IWETH(addresses.weth).withdraw(amountEthReturn);
+ (payable(_originalSender)).sendValue(amountEthReturn);
+ }
+ return amountEth;
+ }
+
+ /**
+ * Swaps the debt tokens obtained from issuance for the collateral
+ *
+ * @param _collateralToken Address of the collateral token buy
+ * @param _debtToken Address of the debt token to sell
+ * @param _debtAmount Amount of debt token to sell
+ * @param _swapData Struct containing path and fee data for swap
+ *
+ * @return Amount of collateral token obtained
+ */
+ function _swapDebtForCollateralToken(
+ address _collateralToken,
+ address _debtToken,
+ uint256 _debtAmount,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ isValidPath(_swapData.path, _debtToken, _collateralToken)
+ returns (uint256)
+ {
+ return addresses.swapExactTokensForTokens(
+ _debtAmount,
+ // minAmountOut is 0 here since we are going to make up the shortfall with the input token.
+ // Sandwich protection is provided by the check at the end against _maxAmountInputToken parameter specified by the user
+ 0,
+ _swapData
+ );
+ }
+
+ /**
+ * Acquires debt tokens needed for flashloan repayment by swapping a portion of the collateral tokens obtained from redemption
+ *
+ * @param _debtAmount Amount of debt token to buy
+ * @param _debtToken Address of debt token
+ * @param _collateralAmount Amount of collateral token available to spend / used as maxAmountIn parameter
+ * @param _collateralToken Address of collateral token
+ * @param _swapData Struct containing path and fee data for swap
+ *
+ * @return Amount of collateral token spent
+ */
+ function _swapCollateralForDebtToken(
+ uint256 _debtAmount,
+ address _debtToken,
+ uint256 _collateralAmount,
+ address _collateralToken,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ isValidPath(_swapData.path, _collateralToken, _debtToken)
+ returns (uint256)
+ {
+ return addresses.swapTokensForExactTokens(
+ _debtAmount,
+ _collateralAmount,
+ _swapData
+ );
+ }
+
+ /**
+ * Acquires the required amount of collateral tokens by swapping the input tokens
+ * Does nothing if collateral and input token are indentical
+ *
+ * @param _collateralToken Address of collateral token
+ * @param _amountRequired Remaining amount of collateral token required to repay flashloan, after having swapped debt tokens for collateral
+ * @param _inputToken Address of input token to swap
+ * @param _maxAmountInputToken Maximum amount of input token to spend
+ * @param _swapData Data (token addresses and fee levels) describing the swap path
+ *
+ * @return Amount of input token spent
+ */
+ function _swapInputForCollateralToken(
+ address _collateralToken,
+ uint256 _amountRequired,
+ address _inputToken,
+ uint256 _maxAmountInputToken,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ isValidPath(
+ _swapData.path,
+ _inputToken,
+ _collateralToken
+ )
+ returns (uint256)
+ {
+ if(_collateralToken == _inputToken) return _amountRequired;
+ return addresses.swapTokensForExactTokens(
+ _amountRequired,
+ _maxAmountInputToken,
+ _swapData
+ );
+ }
+
+
+ /**
+ * Swaps the collateral tokens obtained from redemption for the selected output token
+ * If both tokens are the same, does nothing
+ *
+ * @param _collateralToken Address of collateral token
+ * @param _collateralTokenAmount Amount of colalteral token to swap
+ * @param _outputToken Address of the ERC20 token to swap into
+ * @param _minAmountOutputToken Minimum amount of output token to return to the user
+ * @param _swapData Data (token addresses and fee levels) describing the swap path
+ *
+ * @return Amount of output token obtained
+ */
+ function _swapCollateralForOutputToken(
+ address _collateralToken,
+ uint256 _collateralTokenAmount,
+ address _outputToken,
+ uint256 _minAmountOutputToken,
+ DEXAdapter.SwapData memory _swapData
+ )
+ internal
+ isValidPath(_swapData.path, _collateralToken, _outputToken)
+ returns (uint256)
+ {
+ return addresses.swapExactTokensForTokens(
+ _collateralTokenAmount,
+ _minAmountOutputToken,
+ _swapData
+ );
+ }
+
+
+
+ /**
+ * Deposit collateral to aave to obtain collateralAToken for issuance
+ *
+ * @param _collateralToken Address of collateral token
+ * @param _depositAmount Amount to deposit
+ */
+ function _depositCollateralToken(
+ address _collateralToken,
+ uint256 _depositAmount
+ ) internal {
+ LENDING_POOL.deposit(_collateralToken, _depositAmount, address(this), 0);
+ }
+
+ /**
+ * Convert collateralAToken from set redemption to collateralToken by withdrawing underlying from Aave
+ *
+ * @param _collateralToken Address of the collateralToken to withdraw from Aave lending pool
+ * @param _collateralAmount Amount of collateralToken to withdraw
+ */
+ function _withdrawCollateralToken(
+ address _collateralToken,
+ uint256 _collateralAmount
+ ) internal {
+ LENDING_POOL.withdraw(_collateralToken, _collateralAmount, address(this));
+ }
+
+ /**
+ * Sets a max approval limit for an ERC20 token, provided the current allowance
+ * is less than the required allownce.
+ *
+ * @param _token Token to approve
+ * @param _spender Spender address to approve
+ * @param _requiredAllowance Target allowance to set
+ */
+ function _safeApprove(
+ IERC20 _token,
+ address _spender,
+ uint256 _requiredAllowance
+ )
+ internal
+ {
+ uint256 allowance = _token.allowance(address(this), _spender);
+ if (allowance < _requiredAllowance) {
+ _token.safeIncreaseAllowance(_spender, MAX_UINT256 - allowance);
+ }
+ }
+
+ /**
+ * Approves max amount of token to lending pool
+ *
+ * @param _token Address of the token to approve
+ */
+ function _approveTokenToLendingPool(
+ IERC20 _token
+ )
+ internal
+ {
+ uint256 allowance = _token.allowance(address(this), address(LENDING_POOL));
+ if (allowance > 0) {
+ _token.approve(address(LENDING_POOL), 0);
+ }
+ _token.approve(address(LENDING_POOL), MAX_UINT256);
+ }
+
+ /**
+ * Triggers the flashloan from the BalancerV2 Vault
+ *
+ * @param assets Addresses of tokens to loan
+ * @param amounts Amounts to loan
+ * @param params Encoded memory to forward to the executeOperation method
+ */
+ function _flashloan(
+ address[] memory assets,
+ uint256[] memory amounts,
+ bytes memory params
+ )
+ internal
+ {
+ require(flashLoanBenefactor == address(0), "Flashloan already taken");
+ flashLoanBenefactor = msg.sender;
+ balancerV2Vault.flashLoan(this, assets, amounts, params);
+ flashLoanBenefactor = address(0);
+ }
+
+ /**
+ * Redeems a given amount of SetToken.
+ *
+ * @param _setToken Address of the SetToken to be redeemed
+ * @param _amount Amount of SetToken to be redeemed
+ */
+ function _redeemExactSet(ISetToken _setToken, uint256 _amount) internal returns (uint256) {
+ _setToken.safeTransferFrom(msg.sender, address(this), _amount);
+ debtIssuanceModule.redeem(_setToken, _amount, address(this));
+ }
+ receive() external payable {}
+}
diff --git a/contracts/interfaces/external/balancer-v2/IVault.sol b/contracts/interfaces/external/balancer-v2/IVault.sol
new file mode 100644
index 00000000..96510e9d
--- /dev/null
+++ b/contracts/interfaces/external/balancer-v2/IVault.sol
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.6.10 <0.9.0;
+pragma experimental ABIEncoderV2;
+
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+interface IFlashLoanRecipient {
+ /**
+ * @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
+ *
+ * At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
+ * call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
+ * Vault, or else the entire flash loan will revert.
+ *
+ * `userData` is the same value passed in the `IVault.flashLoan` call.
+ */
+ function receiveFlashLoan(
+ IERC20[] memory tokens,
+ uint256[] memory amounts,
+ uint256[] memory feeAmounts,
+ bytes memory userData
+ ) external;
+}
+
+/**
+ * Stripped down interface of IVault.
+ * https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/interfaces/contracts/vault/IVault.sol
+ */
+interface IVault {
+ // Swaps
+ //
+ // Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,
+ // they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be
+ // aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.
+ //
+ // The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.
+ // In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),
+ // and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').
+ // More complex swaps, such as one token in to multiple tokens out can be achieved by batching together
+ // individual swaps.
+ //
+ // There are two swap kinds:
+ // - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the
+ // `onSwap` hook) the amount of tokens out (to send to the recipient).
+ // - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines
+ // (via the `onSwap` hook) the amount of tokens in (to receive from the sender).
+ //
+ // Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with
+ // the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated
+ // tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended
+ // swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at
+ // the final intended token.
+ //
+ // In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal
+ // Balance) after all individual swaps have been completed, and the net token balance change computed. This makes
+ // certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost
+ // much less gas than they would otherwise.
+ //
+ // It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple
+ // Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only
+ // updating the Pool's internal accounting).
+ //
+ // To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token
+ // involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the
+ // minimum amount of tokens to receive (by passing a negative value) is specified.
+ //
+ // Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after
+ // this point in time (e.g. if the transaction failed to be included in a block promptly).
+ //
+ // If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do
+ // the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be
+ // passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the
+ // same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).
+ //
+ // Finally, Internal Balance can be used when either sending or receiving tokens.
+
+ enum SwapKind {
+ GIVEN_IN,
+ GIVEN_OUT
+ }
+
+ /**
+ * @dev Performs a swap with a single Pool.
+ *
+ * If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens
+ * taken from the Pool, which must be greater than or equal to `limit`.
+ *
+ * If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens
+ * sent to the Pool, which must be less than or equal to `limit`.
+ *
+ * Internal Balance usage and the recipient are determined by the `funds` struct.
+ *
+ * Emits a `Swap` event.
+ */
+ function swap(
+ SingleSwap memory singleSwap,
+ FundManagement memory funds,
+ uint256 limit,
+ uint256 deadline
+ ) external payable returns (uint256);
+
+ /**
+ * @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on
+ * the `kind` value.
+ *
+ * `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).
+ * Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.
+ *
+ * The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
+ * used to extend swap behavior.
+ */
+ struct SingleSwap {
+ bytes32 poolId;
+ SwapKind kind;
+ address assetIn;
+ address assetOut;
+ uint256 amount;
+ bytes userData;
+ }
+
+ /**
+ * @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either
+ * the amount of tokens sent to or received from the Pool, depending on the `kind` value.
+ *
+ * Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the
+ * Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at
+ * the same index in the `assets` array.
+ *
+ * Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a
+ * Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or
+ * `amountOut` depending on the swap kind.
+ *
+ * Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out
+ * of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal
+ * the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.
+ *
+ * The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,
+ * or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and
+ * out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to
+ * or unwrapped from WETH by the Vault.
+ *
+ * Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies
+ * the minimum or maximum amount of each token the vault is allowed to transfer.
+ *
+ * `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the
+ * equivalent `swap` call.
+ *
+ * Emits `Swap` events.
+ */
+ function batchSwap(
+ SwapKind kind,
+ BatchSwapStep[] memory swaps,
+ address[] memory assets,
+ FundManagement memory funds,
+ int256[] memory limits,
+ uint256 deadline
+ ) external payable returns (int256[] memory);
+
+ /**
+ * @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the
+ * `assets` array passed to that function, and ETH assets are converted to WETH.
+ *
+ * If `amount` is zero, the multihop mechanism is used to determine the actual amount based on the amount in/out
+ * from the previous swap, depending on the swap kind.
+ *
+ * The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
+ * used to extend swap behavior.
+ */
+ struct BatchSwapStep {
+ bytes32 poolId;
+ uint256 assetInIndex;
+ uint256 assetOutIndex;
+ uint256 amount;
+ bytes userData;
+ }
+
+ /**
+ * @dev Emitted for each individual swap performed by `swap` or `batchSwap`.
+ */
+ event Swap(
+ bytes32 indexed poolId,
+ IERC20 indexed tokenIn,
+ IERC20 indexed tokenOut,
+ uint256 amountIn,
+ uint256 amountOut
+ );
+
+ /**
+ * @dev All tokens in a swap are either sent from the `sender` account to the Vault, or from the Vault to the
+ * `recipient` account.
+ *
+ * If the caller is not `sender`, it must be an authorized relayer for them.
+ *
+ * If `fromInternalBalance` is true, the `sender`'s Internal Balance will be preferred, performing an ERC20
+ * transfer for the difference between the requested amount and the User's Internal Balance (if any). The `sender`
+ * must have allowed the Vault to use their tokens via `IERC20.approve()`. This matches the behavior of
+ * `joinPool`.
+ *
+ * If `toInternalBalance` is true, tokens will be deposited to `recipient`'s internal balance instead of
+ * transferred. This matches the behavior of `exitPool`.
+ *
+ * Note that ETH cannot be deposited to or withdrawn from Internal Balance: attempting to do so will trigger a
+ * revert.
+ */
+ struct FundManagement {
+ address sender;
+ bool fromInternalBalance;
+ address payable recipient;
+ bool toInternalBalance;
+ }
+
+ /**
+ * @dev Simulates a call to `batchSwap`, returning an array of Vault asset deltas. Calls to `swap` cannot be
+ * simulated directly, but an equivalent `batchSwap` call can and will yield the exact same result.
+ *
+ * Each element in the array corresponds to the asset at the same index, and indicates the number of tokens (or ETH)
+ * the Vault would take from the sender (if positive) or send to the recipient (if negative). The arguments it
+ * receives are the same that an equivalent `batchSwap` call would receive.
+ *
+ * Unlike `batchSwap`, this function performs no checks on the sender or recipient field in the `funds` struct.
+ * This makes it suitable to be called by off-chain applications via eth_call without needing to hold tokens,
+ * approve them for the Vault, or even know a user's address.
+ *
+ * Note that this function is not 'view' (due to implementation details): the client code must explicitly execute
+ * eth_call instead of eth_sendTransaction.
+ */
+ function queryBatchSwap(
+ SwapKind kind,
+ BatchSwapStep[] memory swaps,
+ address[] memory assets,
+ FundManagement memory funds
+ ) external returns (int256[] memory assetDeltas);
+
+ // Flash Loans
+
+ /**
+ * @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
+ * and then reverting unless the tokens plus a proportional protocol fee have been returned.
+ *
+ * The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
+ * for each token contract. `tokens` must be sorted in ascending order.
+ *
+ * The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
+ * `receiveFlashLoan` call.
+ *
+ * Emits `FlashLoan` events.
+ */
+ function flashLoan(
+ IFlashLoanRecipient recipient,
+ address[] memory tokens,
+ uint256[] memory amounts,
+ bytes memory userData
+ ) external;
+
+ /**
+ * @dev Emitted for each individual flash loan performed by `flashLoan`.
+ */
+ event FlashLoan(IFlashLoanRecipient indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount);
+
+}
\ No newline at end of file
diff --git a/test/integration/ethereum/addresses.ts b/test/integration/ethereum/addresses.ts
index 67ed495b..ee7eb1c2 100644
--- a/test/integration/ethereum/addresses.ts
+++ b/test/integration/ethereum/addresses.ts
@@ -7,6 +7,9 @@ export const PRODUCTION_ADDRESSES = {
dai: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
weth: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
icEth: "0x7C07F7aBe10CE8e33DC6C5aD68FE033085256A84",
+ icReth: "0xe8888Cdbc0A5958C29e7D91DAE44897c7e64F9BC",
+ rETH: "0xae78736Cd615f374D3085123A210448E74Fc6393",
+ aEthrETH : "0xCc9EE9483f662091a1de4795249E24aC0aC2630f",
aSTETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428",
ETH2xFli: "0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD",
cEther: "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5",
@@ -32,6 +35,7 @@ export const PRODUCTION_ADDRESSES = {
ethAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
pools: {
stEthEth: "0xDC24316b9AE028F1497c275EB9192a3Ea0f67022",
+ rEthEth: "0x0f3159811670c117c372428D4E69AC32325e4D0F",
},
},
sushiswap: {
@@ -44,6 +48,9 @@ export const PRODUCTION_ADDRESSES = {
router: "0xE592427A0AEce92De3Edee1F18E0157C05861564",
quoter: "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6",
},
+ balancerv2: {
+ vault: "0xBA12222222228d8Ba445958a75a0704d566BF2C8",
+ },
},
set: {
controller: "0xa4c8d221d8BB851f83aadd0223a8900A6921A349",
@@ -58,12 +65,18 @@ export const PRODUCTION_ADDRESSES = {
notionalTradeModule: "0x600d9950c6ecAef98Cc42fa207E92397A6c43416",
tradeModule: "0xFaAB3F8f3678f68AA0d307B66e71b636F82C28BF",
airdropModule: "0x09b9e7c7e2daf40fCb286fE6b863e517d5d5c40F",
+ aaveV3LeverageStrategyExtension: "0x7d3f7EDD04916F3Cb2bC6740224c636B9AE43200",
+ aaveV3LeverageModule: "0x71E932715F5987077ADC5A7aA245f38841E0DcBe",
},
lending: {
aave: {
addressProvider: "0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5",
lendingPool: "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9",
},
+ aaveV3: {
+ addressProvider: "0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e",
+ lendingPool: "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
+ },
notional: {
wrappedfCashFactory: "0x5D051DeB5db151C2172dCdCCD42e6A2953E27261",
notionalV2: "0x1344a36a1b56144c3bc62e7757377d288fde0369",
diff --git a/test/integration/ethereum/flashMintLeveraged.spec.ts b/test/integration/ethereum/flashMintLeveraged.spec.ts
new file mode 100644
index 00000000..54b0b6b3
--- /dev/null
+++ b/test/integration/ethereum/flashMintLeveraged.spec.ts
@@ -0,0 +1,466 @@
+import "module-alias/register";
+import { Account, Address } from "@utils/types";
+import DeployHelper from "@utils/deploys";
+import { getAccounts, getWaffleExpect, preciseMul } from "@utils/index";
+import { setBlockNumber } from "@utils/test/testingUtils";
+import { ethers } from "hardhat";
+import { BigNumber, utils } from "ethers";
+import { FlashMintLeveraged } from "@utils/contracts/index";
+import {
+ IWETH,
+ StandardTokenMock,
+ IDebtIssuanceModule,
+ IERC20__factory,
+ AaveV3LeverageStrategyExtension__factory
+} from "../../../typechain";
+import { PRODUCTION_ADDRESSES, STAGING_ADDRESSES } from "./addresses";
+import { ADDRESS_ZERO, MAX_UINT_256 } from "@utils/constants";
+import { ether } from "@utils/index";
+import { impersonateAccount } from "./utils";
+
+const expect = getWaffleExpect();
+
+enum Exchange {
+ None,
+ Sushiswap,
+ Quickswap,
+ UniV3,
+ Curve,
+}
+
+type SwapData = {
+ path: Address[];
+ fees: number[];
+ pool: Address;
+ exchange: Exchange;
+};
+
+if (process.env.INTEGRATIONTEST) {
+ describe.only("FlashMintLeveraged - Integration Test", async () => {
+ const addresses = process.env.USE_STAGING_ADDRESSES ? STAGING_ADDRESSES : PRODUCTION_ADDRESSES;
+ let owner: Account;
+ let deployer: DeployHelper;
+
+ let rEth: StandardTokenMock;
+ let setToken: StandardTokenMock;
+ let weth: IWETH;
+
+ // const collateralTokenAddress = addresses.tokens.stEth;
+ setBlockNumber(17665622);
+
+ before(async () => {
+ [owner] = await getAccounts();
+ deployer = new DeployHelper(owner.wallet);
+
+ rEth = (await ethers.getContractAt(
+ "StandardTokenMock",
+ addresses.tokens.rETH,
+ )) as StandardTokenMock;
+
+ setToken = (await ethers.getContractAt(
+ "StandardTokenMock",
+ addresses.tokens.icReth,
+ )) as StandardTokenMock;
+
+
+ weth = (await ethers.getContractAt("IWETH", addresses.tokens.weth)) as IWETH;
+ });
+
+ it("can get lending pool from address provider", async () => {
+ const addressProvider = await ethers.getContractAt(
+ "IPoolAddressesProvider",
+ addresses.lending.aaveV3.addressProvider,
+ );
+ const lendingPool = await addressProvider.getPool();
+ expect(lendingPool).to.eq(addresses.lending.aaveV3.lendingPool);
+ });
+
+ context("When exchange issuance is deployed", () => {
+ let flashMintLeveraged: FlashMintLeveraged;
+ before(async () => {
+ flashMintLeveraged = await deployer.extensions.deployFlashMintLeveraged(
+ addresses.tokens.weth,
+ addresses.dexes.uniV2.router,
+ addresses.dexes.sushiswap.router,
+ addresses.dexes.uniV3.router,
+ addresses.dexes.uniV3.quoter,
+ addresses.setFork.controller,
+ addresses.setFork.debtIssuanceModuleV2,
+ addresses.setFork.aaveV3LeverageModule,
+ addresses.lending.aaveV3.lendingPool,
+ addresses.dexes.curve.addressProvider,
+ addresses.dexes.curve.calculator,
+ addresses.dexes.balancerv2.vault
+ );
+ });
+
+ it("weth address is set correctly", async () => {
+ const returnedAddresses = await flashMintLeveraged.addresses();
+ expect(returnedAddresses.weth).to.eq(utils.getAddress(addresses.tokens.weth));
+ });
+
+ it("sushi router address is set correctly", async () => {
+ const returnedAddresses = await flashMintLeveraged.addresses();
+ expect(returnedAddresses.sushiRouter).to.eq(
+ utils.getAddress(addresses.dexes.sushiswap.router),
+ );
+ });
+
+ it("uniV2 router address is set correctly", async () => {
+ const returnedAddresses = await flashMintLeveraged.addresses();
+ expect(returnedAddresses.quickRouter).to.eq(
+ utils.getAddress(addresses.dexes.uniV2.router),
+ );
+ });
+
+ it("uniV3 router address is set correctly", async () => {
+ const returnedAddresses = await flashMintLeveraged.addresses();
+ expect(returnedAddresses.uniV3Router).to.eq(
+ utils.getAddress(addresses.dexes.uniV3.router),
+ );
+ });
+
+ it("controller address is set correctly", async () => {
+ expect(await flashMintLeveraged.setController()).to.eq(
+ utils.getAddress(addresses.setFork.controller),
+ );
+ });
+
+ it("debt issuance module address is set correctly", async () => {
+ expect(await flashMintLeveraged.debtIssuanceModule()).to.eq(
+ utils.getAddress(addresses.setFork.debtIssuanceModuleV2),
+ );
+ });
+
+ describe("When setToken is approved", () => {
+ let collateralAToken: StandardTokenMock;
+ let debtToken: StandardTokenMock;
+ let collateralATokenAddress: Address;
+ let collateralTokenAddress: Address;
+ let debtTokenAddress: Address;
+ before(async () => {
+ const arETHWhale = "0x4D17676309cb16fA991E6AE43181d08203b781F8";
+ const rEthWhale = "0x7d6149aD9A573A6E2Ca6eBf7D4897c1B766841B4";
+ const operator = "0x6904110f17feD2162a11B5FA66B188d801443Ea4";
+ const whaleSigner = await impersonateAccount(arETHWhale);
+
+ const rEthWhaleSigner = await impersonateAccount(rEthWhale);
+
+ const arETH = IERC20__factory.connect(addresses.tokens.aEthrETH, whaleSigner);
+
+ const rETH = IERC20__factory.connect(addresses.tokens.rETH, rEthWhaleSigner);
+ await rETH.transfer(owner.address, ether(100));
+ await arETH.transfer(owner.address, ether(100));
+
+ await arETH.connect(owner.wallet).approve(addresses.setFork.debtIssuanceModuleV2, ether(10));
+ await rETH.connect(owner.wallet).approve(flashMintLeveraged.address, ether(100));
+ const debtIssuanceModule = await ethers.getContractAt(
+ "IDebtIssuanceModule", addresses.setFork.debtIssuanceModuleV2, owner.wallet) as IDebtIssuanceModule;
+
+
+ const issueTx = await debtIssuanceModule.issue(setToken.address, ether(10), owner.address);
+
+ await issueTx.wait();
+
+ const operatorSigner = await impersonateAccount(operator);
+
+ const aaveV3LeverageStrategyExtension = AaveV3LeverageStrategyExtension__factory.connect(
+ addresses.setFork.aaveV3LeverageStrategyExtension, operatorSigner);
+ const engageTx = await aaveV3LeverageStrategyExtension.engage("BalancerV2ExchangeAdapter");
+ await engageTx.wait();
+
+
+ await flashMintLeveraged.approveSetToken(setToken.address);
+
+ const leveragedTokenData = await flashMintLeveraged.getLeveragedTokenData(
+ setToken.address,
+ ether(1),
+ true,
+ );
+
+ collateralATokenAddress = leveragedTokenData.collateralAToken;
+ collateralTokenAddress = leveragedTokenData.collateralToken;
+ debtTokenAddress = leveragedTokenData.debtToken;
+
+ collateralAToken = (await ethers.getContractAt(
+ "StandardTokenMock",
+ collateralATokenAddress,
+ )) as StandardTokenMock;
+ debtToken = (await ethers.getContractAt(
+ "StandardTokenMock",
+ debtTokenAddress,
+ )) as StandardTokenMock;
+
+
+ });
+
+ it("should adjust collateral a token allowance correctly", async () => {
+ expect(
+ await collateralAToken.allowance(
+ flashMintLeveraged.address,
+ addresses.setFork.debtIssuanceModuleV2,
+ ),
+ ).to.equal(MAX_UINT_256);
+ });
+ it("should adjust debt token allowance correctly", async () => {
+ expect(
+ await debtToken.allowance(flashMintLeveraged.address, addresses.setFork.debtIssuanceModuleV2),
+ ).to.equal(MAX_UINT_256);
+ });
+
+ ["collateralToken", "WETH", "ETH"].forEach(inputTokenName => {
+ describe(`When input/output token is ${inputTokenName}`, () => {
+ let subjectSetAmount: BigNumber;
+ let amountIn: BigNumber;
+ beforeEach(async () => {
+ amountIn = ether(2);
+ subjectSetAmount = ether(1);
+ });
+
+ describe(
+ inputTokenName === "ETH" ? "issueExactSetFromETH" : "#issueExactSetFromERC20",
+ () => {
+ let swapDataDebtToCollateral: SwapData;
+ let swapDataInputToken: SwapData;
+
+
+ let inputToken: StandardTokenMock | IWETH;
+
+ let subjectSetToken: Address;
+ let subjectMaxAmountIn: BigNumber;
+ let subjectInputToken: Address;
+
+ beforeEach(async () => {
+ swapDataDebtToCollateral = {
+ path: [addresses.tokens.weth, addresses.tokens.rETH],
+ fees: [500],
+ pool: ADDRESS_ZERO,
+ exchange: Exchange.UniV3,
+ };
+
+
+ swapDataInputToken = {
+ path: [],
+ fees: [],
+ pool: ADDRESS_ZERO,
+ exchange: Exchange.None,
+ };
+
+
+
+ if (inputTokenName === "collateralToken") {
+ inputToken = rEth;
+ } else {
+ swapDataInputToken = swapDataDebtToCollateral;
+
+ if (inputTokenName === "WETH") {
+ inputToken = weth;
+ await weth.deposit({ value: amountIn });
+ }
+ }
+
+ let inputTokenBalance: BigNumber;
+ if (inputTokenName === "ETH") {
+ subjectMaxAmountIn = amountIn;
+ } else {
+ inputTokenBalance = await inputToken.balanceOf(owner.address);
+ subjectMaxAmountIn = inputTokenBalance;
+ await inputToken.approve(flashMintLeveraged.address, subjectMaxAmountIn);
+ subjectInputToken = inputToken.address;
+ }
+ subjectSetToken = setToken.address;
+ });
+
+ async function subject() {
+ if (inputTokenName === "ETH") {
+ return flashMintLeveraged.issueExactSetFromETH(
+ subjectSetToken,
+ subjectSetAmount,
+ swapDataDebtToCollateral,
+ swapDataInputToken ,
+ { value: subjectMaxAmountIn },
+ );
+ }
+ return flashMintLeveraged.issueExactSetFromERC20(
+ subjectSetToken,
+ subjectSetAmount,
+ subjectInputToken,
+ subjectMaxAmountIn,
+ swapDataDebtToCollateral,
+ swapDataInputToken ,
+ );
+ }
+
+ async function subjectQuote() {
+ return flashMintLeveraged.callStatic.getIssueExactSet(
+ subjectSetToken,
+ subjectSetAmount,
+ swapDataDebtToCollateral,
+ swapDataInputToken ,
+ );
+ }
+
+
+ it("should issue the correct amount of tokens", async () => {
+ const setBalancebefore = await setToken.balanceOf(owner.address);
+ await subject();
+ const setBalanceAfter = await setToken.balanceOf(owner.address);
+ const setObtained = setBalanceAfter.sub(setBalancebefore);
+ expect(setObtained).to.eq(subjectSetAmount);
+ });
+
+ it("should spend less than specified max amount", async () => {
+ const inputBalanceBefore =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await inputToken.balanceOf(owner.address);
+ await subject();
+ const inputBalanceAfter =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await inputToken.balanceOf(owner.address);
+ const inputSpent = inputBalanceBefore.sub(inputBalanceAfter);
+ expect(inputSpent.gt(0)).to.be.true;
+ expect(inputSpent.lte(subjectMaxAmountIn)).to.be.true;
+ });
+
+ it("should quote the correct input amount", async () => {
+ const inputBalanceBefore =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await inputToken.balanceOf(owner.address);
+ await subject();
+ const inputBalanceAfter =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await inputToken.balanceOf(owner.address);
+ const inputSpent = inputBalanceBefore.sub(inputBalanceAfter);
+
+ const quotedInputAmount = await subjectQuote();
+
+ expect(quotedInputAmount).to.gt(preciseMul(inputSpent, ether(0.99)));
+ expect(quotedInputAmount).to.lt(preciseMul(inputSpent, ether(1.01)));
+ });
+ },
+ );
+
+ describe(
+ inputTokenName === "ETH" ? "redeemExactSetForETH" : "#redeemExactSetForERC20",
+ () => {
+ let swapDataCollateralToDebt: SwapData;
+ let swapDataOutputToken: SwapData;
+
+ let outputToken: StandardTokenMock | IWETH;
+
+ let subjectSetToken: Address;
+ let subjectMinAmountOut: BigNumber;
+ let subjectOutputToken: Address;
+
+ async function subject() {
+ if (inputTokenName === "ETH") {
+ return flashMintLeveraged.redeemExactSetForETH(
+ subjectSetToken,
+ subjectSetAmount,
+ subjectMinAmountOut,
+ swapDataCollateralToDebt,
+ swapDataOutputToken
+ );
+ }
+ return flashMintLeveraged.redeemExactSetForERC20(
+ subjectSetToken,
+ subjectSetAmount,
+ subjectOutputToken,
+ subjectMinAmountOut,
+ swapDataCollateralToDebt,
+ swapDataOutputToken
+ );
+ }
+
+ async function subjectQuote(): Promise {
+ return flashMintLeveraged.callStatic.getRedeemExactSet(
+ subjectSetToken,
+ subjectSetAmount,
+ swapDataCollateralToDebt, swapDataOutputToken
+ );
+ }
+
+ beforeEach(async () => {
+ swapDataCollateralToDebt = {
+ path: [collateralTokenAddress, addresses.tokens.weth],
+ fees: [500],
+ pool: ADDRESS_ZERO,
+ exchange: Exchange.UniV3,
+ };
+
+ if (inputTokenName === "collateralToken") {
+ outputToken = rEth;
+ swapDataOutputToken = {
+ path: [],
+ fees: [],
+ pool: ADDRESS_ZERO,
+ exchange: Exchange.None,
+ };
+ } else {
+ swapDataOutputToken = swapDataCollateralToDebt;
+
+ if (inputTokenName === "WETH") {
+ outputToken = weth;
+ await weth.deposit({ value: amountIn });
+ }
+ }
+
+ subjectMinAmountOut = subjectSetAmount.div(2);
+ subjectSetToken = setToken.address;
+ await setToken.approve(flashMintLeveraged.address, subjectSetAmount);
+
+ if (inputTokenName !== "ETH") {
+ subjectOutputToken = outputToken.address;
+ }
+ });
+
+ it("should redeem the correct amount of tokens", async () => {
+ const setBalanceBefore = await setToken.balanceOf(owner.address);
+ await subject();
+ const setBalanceAfter = await setToken.balanceOf(owner.address);
+ const setRedeemed = setBalanceBefore.sub(setBalanceAfter);
+ expect(setRedeemed).to.eq(subjectSetAmount);
+ });
+
+ it("should return at least the specified minimum of output tokens", async () => {
+ const outputBalanceBefore =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await outputToken.balanceOf(owner.address);
+ await subject();
+ const outputBalanceAfter =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await outputToken.balanceOf(owner.address);
+ const outputObtained = outputBalanceAfter.sub(outputBalanceBefore);
+ expect(outputObtained.gte(subjectMinAmountOut)).to.be.true;
+ });
+
+ it("should quote the correct output amount", async () => {
+ const outputBalanceBefore =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await outputToken.balanceOf(owner.address);
+ await subject();
+ const outputBalanceAfter =
+ inputTokenName === "ETH"
+ ? await owner.wallet.getBalance()
+ : await outputToken.balanceOf(owner.address);
+ const outputObtained = outputBalanceAfter.sub(outputBalanceBefore);
+
+ const outputAmountQuote = await subjectQuote();
+ expect(outputAmountQuote).to.gt(preciseMul(outputObtained, ether(0.99)));
+ expect(outputAmountQuote).to.lt(preciseMul(outputObtained, ether(1.01)));
+ });
+ },
+ );
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts
index 8b1a665d..4ae81600 100644
--- a/utils/contracts/index.ts
+++ b/utils/contracts/index.ts
@@ -11,6 +11,7 @@ export { DEXAdapter } from "../../typechain/DEXAdapter";
export { ExchangeIssuance } from "../../typechain/ExchangeIssuance";
export { ExchangeIssuanceV2 } from "../../typechain/ExchangeIssuanceV2";
export { ExchangeIssuanceLeveraged } from "../../typechain/ExchangeIssuanceLeveraged";
+export { FlashMintLeveraged } from "../../typechain/FlashMintLeveraged";
export { FlashMintLeveragedForCompound } from "../../typechain/FlashMintLeveragedForCompound";
export { ExchangeIssuanceZeroEx } from "../../typechain/ExchangeIssuanceZeroEx";
export { FlashMintNotional } from "../../typechain/FlashMintNotional";
diff --git a/utils/deploys/deployExtensions.ts b/utils/deploys/deployExtensions.ts
index 1df65a6a..aa0f7539 100644
--- a/utils/deploys/deployExtensions.ts
+++ b/utils/deploys/deployExtensions.ts
@@ -38,6 +38,7 @@ import { DEXAdapter__factory } from "../../typechain/factories/DEXAdapter__facto
import { ExchangeIssuance__factory } from "../../typechain/factories/ExchangeIssuance__factory";
import { ExchangeIssuanceV2__factory } from "../../typechain/factories/ExchangeIssuanceV2__factory";
import { ExchangeIssuanceLeveraged__factory } from "../../typechain/factories/ExchangeIssuanceLeveraged__factory";
+import { FlashMintLeveraged__factory } from "../../typechain/factories/FlashMintLeveraged__factory";
import { FlashMintNotional__factory } from "../../typechain/factories/FlashMintNotional__factory";
import { FlashMintLeveragedForCompound__factory } from "../../typechain/factories/FlashMintLeveragedForCompound__factory";
import { FlashMintWrapped } from "../../typechain/FlashMintWrapped";
@@ -215,6 +216,52 @@ export default class DeployExtensions {
);
}
+ public async deployFlashMintLeveraged(
+ wethAddress: Address,
+ quickRouterAddress: Address,
+ sushiRouterAddress: Address,
+ uniV3RouterAddress: Address,
+ uniswapV3QuoterAddress: Address,
+ setControllerAddress: Address,
+ basicIssuanceModuleAddress: Address,
+ aaveLeveragedModuleAddress: Address,
+ aaveAddressProviderAddress: Address,
+ curveCalculatorAddress: Address,
+ curveAddressProviderAddress: Address,
+ BalancerV2VaultAddress: Address,
+ ) {
+ const dexAdapter = await this.deployDEXAdapter();
+
+ const linkId = convertLibraryNameToLinkId(
+ "contracts/exchangeIssuance/DEXAdapter.sol:DEXAdapter",
+ );
+
+ return await new FlashMintLeveraged__factory(
+ // @ts-ignore
+ {
+ [linkId]: dexAdapter.address,
+ },
+ // @ts-ignore
+ this._deployerSigner,
+ ).deploy(
+ {
+ quickRouter: quickRouterAddress,
+ sushiRouter: sushiRouterAddress,
+ uniV3Router: uniV3RouterAddress,
+ uniV3Quoter: uniswapV3QuoterAddress,
+ curveAddressProvider: curveAddressProviderAddress,
+ curveCalculator: curveCalculatorAddress,
+ weth: wethAddress,
+ },
+ setControllerAddress,
+ basicIssuanceModuleAddress,
+ aaveLeveragedModuleAddress,
+ aaveAddressProviderAddress,
+ BalancerV2VaultAddress
+ );
+ }
+
+
public async deployExchangeIssuanceLeveragedForCompound(
wethAddress: Address,
quickRouterAddress: Address,
diff --git a/utils/test/testingUtils.ts b/utils/test/testingUtils.ts
index bc9b6d52..4c8e3585 100644
--- a/utils/test/testingUtils.ts
+++ b/utils/test/testingUtils.ts
@@ -112,7 +112,7 @@ export async function impersonateAccount(address: string): Promise {
method: "hardhat_impersonateAccount",
params: [address],
});
- return await ethers.getSigner(address);
+ return await ethers.provider.getSigner(address);
}
export function setBlockNumber(blockNumber: number) {