// File: src/roller/Strategy.sol



// 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);
    }
}

// File: src/interfaces/IStrategy.sol



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

import {IERC20} from 'src/interfaces/IERC20.sol';
import {IFYToken} from 'src/interfaces/IFYToken.sol';
import {IPool} from 'src/interfaces/IPool.sol';

/// @dev The Strategy contract allows liquidity providers to provide liquidity in underlying
/// 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, immediately converting its assets
/// to underlying as much as possible. If any fyToken can't be exchanged for underlying, the
/// strategy will hold them until maturity when `redeemEjected` can be used.
interface IStrategy {
    enum State {
        DEPLOYED,
        DIVESTED,
        INVESTED,
        EJECTED,
        DRAINED
    }

    function state() external view returns (State); // The state determines which functions are available

    function base() external view returns (IERC20); // Base token for this strategy

    function fyToken() external view returns (IFYToken); // Current fyToken for this strategy

    function pool() external view returns (IPool); // Current pool that this strategy invests in

    function maturity() external view returns (uint256); // Current maturity of the pool that this strategy investes in

    function baseCached() external view returns (uint256); // Base tokens owned by the strategy after the last operation

    function fyTokenCached() external view returns (uint256); // In emergencies, the strategy can keep fyToken of one series

    function poolCached() external view returns (uint256); // Pool tokens owned by the strategy during the investment period

    /// @dev Mint the first strategy tokens, without investing
    function init(address to) external returns (uint256 minted);

    /// @dev Start the strategy investments in the next pool
    /// @notice When calling this function for the first pool, some underlying needs to be transferred to the strategy first, using a batchable router.
    function invest(
        IPool pool_,
        uint256 initial_,
        uint256 ptsToSell_,
        uint256 minRatio_,
        uint256 maxRatio_,
        bytes[] calldata lends_
    ) external returns (uint256 poolTokensObtained);

    /// @dev Divest out of a pool once it has matured
    function divest() external returns (uint256 baseObtained);

    /// @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.
    /// @notice The caller must take care of slippage when selling fyToken, if relevant.
    function eject()
        external
        returns (uint256 baseObtained, uint256 fyTokenObtained);

    /// @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 returns (uint256 soldFYToken, uint256 returnedBase);

    /// @dev If we ejected the pool tokens, we can recapitalize the strategy to avoid a forced migration
    function restart() external returns (uint256 baseIn);

    /// @dev Mint strategy tokens with pool tokens. It can be called only when invested.
    /// @notice The pool tokens that the user contributes need to have been transferred previously, using a batchable router.
    function mint(address to) external returns (uint256 minted);

    /// @dev Burn strategy tokens to withdraw pool tokens. It can be called only when invested.
    /// @notice The strategy tokens that the user burns need to have been transferred previously, using a batchable router.
    function burn(address to) external returns (uint256 poolTokensObtained);

    /// @dev Mint strategy tokens with base tokens. It can be called only when not invested and not ejected.
    /// @notice The base tokens that the user invests need to have been transferred previously, using a batchable router.
    function mintDivested(address to) external returns (uint256 minted);

    /// @dev Burn strategy tokens to withdraw base tokens. It can be called when not invested and not ejected.
    /// @notice The strategy tokens that the user burns need to have been transferred previously, using a batchable router.
    function burnDivested(
        address baseTo
    ) external returns (uint256 baseObtained);

    /// @dev Allows transfer of the admin
    function setAdmin(address) external;

    /// @dev Allows admin to set the lender
    function setLender(address) external;

    /// @dev Returns the admin
    function admin() external view returns (address);

    /// @dev Returns the lender
    function lender() external view returns (address);

    /// @dev Approves the usage of the lender's lend methods
    function approveUnderlying(address) external;
}

// File: src/erc/SafeERC20Namer.sol



