// Contract: AutoleverageCurveMetapool
// Target function: function _maybeConvertCurveOutput(uint256 amountOut) internal override
    ///
    /// @return debt            The unrealized amount of debt that the account had incurred.
    /// @return depositedTokens The yield tokens that the owner has deposited.
    function accounts(address owner) external view returns (int256 debt, address[] memory depositedTokens);

    /// @notice Gets information about a yield token position for the account owned by `owner`.
    ///
    /// @param owner      The address that owns the account.
    /// @param yieldToken The address of the yield token to get the position of.
    ///
    /// @return shares            The amount of shares of that `owner` owns of the yield token.
    /// @return lastAccruedWeight The last recorded accrued weight of the yield token.
    function positions(address owner, address yieldToken)
        external view
        returns (
            uint256 shares,
            uint256 lastAccruedWeight
        );

    /// @notice Gets the amount of debt tokens `spender` is allowed to mint on behalf of `owner`.
    ///
    /// @param owner   The owner of the account.
    /// @param spender The address which is allowed to mint on behalf of `owner`.
    ///
    /// @return allowance The amount of debt tokens that `spender` can mint on behalf of `owner`.
    function mintAllowance(address owner, address spender) external view returns (uint256 allowance);

    /// @notice Gets the amount of shares of `yieldToken` that `spender` is allowed to withdraw on behalf of `owner`.
    ///
    /// @param owner      The owner of the account.
    /// @param spender    The address which is allowed to withdraw on behalf of `owner`.
    /// @param yieldToken The address of the yield token.
    ///
    /// @return allowance The amount of shares that `spender` can withdraw on behalf of `owner`.
    function withdrawAllowance(address owner, address spender, address yieldToken) external view returns (uint256 allowance);

    /// @notice Gets the parameters of an underlying token.
    ///
    /// @param underlyingToken The address of the underlying token.
    ///
    /// @return params The underlying token parameters.
    function getUnderlyingTokenParameters(address underlyingToken)
        external view
        returns (UnderlyingTokenParams memory params);

    /// @notice Get the parameters and state of a yield-token.
    ///
    /// @param yieldToken The address of the yield token.
    ///
    /// @return params The yield token parameters.
    function getYieldTokenParameters(address yieldToken)
        external view
        returns (YieldTokenParams memory params);

    /// @notice Gets current limit, maximum, and rate of the minting limiter.
    ///
    /// @return currentLimit The current amount of debt tokens that can be minted.
    /// @return rate         The maximum possible amount of tokens that can be liquidated at a time.
    /// @return maximum      The highest possible maximum amount of debt tokens that can be minted at a time.
    function getMintLimitInfo()
        external view
        returns (
            uint256 currentLimit,
            uint256 rate,
            uint256 maximum
        );

    /// @notice Gets current limit, maximum, and rate of a repay limiter for `underlyingToken`.
    ///
    /// @param underlyingToken The address of the underlying token.
    ///
    /// @return currentLimit The current amount of underlying tokens that can be repaid.
    /// @return rate         The rate at which the the current limit increases back to its maximum in tokens per block.
    /// @return maximum      The maximum possible amount of tokens that can be repaid at a time.
    function getRepayLimitInfo(address underlyingToken)
        external view
        returns (
            uint256 currentLimit,
            uint256 rate,
            uint256 maximum
        );

    /// @notice Gets current limit, maximum, and rate of the liquidation limiter for `underlyingToken`.
    ///
    /// @param underlyingToken The address of the underlying token.
    ///
    /// @return currentLimit The current amount of underlying tokens that can be liquidated.
    /// @return rate         The rate at which the function increases back to its maximum limit (tokens / block).
    /// @return maximum      The highest possible maximum amount of debt tokens that can be liquidated at a time.
    function getLiquidationLimitInfo(address underlyingToken)
        external view
        returns (
            uint256 currentLimit,
            uint256 rate,
            uint256 maximum
        );
}

/// @title  IAlchemistV2
/// @author Alchemix Finance
interface IAlchemistV2 is
    IAlchemistV2Actions,
    IAlchemistV2AdminActions,
    IAlchemistV2Errors,
    IAlchemistV2Immutables,
    IAlchemistV2Events,
    IAlchemistV2State
{ }/**
 * @title IFlashLoanReceiver interface
 * @notice Interface for the Aave fee IFlashLoanReceiver.
 * @author Aave
 * @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract
 **/
interface IAaveFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}library DataTypes {
  // refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties.
  struct ReserveData {
    //stores the reserve configuration
    ReserveConfigurationMap configuration;
    //the liquidity index. Expressed in ray
    uint128 liquidityIndex;
    //variable borrow index. Expressed in ray
    uint128 variableBorrowIndex;
    //the current supply rate. Expressed in ray
    uint128 currentLiquidityRate;
    //the current variable borrow rate. Expressed in ray
    uint128 currentVariableBorrowRate;
    //the current stable borrow rate. Expressed in ray
    uint128 currentStableBorrowRate;
    uint40 lastUpdateTimestamp;
    //tokens addresses
    address aTokenAddress;
    address stableDebtTokenAddress;
    address variableDebtTokenAddress;
    //address of the interest rate strategy
    address interestRateStrategyAddress;
    //the id of the reserve. Represents the position in the list of the active reserves
    uint8 id;
  }

