// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.16;

import {IStrategy} from 'src/interfaces/IStrategy.sol';
import {SafeERC20Namer} from 'src/erc/SafeERC20Namer.sol';
import {MinimalTransferHelper} from 'src/utils/MinimalTransferHelper.sol';
import {IERC20} from 'src/interfaces/IERC20.sol';
import {ERC20Permit} from 'src/erc/ERC20Permit.sol';
import {IFYToken} from 'src/interfaces/IFYToken.sol';
import {IPool} from 'src/interfaces/IPool.sol';
import {ILender} from 'src/interfaces/ILender.sol';

/// @dev The Strategy contract allows liquidity providers to provide liquidity in yieldspace
/// pool tokens and receive strategy tokens that represent a stake in a YieldSpace pool contract.
/// Upon maturity, the strategy can `divest` from the mature pool, becoming a proportional
/// ownership underlying vault. When not invested, the strategy can `invest` into a Pool using
/// all its underlying.
/// The strategy can also `eject` from a Pool before maturity. Any fyToken obtained will be available
/// to be bought by anyone at face value. If the pool tokens can't be burned, they will be ejected
/// and the strategy can be recapitalized.
contract Strategy is ERC20Permit, IStrategy {
    using MinimalTransferHelper for IERC20;
    using MinimalTransferHelper for IFYToken;
    using MinimalTransferHelper for IPool;

    event Invested(
        address indexed pool,
        uint256 baseInvested,
        uint256 lpTokensObtained
    );
    event Divested(
        address indexed pool,
        uint256 lpTokenDivested,
        uint256 baseObtained
    );
    event Ejected(
        address indexed pool,
        uint256 lpTokenDivested,
        uint256 baseObtained,
        uint256 fyTokenObtained
    );
    event Drained(address indexed pool, uint256 lpTokenDivested);
    event SoldFYToken(uint256 soldFYToken, uint256 returnedBase);
    event Trade(
        address indexed sent,
        address indexed received,
        uint256 amountIn,
        uint256 amountOut,
        address receiver
    );

    address public admin; // The admin executes the transition to the next pool
    address public lender; // The lender contract used for getting fyTokens in the invest call

    State public state; // The state determines which functions are available

    IERC20 public immutable base; // Base token for this strategy
    IFYToken public fyToken; // Current fyToken for this strategy
    uint256 public maturity; // Current maturity for this strategy
    IPool public pool; // Current pool that this strategy invests in

    uint256 public baseCached; // Base tokens held by the strategy
    uint256 public poolCached; // Pool tokens held by the strategy
    uint256 public fyTokenCached; // In emergencies, the strategy can keep fyToken

    constructor(
        string memory name_,
        string memory symbol_,
        IFYToken fyToken_
    )
        ERC20Permit(
            string(name_),
            string(symbol_),
            SafeERC20Namer.tokenDecimals(address(fyToken_))
        )
    {
        // Deploy with a seriesId_ matching the migrating strategy if using the migration feature
        // Deploy with any series matching the desired base in any other case
        fyToken = fyToken_;

        base = IERC20(fyToken_.underlying());

        admin = msg.sender; // Enable the `mint` -> `init` hook.
    }

    modifier isState(State target) {
        require(target == state, 'Not allowed in this state');
        _;
    }

    modifier authorized(address a) {
        require(msg.sender == a);
        _;
    }

    /// @notice Allows for the transfer of the admin contract
    /// @param a New admin
    function setAdmin(address a) external authorized(admin) {
        admin = a;
    }

    /// @notice Allows for the transfer of the lender contract
    /// @param l New lender
    function setLender(address l) external authorized(admin) {
        lender = l;
    }

    /// @notice Allows the admin to approve an underlying asset for the Strategy
    /// @param base_ The underlying asset that will be approved
    function approveUnderlying(address base_) external authorized(admin) {
        // Approve the lender to spend the base asset
        IERC20(base_).approve(lender, type(uint256).max);
    }

    /// @dev State and state variable management
    /// @param target State to transition to
    /// @param pool_ If transitioning to invested, update pool state variable with this parameter
    function _transition(State target, IPool pool_) internal {
        if (target == State.INVESTED) {
            pool = pool_;
            fyToken = IFYToken(address(pool_.fyToken()));
            maturity = pool_.maturity();
        } else if (target == State.DIVESTED) {
            delete fyToken;
            delete maturity;
            delete pool;
        } else if (target == State.EJECTED) {
            delete maturity;
            delete pool;
        } else if (target == State.DRAINED) {
            delete maturity;
            delete pool;
        }
        state = target;
    }

    /// @dev State and state variable management
    /// @param target State to transition to
    function _transition(State target) internal {
        require(target != State.INVESTED, 'Must provide a pool');
        _transition(target, IPool(address(0)));
    }

    // ----------------------- INVEST & DIVEST --------------------------- //

    /// @dev Mint the first strategy tokens, without investing
    /// @param to Recipient for the strategy tokens
    /// @return minted Amount of strategy tokens minted from base tokens
    function init(
        address to
    ) external authorized(admin) returns (uint256 minted) {
        minted = _init(to);
    }

    /// @dev Mint the first strategy tokens, without investing
    /// @param to Recipient for the strategy tokens
    /// @return minted Amount of strategy tokens minted from base tokens
    function _init(
        address to
    ) internal isState(State.DEPLOYED) returns (uint256 minted) {
        // Clear fyToken in case we initialized through `mint`
        delete fyToken;

        baseCached = minted = base.balanceOf(address(this));
        require(minted > 0, 'Not enough base in');
        // Make sure that at the end of the transaction the strategy has enough tokens as to not expose itself to a rounding-down liquidity attack.
        _mint(to, minted);

        _transition(State.DIVESTED);
    }

    /// @dev Start the strategy investments in the next pool
    /// @param pool_ Pool to invest into
    /// @param initial_ Amount of base that will be used to call Pool::init
    /// @param ptsToSell_ Amount of iPTs that will be sold to the pool to set a rate
    /// @param minRatio_ Minimum ratio to use in the mint call
    /// @param maxRatio_ Maximum ratio to use in the mint call
    /// @param lends_ Data used to conduct lending operations in pool's new market (target is to acquire ptsToSell + ptsToMint iPTs)
    /// @return poolTokensObtained Amount of pool tokens minted from base tokens
    /// @notice When calling this function for the first pool, some underlying needs to be transferred to the strategy first, using a batchable router.
    /// @notice Caller should take care to ensure that "initial_ + [amount spent in lends_] = baseCached" is true prior to call
    /// @notice Caller should grant Strategy admin control of pool_ prior to calling invest
    function invest(
        IPool pool_,
        uint256 initial_,
        uint256 ptsToSell_,
        uint256 minRatio_,
        uint256 maxRatio_,
        bytes[] calldata lends_
    )
        external
        authorized(admin)
        isState(State.DIVESTED)
        returns (uint256 poolTokensObtained)
    {
        // Caching
        IFYToken fyToken_ = IFYToken(address(pool_.fyToken()));
        uint256 baseCached_ = baseCached; // We could read the real balance, but this is a bit safer

        require(base == pool_.base(), 'Mismatched base');

        // Mint LP tokens and initialize the pool
        delete baseCached;
        base.safeTransfer(address(pool_), initial_);
        (, , poolTokensObtained) = pool_.init(address(this));

        // Acquire iPTs via lending in the new market
        ILender(lender).batch(lends_);

        // Transfer the acquired tokens to the pool
        fyToken_.transfer(address(pool_), ptsToSell_);

        // Swap FY tokens for base in the new market
        // NOTE: We sell to the pool to avoid a subsequent transfer to the pool
        pool_.sellFYToken(address(pool_), 0);

        // At this stage, the Strategy will hold iPTs (the ones not sold)
        uint256 fyTokensRemaining = fyToken_.balanceOf(address(this));

        // Transfer remaining acquired iPTs to the pool for the mint call
        fyToken_.transfer(address(pool_), fyTokensRemaining);

        // Mint at the current rate with the remaining iPTs, any excess is returned to the admin
        (, , uint256 mintedLpTokens) = IPool(pool_).mint(
            address(this),
            admin,
            minRatio_,
            maxRatio_
        );

        // Update amount of LP tokens held by the strategy
        poolTokensObtained += mintedLpTokens;
        poolCached = poolTokensObtained;

        // Set the pool's strategy
        IPool(pool_).setStrategy(address(this));

        // Return control of pool's admin to admin
        pool_.setAdmin(admin);

        // Update state variables
        fyToken = fyToken_;
        maturity = pool_.maturity();
        pool = pool_;

        _transition(State.INVESTED, pool_);
        emit Invested(address(pool_), baseCached_, poolTokensObtained);
    }

    /// @dev Divest out of a pool once it has matured
    /// @return baseObtained Amount of base tokens obtained from burning pool tokens
    function divest()
        external
        isState(State.INVESTED)
        returns (uint256 baseObtained)
    {
        // Caching
        IPool pool_ = pool;
        IFYToken fyToken_ = fyToken;
        require(uint32(block.timestamp) >= maturity, 'Only after maturity');

        uint256 toDivest = pool_.balanceOf(address(this));

        // Burn lpTokens
        delete poolCached;
        pool_.safeTransfer(address(pool_), toDivest);
        (, uint256 baseFromBurn, uint256 fyTokenFromBurn) = pool_.burn(
            address(this),
            address(this),
            0,
            type(uint256).max
        ); // We don't care about slippage, because the strategy holds to maturity

        // Redeem any fyToken
        uint256 baseFromRedeem = fyToken_.redeem(
            fyTokenFromBurn,
            address(this),
            address(this)
        );

        // Reset the base cache
        baseCached = base.balanceOf(address(this));

        // Transition to Divested
        _transition(State.DIVESTED, pool_);
        emit Divested(
            address(pool_),
            toDivest,
            baseObtained = baseFromBurn + baseFromRedeem
        );
    }

    // ----------------------- EJECT --------------------------- //

    /// @dev Divest out of a pool at any time. If possible the pool tokens will be burnt for base and fyToken, the latter of which
    /// must be sold to return the strategy to a functional state. If the pool token burn reverts, the pool tokens will be transferred
    /// to the caller as a last resort.
    /// @return baseReceived Amount of base tokens received from pool tokens
    /// @return fyTokenReceived Amount of fyToken received from pool tokens
    /// @notice The caller must take care of slippage when selling fyToken, if relevant.
    function eject()
        external
        authorized(admin)
        isState(State.INVESTED)
        returns (uint256 baseReceived, uint256 fyTokenReceived)
    {
        // Caching
        IPool pool_ = pool;
        uint256 toDivest = pool_.balanceOf(address(this));

        // Burn lpTokens, if not possible, eject the pool tokens out. Slippage should be managed by the caller.
        delete poolCached;
        try this.burnPoolTokens(pool_, toDivest) returns (
            uint256 baseReceived_,
            uint256 fyTokenReceived_
        ) {
            baseCached = baseReceived = baseReceived_;
            fyTokenCached = fyTokenReceived = fyTokenReceived_;
            if (fyTokenReceived > 0) {
                _transition(State.EJECTED, pool_);
                emit Ejected(
                    address(pool_),
                    toDivest,
                    baseReceived,
                    fyTokenReceived
                );
            } else {
                _transition(State.DIVESTED, pool_);
                emit Divested(address(pool_), toDivest, baseReceived);
            }
        } catch {
            pool_.safeTransfer(msg.sender, toDivest);
            _transition(State.DRAINED, pool_);
            emit Drained(address(pool_), toDivest);
        }
    }

    /// @dev Burn an amount of pool tokens.
    /// @notice Only the Strategy itself can call this function. It is external and exists so that the transfer is reverted if the burn also reverts.
    /// @param pool_ Pool for the pool tokens.
    /// @param poolTokens Amount of tokens to burn.
    /// @return baseReceived Amount of base tokens received from pool tokens
    /// @return fyTokenReceived Amount of fyToken received from pool tokens
    function burnPoolTokens(
        IPool pool_,
        uint256 poolTokens
    ) external returns (uint256 baseReceived, uint256 fyTokenReceived) {
        require(msg.sender == address(this), 'Unauthorized');

        // Burn lpTokens
        pool_.safeTransfer(address(pool_), poolTokens);
        uint256 baseBalance = base.balanceOf(address(this));
        uint256 fyTokenBalance = fyToken.balanceOf(address(this));
        (, baseReceived, fyTokenReceived) = pool_.burn(
            address(this),
            address(this),
            0,
            type(uint256).max
        );
        require(
            base.balanceOf(address(this)) - baseBalance == baseReceived,
            'Burn failed - base'
        );
        require(
            fyToken.balanceOf(address(this)) - fyTokenBalance ==
                fyTokenReceived,
            'Burn failed - fyToken'
        );
    }

    /// @dev Buy ejected fyToken in the strategy at face value
    /// @param fyTokenTo Address to send the purchased fyToken to.
    /// @param baseTo Address to send any remaining base to.
    /// @return soldFYToken Amount of fyToken sold.
    /// @return returnedBase Amount of base unused and returned.
    function buyFYToken(
        address fyTokenTo,
        address baseTo
    )
        external
        isState(State.EJECTED)
        returns (uint256 soldFYToken, uint256 returnedBase)
    {
        // Caching
        IFYToken fyToken_ = fyToken;
        uint256 baseCached_ = baseCached;
        uint256 fyTokenCached_ = fyTokenCached;

        uint256 baseIn = base.balanceOf(address(this)) - baseCached_;
        (soldFYToken, returnedBase) = baseIn > fyTokenCached_
            ? (fyTokenCached_, baseIn - fyTokenCached_)
            : (baseIn, 0);

        // Update base and fyToken cache
        baseCached = baseCached_ + soldFYToken; // soldFYToken is base not returned
        fyTokenCached = fyTokenCached_ -= soldFYToken;

        // Transition to divested if done
        if (fyTokenCached_ == 0) {
            // Transition to Divested
            _transition(State.DIVESTED);
            emit Divested(address(0), 0, 0);
        }

        // Transfer fyToken and base (if surplus)
        fyToken_.safeTransfer(fyTokenTo, soldFYToken);
        if (soldFYToken < baseIn) {
            base.safeTransfer(baseTo, baseIn - soldFYToken);
        }

        emit SoldFYToken(soldFYToken, returnedBase);
    }

    /// @dev If we drained the strategy, we can recapitalize it with base to avoid a forced migration
    /// @return baseIn Amount of base tokens used to restart
    function restart()
        external
        authorized(admin)
        isState(State.DRAINED)
        returns (uint256 baseIn)
    {
        require(
            (baseCached = baseIn = base.balanceOf(address(this))) > 0,
            'No base to restart'
        );
        _transition(State.DIVESTED);
        emit Divested(address(0), 0, 0);
    }

    // ----------------------- MINT & BURN --------------------------- //

    /// @dev Mint strategy tokens with pool tokens. It can be called only when invested.
    /// @param to Recipient for the strategy tokens
    /// @return minted Amount of strategy tokens minted
    /// @notice The pool tokens that the user contributes need to have been transferred previously, using a batchable router.
    function mint(
        address to
    ) external isState(State.INVESTED) returns (uint256 minted) {
        // Caching
        IPool pool_ = pool;
        uint256 poolCached_ = poolCached;

        // Find how much was deposited
        uint256 deposit = pool_.balanceOf(address(this)) - poolCached_;

        // Update the pool cache
        poolCached = poolCached_ + deposit;

        // Mint strategy tokens
        minted = (_totalSupply * deposit) / poolCached_;
        _mint(to, minted);

        emit Trade(address(pool), address(this), deposit, minted, to);
    }

    /// @dev Burn strategy tokens to withdraw pool tokens. It can be called only when invested.
    /// @param to Recipient for the pool tokens
    /// @return poolTokensObtained Amount of pool tokens obtained
    /// @notice The strategy tokens that the user burns need to have been transferred previously, using a batchable router.
    function burn(
        address to
    ) external isState(State.INVESTED) returns (uint256 poolTokensObtained) {
        // Caching
        IPool pool_ = pool;
        uint256 poolCached_ = poolCached;
        uint256 totalSupply_ = _totalSupply;

        // Burn strategy tokens
        uint256 burnt = _balanceOf[address(this)];
        _burn(address(this), burnt);

        poolTokensObtained = (poolCached_ * burnt) / totalSupply_;
        pool_.safeTransfer(address(to), poolTokensObtained);

        // Update pool cache
        poolCached = poolCached_ - poolTokensObtained;

        emit Trade(address(this), address(pool), burnt, poolTokensObtained, to);
    }

    /// @dev Mint strategy tokens with base tokens. It can be called only when not invested and not ejected.
    /// @param to Recipient for the strategy tokens
    /// @return minted Amount of strategy tokens minted
    /// @notice The base tokens that the user invests need to have been transferred previously, using a batchable router.
    function mintDivested(
        address to
    ) external isState(State.DIVESTED) returns (uint256 minted) {
        // minted = supply * value(deposit) / value(strategy)
        uint256 baseCached_ = baseCached;
        uint256 deposit = base.balanceOf(address(this)) - baseCached_;
        baseCached = baseCached_ + deposit;

        minted = (_totalSupply * deposit) / baseCached_;

        _mint(to, minted);

        emit Trade(address(base), address(this), deposit, minted, to);
    }

    /// @dev Burn strategy tokens to withdraw base tokens. It can be called when not invested and not ejected.
    /// @param to Recipient for the base tokens
    /// @return baseObtained Amount of base tokens obtained
    /// @notice The strategy tokens that the user burns need to have been transferred previously, using a batchable router.
    function burnDivested(
        address to
    ) external isState(State.DIVESTED) returns (uint256 baseObtained) {
        // strategy * burnt/supply = withdrawal
        uint256 baseCached_ = baseCached;
        uint256 burnt = _balanceOf[address(this)];
        baseObtained = (baseCached_ * burnt) / _totalSupply;
        baseCached = baseCached_ - baseObtained;

        _burn(address(this), burnt);
        base.safeTransfer(to, baseObtained);

        emit Trade(address(this), address(base), burnt, baseObtained, to);
    }
}
[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"contract IFYToken","name":"fyToken_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpTokenDivested","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseObtained","type":"uint256"}],"name":"Divested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpTokenDivested","type":"uint256"}],"name":"Drained","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpTokenDivested","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseObtained","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fyTokenObtained","type":"uint256"}],"name":"Ejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"uint256","name":"baseInvested","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpTokensObtained","type":"uint256"}],"name":"Invested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"soldFYToken","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"returnedBase","type":"uint256"}],"name":"SoldFYToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sent","type":"address"},{"indexed":true,"internalType":"address","name":"received","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"}],"name":"Trade","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"base_","type":"address"}],"name":"approveUnderlying","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"base","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseCached","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"poolTokensObtained","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burnDivested","outputs":[{"internalType":"uint256","name":"baseObtained","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IPool","name":"pool_","type":"address"},{"internalType":"uint256","name":"poolTokens","type":"uint256"}],"name":"burnPoolTokens","outputs":[{"internalType":"uint256","name":"baseReceived","type":"uint256"},{"internalType":"uint256","name":"fyTokenReceived","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fyTokenTo","type":"address"},{"internalType":"address","name":"baseTo","type":"address"}],"name":"buyFYToken","outputs":[{"internalType":"uint256","name":"soldFYToken","type":"uint256"},{"internalType":"uint256","name":"returnedBase","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deploymentChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"divest","outputs":[{"internalType":"uint256","name":"baseObtained","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"eject","outputs":[{"internalType":"uint256","name":"baseReceived","type":"uint256"},{"internalType":"uint256","name":"fyTokenReceived","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fyToken","outputs":[{"internalType":"contract IFYToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fyTokenCached","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"init","outputs":[{"internalType":"uint256","name":"minted","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IPool","name":"pool_","type":"address"},{"internalType":"uint256","name":"initial_","type":"uint256"},{"internalType":"uint256","name":"ptsToSell_","type":"uint256"},{"internalType":"uint256","name":"minRatio_","type":"uint256"},{"internalType":"uint256","name":"maxRatio_","type":"uint256"},{"internalType":"bytes[]","name":"lends_","type":"bytes[]"}],"name":"invest","outputs":[{"internalType":"uint256","name":"poolTokensObtained","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maturity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"minted","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mintDivested","outputs":[{"internalType":"uint256","name":"minted","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pool","outputs":[{"internalType":"contract IPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolCached","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"restart","outputs":[{"internalType":"uint256","name":"baseIn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"l","type":"address"}],"name":"setLender","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"state","outputs":[{"internalType":"enum IStrategy.State","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"}]