// SPDX-License-Identifier: MIT
// Last audit: https://github.com/yieldprotocol/yield-utils-v2/commit/0d0b08b6b67cef6dfa69e6e6539bee542f49e25b
// Report: https://code4rena.com/reports/2021-05-yield

pragma solidity >=0.5.0;

import 'src/interfaces/IERC20Metadata.sol';
import 'src/utils/AddressStringUtil.sol';

// produces token descriptors from inconsistent or absent ERC20 symbol implementations that can return string or bytes32
// this library will always produce a string symbol to represent the token
library SafeERC20Namer {
    function bytes32ToString(bytes32 x) private pure returns (string memory) {
        bytes memory bytesString = new bytes(32);
        uint256 charCount = 0;
        for (uint256 j = 0; j < 32; j++) {
            bytes1 char = x[j];
            if (char != 0) {
                bytesString[charCount] = char;
                charCount++;
            }
        }
        bytes memory bytesStringTrimmed = new bytes(charCount);
        for (uint256 j = 0; j < charCount; j++) {
            bytesStringTrimmed[j] = bytesString[j];
        }
        return string(bytesStringTrimmed);
    }

    // assumes the data is in position 2
    function parseStringData(
        bytes memory b
    ) private pure returns (string memory) {
        uint256 charCount = 0;
        // first parse the charCount out of the data
        for (uint256 i = 32; i < 64; i++) {
            charCount <<= 8;
            charCount += uint8(b[i]);
        }

        bytes memory bytesStringTrimmed = new bytes(charCount);
        for (uint256 i = 0; i < charCount; i++) {
            bytesStringTrimmed[i] = b[i + 64];
        }

        return string(bytesStringTrimmed);
    }

    // uses a heuristic to produce a token name from the address
    // the heuristic returns the full hex of the address string in upper case
    function addressToName(address token) private pure returns (string memory) {
        return AddressStringUtil.toAsciiString(token, 40);
    }

    // uses a heuristic to produce a token symbol from the address
    // the heuristic returns the first 6 hex of the address string in upper case
    function addressToSymbol(
        address token
    ) private pure returns (string memory) {
        return AddressStringUtil.toAsciiString(token, 6);
    }

    // calls an external view token contract method that returns a symbol or name, and parses the output into a string
    function callAndParseStringReturn(
        address token,
        bytes4 selector
    ) private view returns (string memory) {
        (bool success, bytes memory data) = token.staticcall(
            abi.encodeWithSelector(selector)
        );
        // if not implemented, or returns empty data, return empty string
        if (!success || data.length == 0) {
            return '';
        }
        // bytes32 data always has length 32
        if (data.length == 32) {
            bytes32 decoded = abi.decode(data, (bytes32));
            return bytes32ToString(decoded);
        } else if (data.length > 64) {
            return abi.decode(data, (string));
        }
        return '';
    }

    // attempts to extract the token symbol. if it does not implement symbol, returns a symbol derived from the address
    function tokenSymbol(address token) public view returns (string memory) {
        string memory symbol = callAndParseStringReturn(
            token,
            IERC20Metadata.symbol.selector
        );
        if (bytes(symbol).length == 0) {
            // fallback to 6 uppercase hex of address
            return addressToSymbol(token);
        }
        return symbol;
    }

    // attempts to extract the token name. if it does not implement name, returns a name derived from the address
    function tokenName(address token) public view returns (string memory) {
        string memory name = callAndParseStringReturn(
            token,
            IERC20Metadata.name.selector
        );
        if (bytes(name).length == 0) {
            // fallback to full hex of address
            return addressToName(token);
        }
        return name;
    }

    /// @notice Provides a safe ERC20.decimals version which returns '0' as fallback value.
    /// @param token The address of the ERC-20 token contract.
    /// @return (uint8) Token decimals.
    function tokenDecimals(address token) public view returns (uint8) {
        (bool success, bytes memory data) = token.staticcall(
            abi.encodeWithSelector(IERC20Metadata.decimals.selector)
        );
        return success && data.length == 32 ? abi.decode(data, (uint8)) : 0;
    }
}

