// File: contracts/altoids.sol



pragma solidity 0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";

interface ICurvePool {
    function get_dy(
        int128 in_index,
        int128 out_index,
        uint256 in_amount
    ) external view returns (uint256);

    function exchange(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    ) external returns (uint256);
}


interface IDepositor {
    function incentiveToken() external view returns (uint256);

    function lockToken() external;
}

interface IUniV2Router {
    function getAmountsOut(uint256 amountIn, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

interface IERC20 {
    function approve(address spender, uint256 amount) external;

    function balanceOf(address) external view returns (uint256);
}

contract Altoids is Ownable {
    IDepositor constant depositorFxs =
        IDepositor(0xFaF3740167B866b571465B063c6B3A71Ba9b6285);
    IDepositor constant depositorAngle =
        IDepositor(0x8A97e8B3389D431182aC67c0DF7D46FF8DCE7121);
    IDepositor constant depositorCrv =
        IDepositor(0xc1e3Ca8A3921719bE0aE3690A0e036feB4f69191);
    IDepositor constant depositorBpt =
        IDepositor(0x3e0d44542972859de3CAdaF856B1a4FD351B4D2E);

    IERC20 constant sdFxs = IERC20(0x402F878BDd1f5C66FdAF0fabaBcF74741B68ac36);
    IERC20 constant sdAngle =
        IERC20(0x752B4c6e92d96467fE9b9a2522EF07228E00F87c);
    IERC20 constant sdCrv = IERC20(0xD1b5651E55D4CeeD36251c61c50C889B36F6abB5);
    IERC20 constant sdBpt = IERC20(0xF24d8651578a55b0C119B9910759a351A3458895);
    IERC20 constant fxs = IERC20(0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0);
    IERC20 constant angle = IERC20(0x31429d1856aD1377A8A0079410B297e1a9e214c2);
    IERC20 constant crv = IERC20(0xD533a949740bb3306d119CC777fa900bA034cd52);
    IERC20 constant bpt = IERC20(0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56);
    IERC20 constant bal = IERC20(0xba100000625a3754423978a60c9317c58a424e3D);
    IERC20 constant weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    ICurvePool constant curveFxs =
        ICurvePool(0x8c524635d52bd7b1Bd55E062303177a7d916C046);
    ICurvePool constant curveAngle =
        ICurvePool(0x48fF31bBbD8Ab553Ebe7cBD84e1eA3dBa8f54957);
    ICurvePool constant curveCrv =
        ICurvePool(0xf7b55C3732aD8b2c2dA7c24f30A69f55c54FB717);
    // ICurvePool constant curveBpt =
    //     ICurvePool(0x9d259cA698746586107C234e9E9461d385ca1041);

    address[] fxs_eth = [address(fxs), address(weth)];
    address[] angle_eth = [address(angle), address(weth)];
    address[] crv_eth = [address(crv), address(weth)];

    IUniV2Router constant sushiswap =
        IUniV2Router(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);
    uint256 constant MAX_UINT = ~uint256(0); // maximum uint value

    constructor() {
        sdFxs.approve(address(curveFxs), MAX_UINT);
        sdAngle.approve(address(curveAngle), MAX_UINT);
        sdCrv.approve(address(curveCrv), MAX_UINT);

        fxs.approve(address(sushiswap), MAX_UINT);
        angle.approve(address(sushiswap), MAX_UINT);
        crv.approve(address(sushiswap), MAX_UINT);
    }

    function claimables() public view returns (uint256[4] memory _mintables) {
        _mintables[0] = depositorFxs.incentiveToken();
        _mintables[1] = depositorAngle.incentiveToken();
        _mintables[2] = depositorCrv.incentiveToken();
        // _mintables[3] = depositorBpt.incentiveToken();
        return _mintables;
    }

    function natives(uint256[4] memory _mintables)
        public
        view
        returns (uint256[4] memory _natives)
    {
        if (_mintables[0] > 0) {
            _natives[0] = curveFxs.get_dy(1, 0, _mintables[0]);
        }
        if (_mintables[1] > 0) {
            _natives[1] = curveAngle.get_dy(1, 0, _mintables[1]);
        }
        if (_mintables[2] > 0) {
            _natives[2] = curveCrv.get_dy(1, 0, _mintables[2]);
        }
        // if (_mintables[3] > 0) {
        //     _natives[3] = curveBpt.get_dy(1, 0, _mintables[3]);
        // }
    }

    function claimable_natives()
        public
        view
        returns (uint256[4] memory _natives)
    {
        return natives(claimables());
    }

    function earnings(uint256[4] memory _natives)
        public
        view
        returns (uint256[4] memory _earnings)
    {
        if (_natives[0] > 0) {
            _earnings[0] = sushiswap.getAmountsOut(_natives[0], fxs_eth)[1];
        }
        if (_natives[1] > 0) {
            _earnings[1] = sushiswap.getAmountsOut(_natives[1], angle_eth)[1];
        }
        if (_natives[2] > 0) {
            _earnings[2] = sushiswap.getAmountsOut(_natives[2], crv_eth)[1];
        }
    }

    function earnable_claimable_natives()
        public
        view
        returns (uint256[4] memory _earnings)
    {
        return earnings(natives(claimables()));
    }

    function print(bool[] memory _doables)
        public
        payable
        onlyOwner
        returns (uint256)
    {
        uint256 before = owner().balance;
        if (_doables[0]) {
            depositorFxs.lockToken();
            curveFxs.exchange(1, 0, sdFxs.balanceOf(address(this)), 0);
            sushiswap.swapExactTokensForETH(
                fxs.balanceOf(address(this)),
                0,
                fxs_eth,
                owner(),
                MAX_UINT
            );
        }
        if (_doables[1]) {
            depositorAngle.lockToken();
            curveAngle.exchange(1, 0, sdAngle.balanceOf(address(this)), 0);
            sushiswap.swapExactTokensForETH(
                angle.balanceOf(address(this)),
                0,
                angle_eth,
                owner(),
                MAX_UINT
            );
        }
        if (_doables[2]) {
            depositorCrv.lockToken();
            curveCrv.exchange(1, 0, sdCrv.balanceOf(address(this)), 0);
            sushiswap.swapExactTokensForETH(
                crv.balanceOf(address(this)),
                0,
                crv_eth,
                owner(),
                MAX_UINT
            );
        }
        uint256 af = owner().balance;
        require(af > before, "no profit");
        return af - before;
    }

    receive() external payable {}

    fallback() external payable {}
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}