// File: src/roller/StrategyRouter.sol



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

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

/// @dev The StrategyRouter contract allows users to interact with YieldSpace
/// pools and their respective strategies through a single transaction. By
/// approving the router, a user could send a base asset to a pool and receive
/// Strategy shares in return. Conversely, a user could also use the router to
/// move from Strategy shares to fyTokens and base assets from the YieldSpace
/// pool as well.
contract StrategyRouter {
    /// @dev Emitted when a user mints Strategy tokens
    event Mint(
        address indexed strategy,
        address indexed pool,
        uint256 baseIn,
        uint256 fyTokenIn,
        uint256 strategyMinted,
        address to
    );

    /// @dev Emitted when a user burns Strategy tokens for iPTs and underlying
    event Burn(
        address indexed strategy,
        address indexed pool,
        uint256 baseOut,
        uint256 fyTokenOut,
        uint256 strategyBurned,
        address to
    );

    /// @notice Mints shares to the user in exchange for base
    /// @param strategy_ The strategy being used to mint
    /// @param assets_ Number of base tokens to use to mint shares
    /// @param pts_ Number of PTs to use to mint shares
    /// @param minRatio_ Minimum ratio used in pool's mint call
    /// @param maxRatio_ Maximum ratio used in pool's mint call
    /// @return minted_ Amount of shares received
    function mint(
        address strategy_,
        uint256 assets_,
        uint256 pts_,
        uint256 minRatio_,
        uint256 maxRatio_
    ) external returns (uint256 minted_) {
        // Acquire LP tokens from the pool
        IPool pool = IStrategy(strategy_).pool();
        IERC20(pool.base()).transferFrom(msg.sender, address(pool), assets_);
        IERC20(pool.fyToken()).transferFrom(msg.sender, address(pool), pts_);
        pool.mint(strategy_, msg.sender, minRatio_, maxRatio_);

        // Mint shares from the strategy
        minted_ = IStrategy(strategy_).mint(msg.sender);

        emit Mint(strategy_, address(pool), assets_, pts_, minted_, msg.sender);
    }

    function mintWithUnderlying(
        address strategy_,
        uint256 assets_,
        uint256 ptsToBuy_,
        uint256 minRatio_,
        uint256 maxRatio_
    ) external returns (uint256 minted_) {
        // Acquire LP tokens from the pool
        IPool pool = IStrategy(strategy_).pool();
        IERC20(pool.base()).transferFrom(msg.sender, address(pool), assets_);

        pool.mintWithBase(
            strategy_,
            msg.sender,
            ptsToBuy_,
            minRatio_,
            maxRatio_
        );

        // Mint shares from the strategy
        minted_ = IStrategy(strategy_).mint(msg.sender);

        emit Mint(strategy_, address(pool), assets_, 0, minted_, msg.sender);
    }

    /// @notice Burns shares from the user and returns base
    /// @param strategy_ The strategy being used to burn
    /// @param shares_ Amount of Strategy shares to burn
    /// @param minRatio_ Minimum ratio used in pool's mint call
    /// @param maxRatio_ Maximum ratio used in pool's mint call
    /// @return baseReceived_ Amount of base tokens returned to the user
    /// @return iptReceived_ Amount of iPTs returned to the user
    function burn(
        address strategy_,
        uint256 shares_,
        uint256 minRatio_,
        uint256 maxRatio_
    ) external returns (uint256 baseReceived_, uint256 iptReceived_) {
        // Get the active pool for the strategy
        address pool = address(IStrategy(strategy_).pool());

        // Burn the shares for LP tokens
        IERC20(strategy_).transferFrom(msg.sender, strategy_, shares_);
        IStrategy(strategy_).burn(pool);

        // Burn the LP tokens
        (, baseReceived_, iptReceived_) = IStrategy(strategy_).pool().burn(
            msg.sender,
            msg.sender,
            minRatio_,
            maxRatio_
        );

        emit Burn(
            strategy_,
            pool,
            baseReceived_,
            iptReceived_,
            shares_,
            msg.sender
        );
    }

    /// @notice Burns shares from the user and returns base and iPTs
    /// @param strategy_ The strategy being used to burn
    /// @param shares_ Amount of Strategy shares to burn
    /// @param minRatio_ Minimum ratio used in pool's mint call
    /// @param maxRatio_ Maximum ratio used in pool's mint call
    /// @return baseReceived_ Amount of base tokens returned to the user
    function burnForUnderlying(
        address strategy_,
        uint256 shares_,
        uint256 minRatio_,
        uint256 maxRatio_
    ) external returns (uint256 baseReceived_) {
        // Get the active pool for the strategy
        address pool = address(IStrategy(strategy_).pool());

        // Burn the shares for LP tokens
        IERC20(strategy_).transferFrom(msg.sender, strategy_, shares_);
        IStrategy(strategy_).burn(pool);

        // Burn the LP tokens
        (, baseReceived_) = IStrategy(strategy_).pool().burnForBase(
            msg.sender,
            minRatio_,
            maxRatio_
        );

        emit Burn(strategy_, pool, baseReceived_, 0, shares_, msg.sender);
    }

    /// @notice Mints shares when strategy is divested
    /// @param strategy_ The strategy being used to mint
    /// @param assets_ Amount of base asset to mint with
    /// @return minted_ Amount of shares minted to caller
    function mintDivested(
        address strategy_,
        uint256 assets_
    ) external returns (uint256 minted_) {
        IStrategy(strategy_).base().transferFrom(
            msg.sender,
            strategy_,
            assets_
        );
        minted_ = IStrategy(strategy_).mintDivested(msg.sender);

        emit Mint(strategy_, address(0), assets_, 0, minted_, msg.sender);
    }

    /// @notice Burns shares for base asset when strategy is divested
    /// @param strategy_ The strategy being used to burn
    /// @param shares_ Amount of shares to burn
    /// @param received_ Amount of base tokens received
    function burnDivested(
        address strategy_,
        uint256 shares_
    ) external returns (uint256 received_) {
        IERC20(strategy_).transferFrom(msg.sender, strategy_, shares_);
        received_ = IStrategy(strategy_).burnDivested(msg.sender);

        emit Burn(strategy_, address(0), received_, 0, shares_, msg.sender);
    }
}

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

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