  struct ReserveConfigurationMap {
    //bit 0-15: LTV
    //bit 16-31: Liq. threshold
    //bit 32-47: Liq. bonus
    //bit 48-55: Decimals
    //bit 56: Reserve is active
    //bit 57: reserve is frozen
    //bit 58: borrowing is enabled
    //bit 59: stable rate borrowing enabled
    //bit 60-63: reserved
    //bit 64-79: reserve factor
    uint256 data;
  }

  struct UserConfigurationMap {
    uint256 data;
  }

  enum InterestRateMode {NONE, STABLE, VARIABLE}
}

interface IAaveLendingPool {

    function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint256);

    function getReserveData(address asset) external view returns (DataTypes.ReserveData memory);

    function flashLoan(
        address receiverAddress,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
    ) external;

    function repay(
        address asset,
        uint256 amount,
        uint256 rateMode,
        address onBehalfOf
    ) external returns (uint256);

    function withdraw(
        address asset,
        uint256 amount,
        address to
    ) external returns (uint256);

    function deposit(
        address asset,
        uint256 amount,
        address onBehalfOf,
        uint16 referralCode
    ) external;

    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        uint16 referralCode,
        address onBehalfOf
    ) external;

}/// @title  Whitelist
/// @author Alchemix Finance
interface IWhitelist {
  /// @dev Emitted when a contract is added to the whitelist.
  ///
  /// @param account The account that was added to the whitelist.
  event AccountAdded(address account);

  /// @dev Emitted when a contract is removed from the whitelist.
  ///
  /// @param account The account that was removed from the whitelist.
  event AccountRemoved(address account);

  /// @dev Emitted when the whitelist is deactivated.
  event WhitelistDisabled();

  /// @dev Returns the list of addresses that are whitelisted for the given contract address.
  ///
  /// @return addresses The addresses that are whitelisted to interact with the given contract.
  function getAddresses() external view returns (address[] memory addresses);

  /// @dev Returns the disabled status of a given whitelist.
  ///
  /// @return disabled A flag denoting if the given whitelist is disabled.
  function disabled() external view returns (bool);

  /// @dev Adds an contract to the whitelist.
  ///
  /// @param caller The address to add to the whitelist.
  function add(address caller) external;

  /// @dev Adds a contract to the whitelist.
  ///
  /// @param caller The address to remove from the whitelist.
  function remove(address caller) external;

  /// @dev Disables the whitelist of the target whitelisted contract.
  ///
  /// This can only occur once. Once the whitelist is disabled, then it cannot be reenabled.
  function disable() external;