// File: src/utils/MinimalTransferHelper.sol



// SPDX-License-Identifier: MIT
// Taken from https://github.com/Uniswap/uniswap-lib/blob/master/contracts/libraries/TransferHelper.sol

pragma solidity >=0.6.0;

import "../interfaces/IERC20.sol";
import "./RevertMsgExtractor.sol";


// helper methods for transferring ERC20 tokens that do not consistently return true/false
library MinimalTransferHelper {
    /// @notice Transfers tokens from msg.sender to a recipient
    /// @dev Errors with the base revert message if transfer fails
    /// @param token The contract address of the token which will be transferred
    /// @param to The recipient of the transfer
    /// @param value The value of the transfer
    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
        if (!(success && (data.length == 0 || abi.decode(data, (bool))))) revert(RevertMsgExtractor.getRevertMsg(data));
    }
}
// File: src/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the number of decimals the token uses
     */
    function decimals() external view returns (uint8);

    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

// File: src/erc/ERC20Permit.sol



// SPDX-License-Identifier: MIT
// AdaFYTokened from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol
pragma solidity ^0.8.0;

import './ERC20.sol';
import '../interfaces/IERC2612.sol';

/**
 * @dev Extension of {ERC20} that allows token holders to use their tokens
 * without sending any transactions by setting {IERC20-allowance} with a
 * signature using the {permit} method, and then spend them via
 * {IERC20-transferFrom}.
 *
 * The {permit} signature mechanism conforms to the {IERC2612} interface.
 */