  /// @dev Checks that the `msg.sender` is whitelisted when it is not an EOA.
  ///
  /// @param account The account to check.
  ///
  /// @return whitelisted A flag denoting if the given account is whitelisted.
  function isWhitelisted(address account) external view returns (bool);
}
/// @title A zapper for leveraged deposits into the Alchemist
abstract contract AutoleverageBase is IAaveFlashLoanReceiver {

    IWhitelist constant whitelist = IWhitelist(0xA3dfCcbad1333DC69997Da28C961FF8B2879e653);
    IAaveLendingPool constant flashLender = IAaveLendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);

    struct Details {
        address pool;
        int128 poolInputIndex;
        int128 poolOutputIndex;
        address alchemist;
        address yieldToken;
        address recipient;
        uint256 targetDebt;
    }

    /// @notice When the msg.sender is not whitelisted
    error Unauthorized(address sender);

    /// @notice When we're passed invalid parameters
    error IllegalArgument(string reason);
    
    /// @notice When the yieldToken has no underlyingToken in the alchemist
    error UnsupportedYieldToken(address yieldToken);

    /// @notice When the collateral is insufficient to mint targetDebt
    error MintFailure();

    /// @notice When the helper contract ends up with too few or too many tokens
    error InexactTokens(uint256 currentBalance, uint256 repayAmount);

    /// @notice Either convert received eth to weth, or transfer ERC20 from the msg.sender to this contract
    /// @param underlyingToken The ERC20 desired to transfer
    /// @param collateralInitial The amount of tokens taken from the user
    function _transferTokensToSelf(address underlyingToken, uint256 collateralInitial) internal virtual;

    /// @notice Convert received eth to weth, or do nothing
    /// @param amountOut The amount received from the curve swap
    function _maybeConvertCurveOutput(uint256 amountOut) internal virtual;

    /// @notice Swap on curve using the supplied params
    /// @param poolAddress Curve pool address
    /// @param debtToken The alAsset debt token address
    /// @param i Curve swap param
    /// @param j Curve swap param
    /// @param minAmountOut Minimum amount received from swap
    /// @return amountOut The actual amount received from swap
    function _curveSwap(address poolAddress, address debtToken, int128 i, int128 j, uint256 minAmountOut) internal virtual returns (uint256 amountOut);

    /// @notice Approve a contract to spend tokens
    function approve(address token, address spender) internal {
        IERC20(token).approve(spender, type(uint256).max);
    }

    /// @notice Transfer tokens from msg.sender here, then call flashloan which calls callback
    /// @dev Must have targetDebt > collateralTotal - collateralInitial, otherwise flashloan payback will fail
    /// @param pool The address of the curve pool to swap on
    /// @param poolInputIndex The `i` param for the curve swap
    /// @param poolOutputIndex The `j` param for the curve swap
    /// @param alchemist The alchemist to deposit and mint from
    /// @param yieldToken The yieldToken to convert deposits into
    /// @param collateralInitial The amount of tokens that will be taken from the user
    /// @param collateralTotal The amount of tokens that will be deposited as collateral for the user
    /// @param targetDebt The amount of debt that the user will incur
    function autoleverage(
        address pool,
        int128 poolInputIndex,
        int128 poolOutputIndex,
        address alchemist,
        address yieldToken,
        uint256 collateralInitial,
        uint256 collateralTotal,
        uint256 targetDebt
    ) external payable {
        // Gate on EOA or whitelisted
        if (!(tx.origin == msg.sender || whitelist.isWhitelisted(msg.sender))) revert Unauthorized(msg.sender);

        // Get underlying token from alchemist
        address underlyingToken = IAlchemistV2(alchemist).getYieldTokenParameters(yieldToken).underlyingToken;
        if (underlyingToken == address(0)) revert UnsupportedYieldToken(yieldToken);

        _transferTokensToSelf(underlyingToken, collateralInitial);

        // Take out flashloan
        address[] memory assets = new address[](1);
        assets[0] = underlyingToken;
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = collateralTotal - collateralInitial;
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;

        bytes memory params = abi.encode(Details({
            pool: pool,
            poolInputIndex: poolInputIndex,
            poolOutputIndex: poolOutputIndex,
            alchemist: alchemist,
            yieldToken: yieldToken,
            recipient: msg.sender,
            targetDebt: targetDebt
        }));

        flashLender.flashLoan(
            address(this),
            assets,
            amounts,
            modes,
            address(0), // onBehalfOf, not used here
            params, // params, passed to callback func to decode as struct
            0 // referralCode
        );
    }

    /// @notice Flashloan callback receiver, will be called by IAaveLendingPool.flashloan()
    /// @dev Never call this function directly
    /// @param assets An array of length 1, pointing to the ERC20 received in the flashloan
    /// @param amounts An array of length 1, with the ERC20 amount received in the flashloan
    /// @param premiums An array of length 1, with the flashloan fee. We will pay back amounts[0] + premiums[0] to the flashloan provider
    /// @param initiator Points to who initiated the flashloan
    /// @param params ABI-encoded `Details` struct containing many details about desired functionality
    /// @return success Always true unless reverts, required by Aave flashloan
    function executeOperation(
        address[] calldata assets,
        uint[] calldata amounts,
        uint[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        // Only called by flashLender
        if (msg.sender != address(flashLender)) revert Unauthorized(msg.sender);
        if (initiator != address(this)) revert IllegalArgument("flashloan initiator must be self");
        Details memory details = abi.decode(params, (Details));
        uint256 repayAmount = amounts[0] + premiums[0];

        uint256 collateralBalance = IERC20(assets[0]).balanceOf(address(this));

        // Deposit into recipient's account
        approve(assets[0], details.alchemist);
        IAlchemistV2(details.alchemist).depositUnderlying(details.yieldToken, collateralBalance, details.recipient, 0);

        // Mint from recipient's account
        try IAlchemistV2(details.alchemist).mintFrom(details.recipient, details.targetDebt, address(this)) {

        } catch {
            revert MintFailure();
        }

        {
            address debtToken = IAlchemistV2(details.alchemist).debtToken();
            uint256 amountOut = _curveSwap(
                details.pool, 
                debtToken, 
                details.poolInputIndex, 
                details.poolOutputIndex, 
                repayAmount
            );

            _maybeConvertCurveOutput(amountOut);


            // Deposit excess assets into the alchemist on behalf of the user
            uint256 excessCollateral = amountOut - repayAmount;
            if (excessCollateral > 0) {
                IAlchemistV2(details.alchemist).depositUnderlying(details.yieldToken, excessCollateral, details.recipient, 0);
            }
        }

        // Approve the LendingPool contract allowance to *pull* the owed amount
        approve(assets[0], address(flashLender));
        uint256 balance = IERC20(assets[0]).balanceOf(address(this));
        if (balance != repayAmount) {
            revert InexactTokens(balance, repayAmount);
        }

        return true;
    }

}
/// @title A zapper for leveraged deposits into the Alchemist
contract AutoleverageCurveMetapool is AutoleverageBase {

    /// @inheritdoc AutoleverageBase
    function _transferTokensToSelf(address underlyingToken, uint256 collateralInitial) internal override {
        if (msg.value > 0) revert IllegalArgument("msg.value should be 0");
        IERC20(underlyingToken).transferFrom(msg.sender, address(this), collateralInitial);
    }