abstract contract ERC20Permit is ERC20, IERC2612 {
    mapping(address => uint256) public override nonces;

    bytes32 public immutable PERMIT_TYPEHASH =
        keccak256(
            'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'
        );
    bytes32 private immutable _DOMAIN_SEPARATOR;
    uint256 public immutable deploymentChainId;

    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_
    ) ERC20(name_, symbol_, decimals_) {
        deploymentChainId = block.chainid;
        _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
    }

    /// @dev Calculate the DOMAIN_SEPARATOR.
    function _calculateDomainSeparator(
        uint256 chainId
    ) private view returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
                    ),
                    keccak256(bytes(name)),
                    keccak256(bytes(version())),
                    chainId,
                    address(this)
                )
            );
    }

    /// @dev Return the DOMAIN_SEPARATOR.
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
        return
            block.chainid == deploymentChainId
                ? _DOMAIN_SEPARATOR
                : _calculateDomainSeparator(block.chainid);
    }

    /// @dev Setting the version as a function so that it can be overriden
    function version() public pure virtual returns (string memory) {
        return '1';
    }

    /**
     * @dev See {IERC2612-permit}.
     *
     * In cases where the free oFYTokenion is not a concern, deadline can simply be
     * set to uint(-1), so it should be seen as an oFYTokenional parameter
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual override {
        require(deadline >= block.timestamp, 'ERC20Permit: expired deadline');

        bytes32 hashStruct = keccak256(
            abi.encode(
                PERMIT_TYPEHASH,
                owner,
                spender,
                amount,
                nonces[owner]++,
                deadline
            )
        );

        bytes32 hash = keccak256(
            abi.encodePacked(
                '\x19\x01',
                block.chainid == deploymentChainId
                    ? _DOMAIN_SEPARATOR
                    : _calculateDomainSeparator(block.chainid),
                hashStruct
            )
        );

        address signer = ecrecover(hash, v, r, s);
        require(
            signer != address(0) && signer == owner,
            'ERC20Permit: invalid signature'
        );

        _setAllowance(owner, spender, amount);
    }
}

// File: src/interfaces/IFYToken.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import './IERC20.sol';
import './IERC5095.sol';
import './IJoin.sol';

interface IFYToken is IERC20, IERC5095 {
    /// @dev Asset that is returned on redemption.
    function base() external view returns (address);

    /// @dev Source of redemption funds.
    function join() external view returns (IJoin);

    /// @dev Unix time at which redemption of FYToken for base are possible
    function maturity() external view returns (uint256);

    /// @dev Record price data at maturity
    function mature() external;

    /// @dev Mint FYToken providing an equal amount of base to the protocol
    function mintWithbase(address to, uint256 amount) external;

    /// @dev Burn FYToken after maturity for an amount of base.
    function redeem(address to, uint256 amount) external returns (uint256);

    /// @dev Mint FYToken.
    /// This function can only be called by other Yield contracts, not users directly.
    /// @param to Wallet to mint the FYToken in.
    /// @param FYTokenAmount Amount of FYToken to mint.
    function mint(address to, uint256 FYTokenAmount) external;

    /// @dev Burn FYToken.
    /// This function can only be called by other Yield contracts, not users directly.
    /// @param from Wallet to burn the FYToken from.
    /// @param FYTokenAmount Amount of FYToken to burn.
    function burn(address from, uint256 FYTokenAmount) external;
}

// File: src/interfaces/IPool.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20} from './IERC20.sol';
import {IERC2612} from './IERC2612.sol';
import {IFYToken} from './IFYToken.sol';
import {IMaturingToken} from './IMaturingToken.sol';
import {IERC20Metadata} from '../erc/ERC20.sol';
import {IERC20Like} from './IERC20Like.sol';

interface IPool is IERC20, IERC2612 {
    function baseToken() external view returns (IERC20Like);

    function base() external view returns (IERC20);

    function burn(
        address baseTo,
        address fyTokenTo,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256, uint256);

    function burnForBase(
        address to,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256);

    function buyBase(
        address to,
        uint128 baseOut,
        uint128 max
    ) external returns (uint128);

    function buyBasePreview(uint128 baseOut) external view returns (uint128);

    function buyFYToken(
        address to,
        uint128 fyTokenOut,
        uint128 max
    ) external returns (uint128);

    function buyFYTokenPreview(
        uint128 fyTokenOut
    ) external view returns (uint128);

    function currentCumulativeRatio()
        external
        view
        returns (
            uint256 currentCumulativeRatio_,
            uint256 blockTimestampCurrent
        );

    function cumulativeRatioLast() external view returns (uint256);

    function fyToken() external view returns (IMaturingToken);

    function g1() external view returns (int128);

    function g2() external view returns (int128);

    function getC() external view returns (int128);

    function getCurrentSharePrice() external view returns (uint256);

    function getCache()
        external
        view
        returns (
            uint104 baseCached,
            uint104 fyTokenCached,
            uint32 blockTimestampLast,
            uint16 g1Fee_
        );

    function getBaseBalance() external view returns (uint128);

    function getFYTokenBalance() external view returns (uint128);

    function getSharesBalance() external view returns (uint128);

    function init(address to) external returns (uint256, uint256, uint256);

    function maturity() external view returns (uint32);

    function mint(
        address to,
        address remainder,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256, uint256);

    function mu() external view returns (int128);

    function mintWithBase(
        address to,
        address remainder,
        uint256 fyTokenToBuy,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256, uint256);

    function retrieveBase(address to) external returns (uint128 retrieved);

    function retrieveFYToken(address to) external returns (uint128 retrieved);

    function retrieveShares(address to) external returns (uint128 retrieved);

    function scaleFactor() external view returns (uint96);

    function sellBase(address to, uint128 min) external returns (uint128);

    function sellBasePreview(uint128 baseIn) external view returns (uint128);

    function sellFYToken(address to, uint128 min) external returns (uint128);

    function sellFYTokenPreview(
        uint128 fyTokenIn
    ) external view returns (uint128);

    function setFees(uint16 g1Fee_) external;

    function sharesToken() external view returns (IERC20Like);

    function ts() external view returns (int128);

    function wrap(address receiver) external returns (uint256 shares);

    function wrapPreview(uint256 assets) external view returns (uint256 shares);

    function unwrap(address receiver) external returns (uint256 assets);

    function unwrapPreview(
        uint256 shares
    ) external view returns (uint256 assets);

    /// Returns the max amount of FYTokens that can be sold to the pool
    function maxFYTokenIn() external view returns (uint128);

    /// Returns the max amount of FYTokens that can be bought from the pool
    function maxFYTokenOut() external view returns (uint128);

    /// Returns the max amount of Base that can be sold to the pool
    function maxBaseIn() external view returns (uint128);

    /// Returns the max amount of Base that can be bought from the pool
    function maxBaseOut() external view returns (uint128);

    /// Returns the result of the total supply invariant function
    function invariant() external view returns (uint128);

    /// Sets the pool's admin
    function setAdmin(address) external;

    /// Returns the admin
    function admin() external view returns (address);

    /// Sets the pool's strategy
    function setStrategy(address) external;

    /// Returns the pool's strategy
    function strategy() external view returns (address);

    /// Pauses the pool
    function pause(bool) external;

    /// Returns whether the pool is paused or not
    function paused() external view returns (bool);
}

// File: src/interfaces/ILender.sol



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

interface ILender {
    function batch(
        bytes[] calldata c
    ) external payable returns (bytes[] memory results);

    function approve(address, uint256, address) external returns (bool);
}

// File: src/interfaces/IERC20Metadata.sol



// SPDX-License-Identifier: MIT
// Taken from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol

pragma solidity ^0.8.0;

import './IERC20.sol';

/**
 * @dev Interface for the oFYTokenional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// File: src/utils/AddressStringUtil.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.5.0;

library AddressStringUtil {
    // converts an address to the uppercase hex string, extracting only len bytes (up to 20, multiple of 2)
    function toAsciiString(
        address addr,
        uint256 len
    ) internal pure returns (string memory) {
        require(
            len % 2 == 0 && len > 0 && len <= 40,
            'AddressStringUtil: INVALID_LEN'
        );
        bytes memory s = new bytes(len);
        uint256 addrNum = uint256(uint160(addr));
        for (uint256 ii = 0; ii < len; ii += 2) {
            uint8 b = uint8(addrNum >> (4 * (38 - ii)));
            s[ii] = char(b >> 4);
            s[ii + 1] = char(b & 0x0f);
        }
        return string(s);
    }

    // hi and lo are only 4 bits and between 0 and 16
    // this method converts those values to the unicode/ascii code point for the hex representation
    // uses upper case for the characters
    function char(uint8 b) private pure returns (bytes1 c) {
        if (b < 10) {
            return bytes1(b + 0x30);
        } else {
            return bytes1(b + 0x37);
        }
    }
}

// File: src/utils/RevertMsgExtractor.sol



// SPDX-License-Identifier: MIT
// Taken from https://github.com/sushiswap/BoringSolidity/blob/441e51c0544cf2451e6116fe00515e71d7c42e2c/contracts/BoringBatchable.sol

pragma solidity >=0.6.0;


library RevertMsgExtractor {
    /// @dev Helper function to extract a useful revert message from a failed call.
    /// If the returned data is malformed or not correctly abi encoded then this call can fail itself.
    function getRevertMsg(bytes memory returnData)
        internal pure
        returns (string memory)
    {
        // If the _res length is less than 68, then the transaction failed silently (without a revert message)
        if (returnData.length < 68) return "Transaction reverted silently";

        assembly {
            // Slice the sighash.
            returnData := add(returnData, 0x04)
        }
        return abi.decode(returnData, (string)); // All that remains is the revert string
    }
}
// File: src/erc/ERC20.sol



// SPDX-License-Identifier: MIT
// Inspired on token.sol from DappHub. Natspec adpated from OpenZeppelin.

pragma solidity ^0.8.0;
import '../interfaces/IERC20Metadata.sol';

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Calls to {transferFrom} do not check for allowance if the caller is the owner
 * of the funds. This allows to reduce the number of approvals that are necessary.
 *
 * Finally, {transferFrom} does not decrease the allowance if it is set to
 * type(uint256).max. This reduces the gas costs without any likely impact.
 */
contract ERC20 is IERC20Metadata {
    uint256 internal _totalSupply;
    mapping(address => uint256) internal _balanceOf;
    mapping(address => mapping(address => uint256)) internal _allowance;
    string public override name = '???';
    string public override symbol = '???';
    uint8 public override decimals = 18;

    /**
     *  @dev Sets the values for {name}, {symbol} and {decimals}.
     */
    constructor(string memory name_, string memory symbol_, uint8 decimals_) {
        name = name_;
        symbol = symbol_;
        decimals = decimals_;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(
        address guy
    ) external view virtual override returns (uint256) {
        return _balanceOf[guy];
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(
        address owner,
        address spender
    ) external view virtual override returns (uint256) {
        return _allowance[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     */
    function approve(
        address spender,
        uint wad
    ) external virtual override returns (bool) {
        return _setAllowance(msg.sender, spender, wad);
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - the caller must have a balance of at least `wad`.
     */
    function transfer(
        address dst,
        uint wad
    ) external virtual override returns (bool) {
        return _transfer(msg.sender, dst, wad);
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `src` must have a balance of at least `wad`.
     * - the caller is not `src`, it must have allowance for ``src``'s tokens of at least
     * `wad`.
     */
    /// if_succeeds {:msg "TransferFrom - decrease allowance"} msg.sender != src ==> old(_allowance[src][msg.sender]) >= wad;
    function transferFrom(
        address src,
        address dst,
        uint wad
    ) external virtual override returns (bool) {
        _decreaseAllowance(src, wad);

        return _transfer(src, dst, wad);
    }

    /**
     * @dev Moves tokens `wad` from `src` to `dst`.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `src` must have a balance of at least `amount`.
     */
    /// if_succeeds {:msg "Transfer - src decrease"} old(_balanceOf[src]) >= _balanceOf[src];
    /// if_succeeds {:msg "Transfer - dst increase"} _balanceOf[dst] >= old(_balanceOf[dst]);
    /// if_succeeds {:msg "Transfer - supply"} old(_balanceOf[src]) + old(_balanceOf[dst]) == _balanceOf[src] + _balanceOf[dst];
    function _transfer(
        address src,
        address dst,
        uint wad
    ) internal virtual returns (bool) {
        require(_balanceOf[src] >= wad, 'ERC20: Insufficient balance');
        unchecked {
            _balanceOf[src] = _balanceOf[src] - wad;
        }
        _balanceOf[dst] = _balanceOf[dst] + wad;

        emit Transfer(src, dst, wad);

        return true;
    }

    /**
     * @dev Sets the allowance granted to `spender` by `owner`.
     *
     * Emits an {Approval} event indicating the updated allowance.
     */
    function _setAllowance(
        address owner,
        address spender,
        uint wad
    ) internal virtual returns (bool) {
        _allowance[owner][spender] = wad;
        emit Approval(owner, spender, wad);

        return true;
    }

    /**
     * @dev Decreases the allowance granted to the caller by `src`, unless src == msg.sender or _allowance[src][msg.sender] == MAX
     *
     * Emits an {Approval} event indicating the updated allowance, if the allowance is updated.
     *
     * Requirements:
     *
     * - `spender` must have allowance for the caller of at least
     * `wad`, unless src == msg.sender
     */
    /// if_succeeds {:msg "Decrease allowance - underflow"} old(_allowance[src][msg.sender]) <= _allowance[src][msg.sender];
    function _decreaseAllowance(
        address src,
        uint wad
    ) internal virtual returns (bool) {
        if (src != msg.sender) {
            uint256 allowed = _allowance[src][msg.sender];
            if (allowed != type(uint).max) {
                require(allowed >= wad, 'ERC20: Insufficient approval');
                unchecked {
                    _setAllowance(src, msg.sender, allowed - wad);
                }
            }
        }

        return true;
    }

    /** @dev Creates `wad` tokens and assigns them to `dst`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     */
    /// if_succeeds {:msg "Mint - balance overflow"} old(_balanceOf[dst]) >= _balanceOf[dst];
    /// if_succeeds {:msg "Mint - supply overflow"} old(_totalSupply) >= _totalSupply;
    function _mint(address dst, uint wad) internal virtual returns (bool) {
        _balanceOf[dst] = _balanceOf[dst] + wad;
        _totalSupply = _totalSupply + wad;
        emit Transfer(address(0), dst, wad);

        return true;
    }

    /**
     * @dev Destroys `wad` tokens from `src`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `src` must have at least `wad` tokens.
     */
    /// if_succeeds {:msg "Burn - balance underflow"} old(_balanceOf[src]) <= _balanceOf[src];
    /// if_succeeds {:msg "Burn - supply underflow"} old(_totalSupply) <= _totalSupply;
    function _burn(address src, uint wad) internal virtual returns (bool) {
        unchecked {
            require(_balanceOf[src] >= wad, 'ERC20: Insufficient balance');
            _balanceOf[src] = _balanceOf[src] - wad;
            _totalSupply = _totalSupply - wad;
            emit Transfer(src, address(0), wad);
        }

        return true;
    }
}

// File: src/interfaces/IERC2612.sol



// SPDX-License-Identifier: MIT
// Code adaFYTokened from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/
pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC2612 standard as defined in the EIP.
 *
 * Adds the {permit} method, which can be used to change one's
 * {IERC20-allowance} without having to send a transaction, by signing a
 * message. This allows users to spend tokens without having to hold Ether.
 *
 * See https://eips.ethereum.org/EIPS/eip-2612.
 */
interface IERC2612 {
    /**
     * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,
     * given `owner`'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current ERC2612 nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);
}

// File: src/interfaces/IERC5095.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC2612.sol';

interface IERC5095 is IERC2612 {
    function maturity() external view returns (uint256);

    function underlying() external view returns (address);

    function convertToUnderlying(uint256) external view returns (uint256);

    function convertToShares(uint256) external view returns (uint256);

    function maxRedeem(address) external view returns (uint256);

    function previewRedeem(uint256) external view returns (uint256);

    function maxWithdraw(address) external view returns (uint256);

    function previewWithdraw(uint256) external view returns (uint256);

    function previewDeposit(uint256) external view returns (uint256);

    function withdraw(uint256, address, address) external returns (uint256);

    function redeem(uint256, address, address) external returns (uint256);

    function deposit(uint256, address) external returns (uint256);

    function mint(uint256, address) external returns (uint256);

    function authMint(address, uint256) external returns (bool);

    function authBurn(address, uint256) external returns (bool);

    function authApprove(address, address, uint256) external returns (bool);
}

// File: src/interfaces/IJoin.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import './IERC20.sol';

interface IJoin {
    /// @dev asset managed by this contract
    function asset() external view returns (address);

    /// @dev Add tokens to this contract.
    function join(address user, uint128 wad) external returns (uint128);

    /// @dev Remove tokens to this contract.
    function exit(address user, uint128 wad) external returns (uint128);
}

// File: src/interfaces/IMaturingToken.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import './IERC20.sol';

interface IMaturingToken is IERC20 {
    function maturity() external view returns (uint256);
}

// File: src/interfaces/IERC20Like.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import './IERC20Metadata.sol';
import './IERC20.sol';

interface IERC20Like is IERC20, IERC20Metadata {
    function mint(address receiver, uint256 shares) external;
}