ETH Price: $3,267.96 (-2.59%)

Contract Diff Checker

Contract Name:
FyToken

Contract Source Code:

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

/**
 * @title Address
 * @author Paul Razvan Berg
 * @notice Collection of functions related to the address type.
 * @dev Forked from OpenZeppelin
 * https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v3.1.0/contracts/utils/Address.sol
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`.
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        /* solhint-disable-next-line no-inline-assembly */
        assembly {
            codehash := extcodehash(account)
        }
        return (codehash != accountHash && codehash != 0x0);
    }
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./AdminInterface.sol";

/**
 * @title Admin
 * @author Paul Razvan Berg
 * @notice Contract module which provides a basic access control mechanism, where there is
 * an account (an admin) that can be granted exclusive access to specific functions.
 *
 * By default, the admin account will be the one that deploys the contract. This can later
 * be changed with {transferAdmin}.
 *
 * This module is used through inheritance. It will make available the modifier `onlyAdmin`,
 * which can be applied to your functions to restrict their use to the admin.
 *
 * @dev Forked from OpenZeppelin
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/access/Ownable.sol
 */
abstract contract Admin is AdminInterface {
    /**
     * @notice Throws if called by any account other than the admin.
     */
    modifier onlyAdmin() {
        require(admin == msg.sender, "ERR_NOT_ADMIN");
        _;
    }

    /**
     * @notice Initializes the contract setting the deployer as the initial admin.
     */
    constructor() {
        address msgSender = msg.sender;
        admin = msgSender;
        emit TransferAdmin(address(0x00), msgSender);
    }

    /**
     * @notice Leaves the contract without admin, so it will not be possible to call
     * `onlyAdmin` functions anymore.
     *
     * Requirements:
     *
     * - The caller must be the administrator.
     *
     * WARNING: Doing this will leave the contract without an admin,
     * thereby removing any functionality that is only available to the admin.
     */
    function _renounceAdmin() external virtual override onlyAdmin {
        emit TransferAdmin(admin, address(0x00));
        admin = address(0x00);
    }

    /**
     * @notice Transfers the admin of the contract to a new account (`newAdmin`).
     * Can only be called by the current admin.
     * @param newAdmin The acount of the new admin.
     */
    function _transferAdmin(address newAdmin) external virtual override onlyAdmin {
        require(newAdmin != address(0x00), "ERR_SET_ADMIN_ZERO_ADDRESS");
        emit TransferAdmin(admin, newAdmin);
        admin = newAdmin;
    }
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./AdminStorage.sol";

/**
 * @title AdminInterface
 * @author Paul Razvan Berg
 */
abstract contract AdminInterface is AdminStorage {
    /**
     * NON-CONSTANT FUNCTIONS
     */
    function _renounceAdmin() external virtual;

    function _transferAdmin(address newAdmin) external virtual;

    /**
     * EVENTS
     */
    event TransferAdmin(address indexed oldAdmin, address indexed newAdmin);
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

abstract contract AdminStorage {
    /**
     * @notice The address of the administrator account or contract.
     */
    address public admin;
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

/**
 * @title AggregatorV3Interface
 * @author Hifi
 * @dev Forked from Chainlink
 * https://github.com/smartcontractkit/chainlink/blob/v0.9.9/evm-contracts/src/v0.7/interfaces/AggregatorV3Interface.sol
 */
interface AggregatorV3Interface {
    function decimals() external view returns (uint8);

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

    function version() external view returns (uint256);

    /*
     * getRoundData and latestRoundData should both raise "No data present"
     * if they do not have data to report, instead of returning unset values
     * which could be misinterpreted as actual reported values.
     */
    function getRoundData(uint80 _roundId)
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );

    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./BalanceSheetStorage.sol";

/**
 * @title BalanceSheetInterface
 * @author Hifi
 */
abstract contract BalanceSheetInterface is BalanceSheetStorage {
    /**
     * CONSTANT FUNCTIONS
     */
    function getClutchableCollateral(FyTokenInterface fyToken, uint256 repayAmount)
        external
        view
        virtual
        returns (uint256);

    function getCurrentCollateralizationRatio(FyTokenInterface fyToken, address borrower)
        public
        view
        virtual
        returns (uint256);

    function getHypotheticalCollateralizationRatio(
        FyTokenInterface fyToken,
        address borrower,
        uint256 lockedCollateral,
        uint256 debt
    ) public view virtual returns (uint256);

    function getVault(FyTokenInterface fyToken, address borrower)
        external
        view
        virtual
        returns (
            uint256,
            uint256,
            uint256,
            bool
        );

    function getVaultDebt(FyTokenInterface fyToken, address borrower) external view virtual returns (uint256);

    function getVaultLockedCollateral(FyTokenInterface fyToken, address borrower)
        external
        view
        virtual
        returns (uint256);

    function isAccountUnderwater(FyTokenInterface fyToken, address borrower) external view virtual returns (bool);

    function isVaultOpen(FyTokenInterface fyToken, address borrower) external view virtual returns (bool);

    /**
     * NON-CONSTANT FUNCTIONS
     */

    function clutchCollateral(
        FyTokenInterface fyToken,
        address liquidator,
        address borrower,
        uint256 clutchedCollateralAmount
    ) external virtual returns (bool);

    function depositCollateral(FyTokenInterface fyToken, uint256 collateralAmount) external virtual returns (bool);

    function freeCollateral(FyTokenInterface fyToken, uint256 collateralAmount) external virtual returns (bool);

    function lockCollateral(FyTokenInterface fyToken, uint256 collateralAmount) external virtual returns (bool);

    function openVault(FyTokenInterface fyToken) external virtual returns (bool);

    function setVaultDebt(
        FyTokenInterface fyToken,
        address borrower,
        uint256 newVaultDebt
    ) external virtual returns (bool);

    function withdrawCollateral(FyTokenInterface fyToken, uint256 collateralAmount) external virtual returns (bool);

    /**
     * EVENTS
     */

    event ClutchCollateral(
        FyTokenInterface indexed fyToken,
        address indexed liquidator,
        address indexed borrower,
        uint256 clutchedCollateralAmount
    );

    event DepositCollateral(FyTokenInterface indexed fyToken, address indexed borrower, uint256 collateralAmount);

    event FreeCollateral(FyTokenInterface indexed fyToken, address indexed borrower, uint256 collateralAmount);

    event LockCollateral(FyTokenInterface indexed fyToken, address indexed borrower, uint256 collateralAmount);

    event OpenVault(FyTokenInterface indexed fyToken, address indexed borrower);

    event SetVaultDebt(FyTokenInterface indexed fyToken, address indexed borrower, uint256 oldDebt, uint256 newDebt);

    event WithdrawCollateral(FyTokenInterface indexed fyToken, address indexed borrower, uint256 collateralAmount);
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./FyTokenInterface.sol";

/**
 * @title BalanceSheetStorage
 * @author Hifi
 */
abstract contract BalanceSheetStorage {
    struct Vault {
        uint256 debt;
        uint256 freeCollateral;
        uint256 lockedCollateral;
        bool isOpen;
    }

    /**
     * @notice The unique Fintroller associated with this contract.
     */
    FintrollerInterface public fintroller;

    /**
     * @dev One vault for each fyToken for each account.
     */
    mapping(address => mapping(address => Vault)) internal vaults;

    /**
     * @notice Indicator that this is a BalanceSheet contract, for inspection.
     */
    bool public constant isBalanceSheet = true;
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

/**
 * @notice Possible error codes that can be returned.
 */
enum MathError { NO_ERROR, DIVISION_BY_ZERO, INTEGER_OVERFLOW, INTEGER_UNDERFLOW, MODULO_BY_ZERO }

/**
 * @title CarefulMath
 * @author Paul Razvan Berg
 * @notice Exponential module for storing fixed-precision decimals.
 * @dev Forked from Compound
 * https://github.com/compound-finance/compound-protocol/blob/v2.8.1/contracts/CarefulMath.sol
 */
abstract contract CarefulMath {
    /**
     * @notice Adds two numbers, returns an error on overflow.
     */
    function addUInt(uint256 a, uint256 b) internal pure returns (MathError, uint256) {
        uint256 c = a + b;

        if (c >= a) {
            return (MathError.NO_ERROR, c);
        } else {
            return (MathError.INTEGER_OVERFLOW, 0);
        }
    }

    /**
     * @notice Add `a` and `b` and then subtract `c`.
     */
    function addThenSubUInt(
        uint256 a,
        uint256 b,
        uint256 c
    ) internal pure returns (MathError, uint256) {
        (MathError err0, uint256 sum) = addUInt(a, b);

        if (err0 != MathError.NO_ERROR) {
            return (err0, 0);
        }

        return subUInt(sum, c);
    }

    /**
     * @notice Integer division of two numbers, truncating the quotient.
     */
    function divUInt(uint256 a, uint256 b) internal pure returns (MathError, uint256) {
        if (b == 0) {
            return (MathError.DIVISION_BY_ZERO, 0);
        }

        return (MathError.NO_ERROR, a / b);
    }

    /**
     * @notice Returns the remainder of dividing two numbers.
     * @dev Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     */
    function modUInt(uint256 a, uint256 b) internal pure returns (MathError, uint256) {
        if (b == 0) {
            return (MathError.MODULO_BY_ZERO, 0);
        }

        return (MathError.NO_ERROR, a % b);
    }

    /**
     * @notice Multiplies two numbers, returns an error on overflow.
     */
    function mulUInt(uint256 a, uint256 b) internal pure returns (MathError, uint256) {
        if (a == 0) {
            return (MathError.NO_ERROR, 0);
        }

        uint256 c = a * b;

        if (c / a != b) {
            return (MathError.INTEGER_OVERFLOW, 0);
        } else {
            return (MathError.NO_ERROR, c);
        }
    }

    /**
     * @notice Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend).
     */
    function subUInt(uint256 a, uint256 b) internal pure returns (MathError, uint256) {
        if (b <= a) {
            return (MathError.NO_ERROR, a - b);
        } else {
            return (MathError.INTEGER_UNDERFLOW, 0);
        }
    }
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./CarefulMath.sol";
import "./Erc20Interface.sol";

import "./ChainlinkOperatorStorage.sol";
import "./AggregatorV3Interface.sol";

/**
 * @title ChainlinkOperatorInterface
 * @author Hifi
 */
abstract contract ChainlinkOperatorInterface is ChainlinkOperatorStorage {
    /**
     * EVENTS
     */
    event DeleteFeed(Erc20Interface indexed asset, AggregatorV3Interface indexed feed);

    event SetFeed(Erc20Interface indexed asset, AggregatorV3Interface indexed feed);

    /**
     * CONSTANT FUNCTIONS.
     */
    function getAdjustedPrice(string memory symbol) external view virtual returns (uint256);

    function getFeed(string memory symbol)
        external
        view
        virtual
        returns (
            Erc20Interface,
            AggregatorV3Interface,
            bool
        );

    function getPrice(string memory symbol) public view virtual returns (uint256);

    /**
     * NON-CONSTANT FUNCTIONS.
     */
    function deleteFeed(string memory symbol) external virtual returns (bool);

    function setFeed(Erc20Interface asset, AggregatorV3Interface feed) external virtual returns (bool);
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";

import "./AggregatorV3Interface.sol";

/**
 * @title ChainlinkOperatorStorage
 * @author Hifi
 */
abstract contract ChainlinkOperatorStorage {
    struct Feed {
        Erc20Interface asset;
        AggregatorV3Interface id;
        bool isSet;
    }

    /**
     * @dev Mapping between Erc20 symbols and Feed structs.
     */
    mapping(string => Feed) internal feeds;

    /**
     * @notice Chainlink price precision for USD-quoted data.
     */
    uint256 public constant pricePrecision = 8;

    /**
     * @notice The ratio between mantissa precision (1e18) and the Chainlink price precision (1e8).
     */
    uint256 public constant pricePrecisionScalar = 1.0e10;
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";
import "./CarefulMath.sol";

/**
 * @title Erc20
 * @author Paul Razvan Berg
 * @notice Implementation of the {Erc20Interface} interface.
 *
 * 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 Erc may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {Erc20Interface-approve}.
 *
 * @dev Forked from OpenZeppelin
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.2.0/contracts/token/Erc20/Erc20.sol
 */
contract Erc20 is
    CarefulMath, /* no dependency */
    Erc20Interface /* one dependency */
{
    /**
     * @notice All three of these values are immutable: they can only be set once during construction.
     * @param name_ Erc20 name of this token.
     * @param symbol_ Erc20 symbol of this token.
     * @param decimals_ Erc20 decimal precision of this token.
     */
    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_
    ) {
        name = name_;
        symbol = symbol_;
        decimals = decimals_;
    }

    /**
     * CONSTANT FUNCTIONS
     */

    /**
     * @notice 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 virtual override returns (uint256) {
        return allowances[owner][spender];
    }

    /**
     * @notice Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return balances[account];
    }

    /**
     * NON-CONSTANT FUNCTIONS
     */

    /**
     * @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * @dev 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.
     *
     * @return a boolean value indicating whether the operation succeeded.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) external virtual override returns (bool) {
        approveInternal(msg.sender, spender, amount);
        return true;
    }

    /**
     * @notice Atomically decreases the allowance granted to `spender` by the caller.
     *
     * @dev This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {Erc20Interface-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) external virtual returns (bool) {
        MathError mathErr;
        uint256 newAllowance;
        (mathErr, newAllowance) = subUInt(allowances[msg.sender][spender], subtractedValue);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_DECREASE_ALLOWANCE_UNDERFLOW");
        approveInternal(msg.sender, spender, newAllowance);
        return true;
    }

    /**
     * @notice Atomically increases the allowance granted to `spender` by the caller.
     *
     * @dev This is an alternative to {approve} that can be used as a mitigation for
     * problems described above.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) external virtual returns (bool) {
        MathError mathErr;
        uint256 newAllowance;
        (mathErr, newAllowance) = addUInt(allowances[msg.sender][spender], addedValue);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_INCREASE_ALLOWANCE_OVERFLOW");
        approveInternal(msg.sender, spender, newAllowance);
        return true;
    }

    /**
     * @notice Moves `amount` tokens from the caller's account to `recipient`.
     *
     * @dev Emits a {Transfer} event.
     *
     * @return a boolean value indicating whether the operation succeeded.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - The caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) external virtual override returns (bool) {
        transferInternal(msg.sender, recipient, amount);
        return true;
    }

    /**
     * @notice See Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * @dev Emits a {Transfer} event. Emits an {Approval} event indicating the
     * updated allowance. This is not required by the Erc. See the note at the
     * beginning of {Erc20};
     *
     * @return a boolean value indicating whether the operation succeeded.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - The caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external virtual override returns (bool) {
        transferInternal(sender, recipient, amount);
        MathError mathErr;
        uint256 newAllowance;
        (mathErr, newAllowance) = subUInt(allowances[sender][msg.sender], amount);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_TRANSFER_FROM_INSUFFICIENT_ALLOWANCE");
        approveInternal(sender, msg.sender, newAllowance);
        return true;
    }

    /**
     * INTERNAL FUNCTIONS
     */

    /**
     * @notice Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * @dev This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function approveInternal(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0x00), "ERR_ERC20_APPROVE_FROM_ZERO_ADDRESS");
        require(spender != address(0x00), "ERR_ERC20_APPROVE_TO_ZERO_ADDRESS");

        allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @notice Destroys `burnAmount` tokens from `holder`, recuding the token supply.
     *
     * @dev Emits a {Burn} event.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `holder` must have at least `amount` tokens.
     */
    function burnInternal(address holder, uint256 burnAmount) internal {
        MathError mathErr;
        uint256 newHolderBalance;
        uint256 newTotalSupply;

        /* Burn the yTokens. */
        (mathErr, newHolderBalance) = subUInt(balances[holder], burnAmount);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_BURN_BALANCE_UNDERFLOW");
        balances[holder] = newHolderBalance;

        /* Reduce the total supply. */
        (mathErr, newTotalSupply) = subUInt(totalSupply, burnAmount);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_BURN_TOTAL_SUPPLY_UNDERFLOW");
        totalSupply = newTotalSupply;

        emit Burn(holder, burnAmount);
    }

    /** @notice Prints new tokens into existence and assigns them to `beneficiary`,
     * increasing the total supply.
     *
     * @dev Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - The beneficiary's balance and the total supply cannot overflow.
     */
    function mintInternal(address beneficiary, uint256 mintAmount) internal {
        MathError mathErr;
        uint256 newBeneficiaryBalance;
        uint256 newTotalSupply;

        /* Mint the yTokens. */
        (mathErr, newBeneficiaryBalance) = addUInt(balances[beneficiary], mintAmount);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_MINT_BALANCE_OVERFLOW");
        balances[beneficiary] = newBeneficiaryBalance;

        /* Increase the total supply. */
        (mathErr, newTotalSupply) = addUInt(totalSupply, mintAmount);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_MINT_TOTAL_SUPPLY_OVERFLOW");
        totalSupply = newTotalSupply;

        emit Mint(beneficiary, mintAmount);
    }

    /**
     * @notice Moves `amount` tokens from `sender` to `recipient`.
     *
     * @dev This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function transferInternal(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0x00), "ERR_ERC20_TRANSFER_FROM_ZERO_ADDRESS");
        require(recipient != address(0x00), "ERR_ERC20_TRANSFER_TO_ZERO_ADDRESS");

        MathError mathErr;
        uint256 newSenderBalance;
        uint256 newRecipientBalance;

        (mathErr, newSenderBalance) = subUInt(balances[sender], amount);
        require(mathErr == MathError.NO_ERROR, "ERR_ERC20_TRANSFER_SENDER_BALANCE_UNDERFLOW");
        balances[sender] = newSenderBalance;

        (mathErr, newRecipientBalance) = addUInt(balances[recipient], amount);
        assert(mathErr == MathError.NO_ERROR);
        balances[recipient] = newRecipientBalance;

        emit Transfer(sender, recipient, amount);
    }
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Erc20Storage.sol";

/**
 * @title Erc20Interface
 * @author Paul Razvan Berg
 * @notice Interface of the Erc20 standard
 * @dev Forked from OpenZeppelin
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.2.0/contracts/token/ERC20/IERC20.sol
 */
abstract contract Erc20Interface is Erc20Storage {
    /**
     * CONSTANT FUNCTIONS
     */
    function allowance(address owner, address spender) external view virtual returns (uint256);

    function balanceOf(address account) external view virtual returns (uint256);

    /**
     * NON-CONSTANT FUNCTIONS
     */
    function approve(address spender, uint256 amount) external virtual returns (bool);

    function transfer(address recipient, uint256 amount) external virtual returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external virtual returns (bool);

    /**
     * EVENTS
     */
    event Approval(address indexed owner, address indexed spender, uint256 amount);

    event Burn(address indexed holder, uint256 burnAmount);

    event Mint(address indexed beneficiary, uint256 mintAmount);

    event Transfer(address indexed from, address indexed to, uint256 amount);
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Erc20.sol";
import "./Erc20PermitInterface.sol";

/**
 * @title Erc20Permit
 * @author Paul Razvan Berg
 * @notice Extension of Erc20 that allows token holders to use their tokens
 * without sending any transactions by setting the allowance with a signature
 * using the `permit` method, and then spend them via `transferFrom`.
 * @dev See https://eips.ethereum.org/EIPS/eip-2612.
 */
contract Erc20Permit is
    Erc20PermitInterface, /* one dependency */
    Erc20 /* three dependencies */
{
    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_
    ) Erc20(name_, symbol_, decimals_) {
        uint256 chainId;
        /* solhint-disable-next-line no-inline-assembly */
        assembly {
            chainId := chainid()
        }
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(name)),
                keccak256(bytes(version)),
                chainId,
                address(this)
            )
        );
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,
     * assuming the latter's signed approval.
     *
     * IMPORTANT: The same issues Erc20 `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.
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external override {
        require(owner != address(0x00), "ERR_ERC20_PERMIT_OWNER_ZERO_ADDRESS");
        require(spender != address(0x00), "ERR_ERC20_PERMIT_SPENDER_ZERO_ADDRESS");
        require(deadline >= block.timestamp, "ERR_ERC20_PERMIT_EXPIRED");

        /* It's safe to use the "+" operator here because the nonce cannot realistically overflow, ever. */
        bytes32 hashStruct = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
        address recoveredOwner = ecrecover(digest, v, r, s);

        require(recoveredOwner != address(0x00), "ERR_ERC20_PERMIT_RECOVERED_OWNER_ZERO_ADDRESS");
        require(recoveredOwner == owner, "ERR_ERC20_PERMIT_INVALID_SIGNATURE");

        approveInternal(owner, spender, amount);
    }
}

/* SPDX-License-Identifier: MIT */
/* solhint-disable var-name-mixedcase */
pragma solidity ^0.7.0;

import "./Erc20PermitStorage.sol";

/**
 * @notice Erc20PermitInterface
 * @author Paul Razvan Berg
 */
abstract contract Erc20PermitInterface is Erc20PermitStorage {
    /**
     * NON-CONSTANT FUNCTIONS
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual;
}

/* SPDX-License-Identifier: MIT */
/* solhint-disable var-name-mixedcase */
pragma solidity ^0.7.0;

/**
 * @notice Erc20PermitStorage
 * @author Paul Razvan Berg
 */
abstract contract Erc20PermitStorage {
    /**
     * @notice The Eip712 domain's keccak256 hash.
     */
    bytes32 public DOMAIN_SEPARATOR;

    /**
     * @notice keccak256("Permit(address owner,address spender,uint256 amount,uint256 nonce,uint256 deadline)");
     */
    bytes32 public constant PERMIT_TYPEHASH = 0xfc77c2b9d30fe91687fd39abb7d16fcdfe1472d065740051ab8b13e4bf4a617f;

    /**
     * @notice Provides replay protection.
     */
    mapping(address => uint256) public nonces;

    /**
     * @notice Eip712 version of this implementation.
     */
    string public constant version = "1";
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Admin.sol";
import "./Erc20Interface.sol";
import "./Erc20RecoverInterface.sol";
import "./SafeErc20.sol";

/**
 * @title Erc20Recover
 * @author Paul Razvan Berg
 * @notice Gives the administrator the ability to recover the Erc20 tokens that
 * had been sent (accidentally, or not) to the contract.
 */
abstract contract Erc20Recover is
    Erc20RecoverInterface, /* one dependency */
    Admin /* two dependencies */
{
    using SafeErc20 for Erc20Interface;

    /**
     * @notice Sets the tokens that this contract cannot recover.
     *
     * @dev Emits a {SetNonRecoverableTokens} event.
     *
     * Requirements:
     *
     * - The caller must be the administrator.
     * - The contract must be non-initialized.
     * - The array of given tokens cannot be empty.
     *
     * @param tokens The array of tokens to set as non-recoverable.
     */
    function _setNonRecoverableTokens(Erc20Interface[] calldata tokens) external override onlyAdmin {
        /* Checks */
        require(isRecoverInitialized == false, "ERR_INITALIZED");

        /* Iterate over the token list, sanity check each and update the mapping. */
        uint256 length = tokens.length;
        for (uint256 i = 0; i < length; i += 1) {
            tokens[i].symbol();
            nonRecoverableTokens.push(tokens[i]);
        }

        /* Effects: prevent this function from ever being called again. */
        isRecoverInitialized = true;

        emit SetNonRecoverableTokens(admin, tokens);
    }

    /**
     * @notice Recover Erc20 tokens sent to this contract (by accident or otherwise).
     * @dev Emits a {RecoverToken} event.
     *
     * Requirements:
     *
     * - The caller must be the administrator.
     * - The contract must be initialized.
     * - The amount to recover cannot be zero.
     * - The token to recover cannot be among the non-recoverable tokens.
     *
     * @param token The token to make the recover for.
     * @param recoverAmount The uint256 amount to recover, specified in the token's decimal system.
     */
    function _recover(Erc20Interface token, uint256 recoverAmount) external override onlyAdmin {
        /* Checks */
        require(isRecoverInitialized == true, "ERR_NOT_INITALIZED");
        require(recoverAmount > 0, "ERR_RECOVER_ZERO");

        bytes32 tokenSymbolHash = keccak256(bytes(token.symbol()));
        uint256 length = nonRecoverableTokens.length;

        /**
         * We iterate over the non-recoverable token array and check that:
         *
         *   1. The addresses of the tokens are not the same
         *   2. The symbols of the tokens are not the same
         *
         * It is true that the second check may lead to a false positive, but
         * there is no better way to fend off against proxied tokens.
         */
        for (uint256 i = 0; i < length; i += 1) {
            require(
                address(token) != address(nonRecoverableTokens[i]) &&
                    tokenSymbolHash != keccak256(bytes(nonRecoverableTokens[i].symbol())),
                "ERR_RECOVER_NON_RECOVERABLE_TOKEN"
            );
        }

        /* Interactions */
        token.safeTransfer(admin, recoverAmount);

        emit Recover(admin, token, recoverAmount);
    }
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";
import "./Erc20RecoverStorage.sol";

abstract contract Erc20RecoverInterface is Erc20RecoverStorage {
    /**
     * NON-CONSTANT FUNCTIONS
     */
    function _recover(Erc20Interface token, uint256 recoverAmount) external virtual;

    function _setNonRecoverableTokens(Erc20Interface[] calldata tokens) external virtual;

    /**
     * EVENTS
     */
    event Recover(address indexed admin, Erc20Interface token, uint256 recoverAmount);
    event SetNonRecoverableTokens(address indexed admin, Erc20Interface[] nonRecoverableTokens);
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";

abstract contract Erc20RecoverStorage {
    /**
     * @notice The tokens that can be recovered cannot be in this mapping.
     */
    Erc20Interface[] public nonRecoverableTokens;

    /**
     * @dev A flag that signals whether the the non-recoverable tokens were set or not.
     */
    bool internal isRecoverInitialized;
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

/**
 * @title ExponentialStorage
 * @author Paul Razvan Berg
 * @notice The storage interface ancillary to an Erc20 contract.
 */
abstract contract Erc20Storage {
    /**
     * @notice Returns the number of decimals used to get its user representation.
     */
    uint8 public decimals;

    /**
     * @notice Returns the name of the token.
     */
    string public name;

    /**
     * @notice Returns the symbol of the token, usually a shorter version of
     * the name.
     */
    string public symbol;

    /**
     * @notice Returns the amount of tokens in existence.
     */
    uint256 public totalSupply;

    mapping(address => mapping(address => uint256)) internal allowances;

    mapping(address => uint256) internal balances;
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./CarefulMath.sol";
import "./ExponentialStorage.sol";

/**
 * @title Exponential module for storing fixed-precision decimals.
 * @author Paul Razvan Berg
 * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places.
 * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is: `Exp({mantissa: 5100000000000000000})`.
 * @dev Forked from Compound
 * https://github.com/compound-finance/compound-protocol/blob/v2.6/contracts/Exponential.sol
 */
abstract contract Exponential is
    CarefulMath, /* no dependency */
    ExponentialStorage /* no dependency */
{
    /**
     * @dev Adds two exponentials, returning a new exponential.
     */
    function addExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {
        (MathError error, uint256 result) = addUInt(a.mantissa, b.mantissa);

        return (error, Exp({ mantissa: result }));
    }

    /**
     * @dev Divides two exponentials, returning a new exponential.
     * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b.
     * NOTE: Returns an error if (`num` * 10e18) > MAX_INT, or if `denom` is zero.
     */
    function divExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {
        (MathError err0, uint256 scaledNumerator) = mulUInt(a.mantissa, expScale);
        if (err0 != MathError.NO_ERROR) {
            return (err0, Exp({ mantissa: 0 }));
        }

        (MathError err1, uint256 rational) = divUInt(scaledNumerator, b.mantissa);
        if (err1 != MathError.NO_ERROR) {
            return (err1, Exp({ mantissa: 0 }));
        }

        return (MathError.NO_ERROR, Exp({ mantissa: rational }));
    }

    /**
     * @dev Multiplies two exponentials, returning a new exponential.
     */
    function mulExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {
        (MathError err0, uint256 doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa);
        if (err0 != MathError.NO_ERROR) {
            return (err0, Exp({ mantissa: 0 }));
        }

        /*
         * We add half the scale before dividing so that we get rounding instead of truncation.
         * See "Listing 6" and text above it at https://accu.org/index.php/journals/1717
         * Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18.
         */
        (MathError err1, uint256 doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct);
        if (err1 != MathError.NO_ERROR) {
            return (err1, Exp({ mantissa: 0 }));
        }

        (MathError err2, uint256 product) = divUInt(doubleScaledProductWithHalfScale, expScale);
        /* The only possible error `div` is MathError.DIVISION_BY_ZERO but we control `expScale` and it's not zero. */
        assert(err2 == MathError.NO_ERROR);

        return (MathError.NO_ERROR, Exp({ mantissa: product }));
    }

    /**
     * @dev Multiplies three exponentials, returning a new exponential.
     */
    function mulExp3(
        Exp memory a,
        Exp memory b,
        Exp memory c
    ) internal pure returns (MathError, Exp memory) {
        (MathError err, Exp memory ab) = mulExp(a, b);
        if (err != MathError.NO_ERROR) {
            return (err, ab);
        }
        return mulExp(ab, c);
    }

    /**
     * @dev Subtracts two exponentials, returning a new exponential.
     */
    function subExp(Exp memory a, Exp memory b) internal pure returns (MathError, Exp memory) {
        (MathError error, uint256 result) = subUInt(a.mantissa, b.mantissa);

        return (error, Exp({ mantissa: result }));
    }
}

/* SPDX-License-Identifier: LPGL-3.0-or-later */
pragma solidity ^0.7.0;

/**
 * @title ExponentialStorage
 * @author Paul Razvan Berg
 * @notice The storage interface ancillary to an Exponential contract.
 */
abstract contract ExponentialStorage {
    struct Exp {
        uint256 mantissa;
    }

    /**
     * @dev In Exponential denomination, 1e18 is 1.
     */
    uint256 internal constant expScale = 1e18;
    uint256 internal constant halfExpScale = expScale / 2;
    uint256 internal constant mantissaOne = expScale;
}

/* SPDX-License-Identifier: LPGL-3.0-or-later */
pragma solidity ^0.7.0;

import "./FintrollerStorage.sol";
import "./FyTokenInterface.sol";
import "./ChainlinkOperatorInterface.sol";

abstract contract FintrollerInterface is FintrollerStorage {
    /**
     * CONSTANT FUNCTIONS
     */

    function getBond(FyTokenInterface fyToken)
        external
        view
        virtual
        returns (
            uint256 debtCeiling,
            uint256 collateralizationRatioMantissa,
            bool isBorrowAllowed,
            bool isDepositCollateralAllowed,
            bool isLiquidateBorrowAllowed,
            bool isListed,
            bool isRedeemFyTokenAllowed,
            bool isRepayBorrowAllowed,
            bool isSupplyUnderlyingAllowed
        );

    function getBorrowAllowed(FyTokenInterface fyToken) external view virtual returns (bool);

    function getBondCollateralizationRatio(FyTokenInterface fyToken) external view virtual returns (uint256);

    function getBondDebtCeiling(FyTokenInterface fyToken) external view virtual returns (uint256);

    function getDepositCollateralAllowed(FyTokenInterface fyToken) external view virtual returns (bool);

    function getLiquidateBorrowAllowed(FyTokenInterface fyToken) external view virtual returns (bool);

    function getRedeemFyTokensAllowed(FyTokenInterface fyToken) external view virtual returns (bool);

    function getRepayBorrowAllowed(FyTokenInterface fyToken) external view virtual returns (bool);

    function getSupplyUnderlyingAllowed(FyTokenInterface fyToken) external view virtual returns (bool);

    /**
     * NON-CONSTANT FUNCTIONS
     */

    function listBond(FyTokenInterface fyToken) external virtual returns (bool);

    function setBondCollateralizationRatio(FyTokenInterface fyToken, uint256 newCollateralizationRatioMantissa)
        external
        virtual
        returns (bool);

    function setBondDebtCeiling(FyTokenInterface fyToken, uint256 newDebtCeiling) external virtual returns (bool);

    function setBorrowAllowed(FyTokenInterface fyToken, bool state) external virtual returns (bool);

    function setDepositCollateralAllowed(FyTokenInterface fyToken, bool state) external virtual returns (bool);

    function setLiquidateBorrowAllowed(FyTokenInterface fyToken, bool state) external virtual returns (bool);

    function setLiquidationIncentive(uint256 newLiquidationIncentiveMantissa) external virtual returns (bool);

    function setOracle(ChainlinkOperatorInterface newOracle) external virtual returns (bool);

    function setRedeemFyTokensAllowed(FyTokenInterface fyToken, bool state) external virtual returns (bool);

    function setRepayBorrowAllowed(FyTokenInterface fyToken, bool state) external virtual returns (bool);

    function setSupplyUnderlyingAllowed(FyTokenInterface fyToken, bool state) external virtual returns (bool);

    /**
     * EVENTS
     */
    event ListBond(address indexed admin, FyTokenInterface indexed fyToken);

    event SetBorrowAllowed(address indexed admin, FyTokenInterface indexed fyToken, bool state);

    event SetBondCollateralizationRatio(
        address indexed admin,
        FyTokenInterface indexed fyToken,
        uint256 oldCollateralizationRatio,
        uint256 newCollateralizationRatio
    );

    event SetBondDebtCeiling(
        address indexed admin,
        FyTokenInterface indexed fyToken,
        uint256 oldDebtCeiling,
        uint256 newDebtCeiling
    );

    event SetDepositCollateralAllowed(address indexed admin, FyTokenInterface indexed fyToken, bool state);

    event SetLiquidateBorrowAllowed(address indexed admin, FyTokenInterface indexed fyToken, bool state);

    event SetLiquidationIncentive(
        address indexed admin,
        uint256 oldLiquidationIncentive,
        uint256 newLiquidationIncentive
    );

    event SetRedeemFyTokensAllowed(address indexed admin, FyTokenInterface indexed fyToken, bool state);

    event SetRepayBorrowAllowed(address indexed admin, FyTokenInterface indexed fyToken, bool state);

    event SetOracle(address indexed admin, address oldOracle, address newOracle);

    event SetSupplyUnderlyingAllowed(address indexed admin, FyTokenInterface indexed fyToken, bool state);
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./Exponential.sol";

import "./FyTokenInterface.sol";
import "./ChainlinkOperatorInterface.sol";

/**
 * @title FintrollerStorage
 * @author Hifi
 */
abstract contract FintrollerStorage is Exponential {
    struct Bond {
        Exp collateralizationRatio;
        uint256 debtCeiling;
        bool isBorrowAllowed;
        bool isDepositCollateralAllowed;
        bool isLiquidateBorrowAllowed;
        bool isListed;
        bool isRedeemFyTokenAllowed;
        bool isRepayBorrowAllowed;
        bool isSupplyUnderlyingAllowed;
    }

    /**
     * @dev Maps the fyToken address to the Bond structs.
     */
    mapping(FyTokenInterface => Bond) internal bonds;

    /**
     * @notice The contract that provides price data for the collateral and the underlying asset.
     */
    ChainlinkOperatorInterface public oracle;

    /**
     * @notice Multiplier representing the discount on collateral that a liquidator receives.
     */
    uint256 public liquidationIncentiveMantissa;

    /**
     * @dev The threshold below which the collateralization ratio cannot be set, equivalent to 100%.
     */
    uint256 internal constant collateralizationRatioLowerBoundMantissa = 1.0e18;

    /**
     * @dev The threshold above which the collateralization ratio cannot be set, equivalent to 10,000%.
     */
    uint256 internal constant collateralizationRatioUpperBoundMantissa = 1.0e20;

    /**
     * @dev The dafault collateralization ratio set when a new bond is listed, equivalent to 150%.
     */
    uint256 internal constant defaultCollateralizationRatioMantissa = 1.5e18;

    /**
     * @dev The threshold below which the liquidation incentive cannot be set, equivalent to 100%.
     */
    uint256 internal constant liquidationIncentiveLowerBoundMantissa = 1.0e18;

    /**
     * @dev The threshold above which the liquidation incentive cannot be set, equivalent to 150%.
     */
    uint256 internal constant liquidationIncentiveUpperBoundMantissa = 1.5e18;

    /**
     * @notice Indicator that this is a Fintroller contract, for inspection.
     */
    bool public constant isFintroller = true;
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./Admin.sol";
import "./CarefulMath.sol";
import "./Erc20.sol";
import "./Erc20Interface.sol";
import "./Erc20Permit.sol";
import "./Erc20Recover.sol";
import "./ReentrancyGuard.sol";

import "./BalanceSheetInterface.sol";
import "./FintrollerInterface.sol";
import "./FyTokenInterface.sol";
import "./RedemptionPool.sol";

/**
 * @title FyToken
 * @author Hifi
 * @notice Zero-coupon bond that tracks an Erc20 underlying asset.
 */
contract FyToken is
    ReentrancyGuard, /* no depedency */
    FyTokenInterface, /* one dependency */
    Admin, /* two dependencies */
    Exponential, /* two dependencies */
    Erc20, /* three dependencies */
    Erc20Permit, /* five dependencies */
    Erc20Recover /* five dependencies */
{
    modifier isVaultOpen(address account) {
        require(balanceSheet.isVaultOpen(this, account), "ERR_VAULT_NOT_OPEN");
        _;
    }

    /**
     * @notice The fyToken always has 18 decimals.
     * @dev Instantiates the Redemption Pool.
     * @param name_ Erc20 name of this token.
     * @param symbol_ Erc20 symbol of this token.
     * @param expirationTime_ Unix timestamp in seconds for when this token expires.
     * @param fintroller_ The address of the Fintroller contract.
     * @param balanceSheet_ The address of the BalanceSheet contract.
     * @param underlying_ The contract address of the underlying asset.
     * @param collateral_ The contract address of the collateral asset.
     */
    constructor(
        string memory name_,
        string memory symbol_,
        uint256 expirationTime_,
        FintrollerInterface fintroller_,
        BalanceSheetInterface balanceSheet_,
        Erc20Interface underlying_,
        Erc20Interface collateral_
    ) Erc20Permit(name_, symbol_, 18) Admin() {
        uint8 defaultNumberOfDecimals = 18;

        /* Set the underlying contract and calculate the decimal scalar offsets. */
        uint256 underlyingDecimals = underlying_.decimals();
        require(underlyingDecimals > 0, "ERR_FYTOKEN_CONSTRUCTOR_UNDERLYING_DECIMALS_ZERO");
        require(underlyingDecimals <= defaultNumberOfDecimals, "ERR_FYTOKEN_CONSTRUCTOR_UNDERLYING_DECIMALS_OVERFLOW");
        underlyingPrecisionScalar = 10**(defaultNumberOfDecimals - underlyingDecimals);
        underlying = underlying_;

        /* Set the collateral contract and calculate the decimal scalar offsets. */
        uint256 collateralDecimals = collateral_.decimals();
        require(collateralDecimals > 0, "ERR_FYTOKEN_CONSTRUCTOR_COLLATERAL_DECIMALS_ZERO");
        require(defaultNumberOfDecimals >= collateralDecimals, "ERR_FYTOKEN_CONSTRUCTOR_COLLATERAL_DECIMALS_OVERFLOW");
        collateralPrecisionScalar = 10**(defaultNumberOfDecimals - collateralDecimals);
        collateral = collateral_;

        /* Set the unix expiration time. */
        require(expirationTime_ > block.timestamp, "ERR_FYTOKEN_CONSTRUCTOR_EXPIRATION_TIME_NOT_VALID");
        expirationTime = expirationTime_;

        /* Set the Fintroller contract and sanity check it. */
        fintroller = fintroller_;
        fintroller.isFintroller();

        /* Set the Balance Sheet contract and sanity check it. */
        balanceSheet = balanceSheet_;
        balanceSheet.isBalanceSheet();

        /* Create the Redemption Pool contract and transfer the owner from the fyToken itself to the current caller. */
        redemptionPool = new RedemptionPool(fintroller_, this);
        AdminInterface(address(redemptionPool))._transferAdmin(msg.sender);
    }

    /**
     * CONSTANT FUNCTIONS
     */

    /**
     * @notice Checks if the bond matured.
     * @return bool true = bond matured, otherwise it didn't.
     */
    function isMatured() public view override returns (bool) {
        return block.timestamp >= expirationTime;
    }

    /**
     * NON-CONSTANT FUNCTIONS
     */

    struct BorrowLocalVars {
        MathError mathErr;
        uint256 debt;
        uint256 debtCeiling;
        uint256 lockedCollateral;
        uint256 hypotheticalCollateralizationRatioMantissa;
        uint256 hypotheticalTotalSupply;
        uint256 newDebt;
        uint256 thresholdCollateralizationRatioMantissa;
    }

    /**
     * @notice Increases the debt of the caller and mints new fyToken.
     *
     * @dev Emits a {Borrow}, {Mint} and {Transfer} event.
     *
     * Requirements:
     *
     * - The vault must be open.
     * - Must be called prior to maturation.
     * - The amount to borrow cannot be zero.
     * - The Fintroller must allow this action to be performed.
     * - The locked collateral cannot be zero.
     * - The total supply of fyTokens cannot exceed the debt ceiling.
     * - The caller must not fall below the threshold collateralization ratio.
     *
     * @param borrowAmount The amount of fyTokens to borrow and print into existence.
     * @return bool true = success, otherwise it reverts.
     */
    function borrow(uint256 borrowAmount) public override isVaultOpen(msg.sender) nonReentrant returns (bool) {
        BorrowLocalVars memory vars;

        /* Checks: bond not matured. */
        require(isMatured() == false, "ERR_BOND_MATURED");

        /* Checks: the zero edge case. */
        require(borrowAmount > 0, "ERR_BORROW_ZERO");

        /* Checks: the Fintroller allows this action to be performed. */
        require(fintroller.getBorrowAllowed(this), "ERR_BORROW_NOT_ALLOWED");

        /* Checks: debt ceiling. */
        (vars.mathErr, vars.hypotheticalTotalSupply) = addUInt(totalSupply, borrowAmount);
        require(vars.mathErr == MathError.NO_ERROR, "ERR_BORROW_MATH_ERROR");
        vars.debtCeiling = fintroller.getBondDebtCeiling(this);
        require(vars.hypotheticalTotalSupply <= vars.debtCeiling, "ERR_BORROW_DEBT_CEILING_OVERFLOW");

        /* Add the borrow amount to the borrower account's current debt. */
        (vars.debt, , vars.lockedCollateral, ) = balanceSheet.getVault(this, msg.sender);
        require(vars.lockedCollateral > 0, "ERR_BORROW_LOCKED_COLLATERAL_ZERO");
        (vars.mathErr, vars.newDebt) = addUInt(vars.debt, borrowAmount);
        require(vars.mathErr == MathError.NO_ERROR, "ERR_BORROW_MATH_ERROR");

        /* Checks: the hypothetical collateralization ratio is above the threshold. */
        vars.hypotheticalCollateralizationRatioMantissa = balanceSheet.getHypotheticalCollateralizationRatio(
            this,
            msg.sender,
            vars.lockedCollateral,
            vars.newDebt
        );
        vars.thresholdCollateralizationRatioMantissa = fintroller.getBondCollateralizationRatio(this);
        require(
            vars.hypotheticalCollateralizationRatioMantissa >= vars.thresholdCollateralizationRatioMantissa,
            "ERR_BELOW_COLLATERALIZATION_RATIO"
        );

        /* Effects: print the new fyTokens into existence. */
        mintInternal(msg.sender, borrowAmount);

        /* Emit a Transfer event. */
        emit Transfer(address(this), msg.sender, borrowAmount);

        /* Interactions: increase the debt of the borrower account. */
        require(balanceSheet.setVaultDebt(this, msg.sender, vars.newDebt), "ERR_BORROW_CALL_SET_VAULT_DEBT");

        /* Emit a Borrow event. */
        emit Borrow(msg.sender, borrowAmount);

        return true;
    }

    /**
     * @notice Destroys `burnAmount` tokens from `holder`, reducing the token supply.
     *
     * @dev Emits a {Burn} and a {Transfer} event.
     *
     * Requirements:
     *
     * - Must be called prior to maturation.
     * - Can only be called by the Redemption Pool.
     * - The amount to burn cannot be zero.
     *
     * @param holder The account whose fyTokens to burn.
     * @param burnAmount The amount of fyTokens to burn.
     * @return bool true = success, otherwise it reverts.
     */
    function burn(address holder, uint256 burnAmount) external override nonReentrant returns (bool) {
        /* Checks: the caller is the Redemption Pool. */
        require(msg.sender == address(redemptionPool), "ERR_BURN_NOT_AUTHORIZED");

        /* Checks: the zero edge case. */
        require(burnAmount > 0, "ERR_BURN_ZERO");

        /* Effects: burns the fyTokens. */
        burnInternal(holder, burnAmount);

        /* Emit a Transfer event. */
        emit Transfer(holder, address(this), burnAmount);

        return true;
    }

    struct LiquidateBorrowsLocalVars {
        MathError mathErr;
        uint256 collateralizationRatioMantissa;
        uint256 lockedCollateral;
        bool isAccountUnderwater;
    }

    /**
     * @notice Repays the debt of the borrower and rewards the caler with a surplus of collateral.
     *
     * @dev Emits a {RepayBorrow}, {Transfer}, {ClutchCollateral} and {LiquidateBorrow} event.
     *
     * Requirements:
     *
     * - The vault must be open.
     * - The liquidator cannot liquidate themselves.
     * - The amount to repay cannot be zero.
     * - The Fintroller must allow this action to be performed.
     * - The borrower must be underwater if the bond didn't mature.
     * - The caller must have at least `repayAmount` fyTokens.
     * - The borrower must have at least `repayAmount` debt.
     * - The amount of clutched collateral cannot be more than what the borrower has in the vault.
     *
     * @param borrower The account to liquidate.
     * @param repayAmount The amount of fyTokens to repay.
     * @return bool true = success, otherwise it reverts.
     */
    function liquidateBorrow(address borrower, uint256 repayAmount)
        external
        override
        isVaultOpen(borrower)
        nonReentrant
        returns (bool)
    {
        LiquidateBorrowsLocalVars memory vars;

        /* Checks: borrowers cannot self liquidate. */
        require(msg.sender != borrower, "ERR_LIQUIDATE_BORROW_SELF");

        /* Checks: the zero edge case. */
        require(repayAmount > 0, "ERR_LIQUIDATE_BORROW_ZERO");

        /* Checks: the Fintroller allows this action to be performed. */
        require(fintroller.getLiquidateBorrowAllowed(this), "ERR_LIQUIDATE_BORROW_NOT_ALLOWED");

        /* After maturation, any vault can be liquidated, irrespective of collateralization ratio. */
        if (isMatured() == false) {
            /* Checks: the borrower fell below the threshold collateralization ratio. */
            vars.isAccountUnderwater = balanceSheet.isAccountUnderwater(this, borrower);
            require(vars.isAccountUnderwater, "ERR_ACCOUNT_NOT_UNDERWATER");
        }

        /* Effects & Interactions: repay the borrower's debt. */
        repayBorrowInternal(msg.sender, borrower, repayAmount);

        /* Interactions: clutch the collateral. */
        uint256 clutchableCollateralAmount = balanceSheet.getClutchableCollateral(this, repayAmount);
        require(
            balanceSheet.clutchCollateral(this, msg.sender, borrower, clutchableCollateralAmount),
            "ERR_LIQUIDATE_BORROW_CALL_CLUTCH_COLLATERAL"
        );

        emit LiquidateBorrow(msg.sender, borrower, repayAmount, clutchableCollateralAmount);

        return true;
    }

    /**
    /** @notice Prints new tokens into existence and assigns them to `beneficiary`,
     * increasing the total supply.
     *
     * @dev Emits a {Mint} and a {Transfer} event.
     *
     * Requirements:
     *
     * - Can only be called by the Redemption Pool.
     * - The amount to mint cannot be zero.
     *
     * @param beneficiary The borrower account for which to mint the tokens.
     * @param mintAmount The amount of fyTokens to print into existence.
     * @return bool true = success, otherwise it reverts.
     */
    function mint(address beneficiary, uint256 mintAmount) external override nonReentrant returns (bool) {
        /* Checks: the caller is the Redemption Pool. */
        require(msg.sender == address(redemptionPool), "ERR_MINT_NOT_AUTHORIZED");

        /* Checks: the zero edge case. */
        require(mintAmount > 0, "ERR_MINT_ZERO");

        /* Effects: print the new fyTokens into existence. */
        mintInternal(beneficiary, mintAmount);

        /* Emit a Transfer event. */
        emit Transfer(address(this), beneficiary, mintAmount);

        return true;
    }

    /**
     * @notice Deletes the borrower account's debt from the registry and take the fyTokens
     * out of circulation.
     *
     * @dev Emits a {Burn}, {Transfer} and {RepayBorrow} event.
     *
     * Requirements:
     *
     * - The vault must be open.
     * - The amount to repay cannot be zero.
     * - The Fintroller must allow this action to be performed.
     * - The caller must have at least `repayAmount` fyTokens.
     * - The caller must have at least `repayAmount` debt.
     *
     * @param repayAmount The amount of fyTokens to repay.
     * @return bool true = success, otherwise it reverts.
     */
    function repayBorrow(uint256 repayAmount) external override isVaultOpen(msg.sender) nonReentrant returns (bool) {
        repayBorrowInternal(msg.sender, msg.sender, repayAmount);
        return true;
    }

    /**
     * @notice Clears the borrower account's debt from the registry and take the fyTokens
     * out of circulation.
     *
     * @dev Emits a {Burn}, {Transfer} and {RepayBorrow} event.
     *
     * Requirements: same as the `repayBorrow` function, but here `borrower` is the account that must
     * have at least `repayAmount` fyTokens to repay the borrow.
     *
     * @param borrower The borrower account for which to repay the borrow.
     * @param repayAmount The amount of fyTokens to repay.
     * @return bool true = success, otherwise it reverts.
     */
    function repayBorrowBehalf(address borrower, uint256 repayAmount)
        external
        override
        isVaultOpen(borrower)
        nonReentrant
        returns (bool)
    {
        repayBorrowInternal(msg.sender, borrower, repayAmount);
        return true;
    }

    /**
     * @notice Updates the Fintroller contract's address saved in storage.
     *
     * @dev Throws a {SetFintroller} event.
     *
     * Requirements:
     *
     * - The caller must be the admin.
     * - The new Fintroller must pass the inspection.
     *
     * @param newFintroller The address of the new Fintroller contract.
     * @return bool true = success, otherwise it reverts.
     */
    function _setFintroller(FintrollerInterface newFintroller) external override onlyAdmin returns (bool) {
        /* Checks: sanity check the new Fintroller contract. */
        require(newFintroller.isFintroller(), "ERR_SET_FINTROLLER_INSPECTION");

        /* Effects: update storage. */
        FintrollerInterface oldFintroller = fintroller;
        fintroller = newFintroller;

        emit SetFintroller(admin, oldFintroller, newFintroller);

        return true;
    }

    /**
     * INTERNAL FUNCTIONS
     */

    /**
     * @dev See the documentation for the public functions that call this internal function.
     */
    function repayBorrowInternal(
        address payer,
        address borrower,
        uint256 repayAmount
    ) internal {
        /* Checks: the zero edge case. */
        require(repayAmount > 0, "ERR_REPAY_BORROW_ZERO");

        /* Checks: the Fintroller allows this action to be performed. */
        require(fintroller.getRepayBorrowAllowed(this), "ERR_REPAY_BORROW_NOT_ALLOWED");

        /* Checks: borrower has a debt to pay. */
        uint256 debt = balanceSheet.getVaultDebt(this, borrower);
        require(debt >= repayAmount, "ERR_REPAY_BORROW_INSUFFICIENT_DEBT");

        /* Checks: the payer has enough fyTokens. */
        require(balanceOf(payer) >= repayAmount, "ERR_REPAY_BORROW_INSUFFICIENT_BALANCE");

        /* Effects: burn the fyTokens. */
        burnInternal(payer, repayAmount);

        /* Emit a Transfer event. */
        emit Transfer(payer, address(this), repayAmount);

        /* Calculate the new debt of the borrower. */
        MathError mathErr;
        uint256 newDebt;
        (mathErr, newDebt) = subUInt(debt, repayAmount);
        /* This operation can't fail because of the previous `require`. */
        assert(mathErr == MathError.NO_ERROR);

        /* Interactions: reduce the debt of the borrower . */
        require(balanceSheet.setVaultDebt(this, borrower, newDebt), "ERR_REPAY_BORROW_CALL_SET_VAULT_DEBT");

        /* Emit both a RepayBorrow event. */
        emit RepayBorrow(payer, borrower, repayAmount, newDebt);
    }
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";
import "./FyTokenStorage.sol";

/**
 * @title FyTokenInterface
 * @author Hifi
 */
abstract contract FyTokenInterface is
    FyTokenStorage, /* no dependency */
    Erc20Interface /* one dependency */
{
    /**
     * CONSTANT FUNCTIONS
     */
    function isMatured() public view virtual returns (bool);

    /**
     * NON-CONSTANT FUNCTIONS
     */
    function borrow(uint256 borrowAmount) external virtual returns (bool);

    function burn(address holder, uint256 burnAmount) external virtual returns (bool);

    function liquidateBorrow(address borrower, uint256 repayAmount) external virtual returns (bool);

    function mint(address beneficiary, uint256 mintAmount) external virtual returns (bool);

    function repayBorrow(uint256 repayAmount) external virtual returns (bool);

    function repayBorrowBehalf(address borrower, uint256 repayAmount) external virtual returns (bool);

    function _setFintroller(FintrollerInterface newFintroller) external virtual returns (bool);

    /**
     * EVENTS
     */
    event Borrow(address indexed borrower, uint256 borrowAmount);

    event LiquidateBorrow(
        address indexed liquidator,
        address indexed borrower,
        uint256 repayAmount,
        uint256 clutchedCollateralAmount
    );

    event RepayBorrow(address indexed payer, address indexed borrower, uint256 repayAmount, uint256 newDebt);

    event SetFintroller(address indexed admin, FintrollerInterface oldFintroller, FintrollerInterface newFintroller);
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";
import "./BalanceSheetInterface.sol";
import "./FintrollerInterface.sol";
import "./RedemptionPoolInterface.sol";

/**
 * @title FyTokenStorage
 * @author Hifi
 */
abstract contract FyTokenStorage {
    /**
     * STORAGE PROPERTIES
     */

    /**
     * @notice The global debt registry.
     */
    BalanceSheetInterface public balanceSheet;

    /**
     * @notice The Erc20 asset that backs the borrows of this fyToken.
     */
    Erc20Interface public collateral;

    /**
     * @notice The ratio between mantissa precision (1e18) and the collateral precision.
     */
    uint256 public collateralPrecisionScalar;

    /**
     * @notice Unix timestamp in seconds for when this token expires.
     */
    uint256 public expirationTime;

    /**
     * @notice The unique Fintroller associated with this contract.
     */
    FintrollerInterface public fintroller;

    /**
     * @notice The unique Redemption Pool associated with this contract.
     */
    RedemptionPoolInterface public redemptionPool;

    /**
     * @notice The Erc20 underlying, or target, asset for this fyToken.
     */
    Erc20Interface public underlying;

    /**
     * @notice The ratio between mantissa precision (1e18) and the underlying precision.
     */
    uint256 public underlyingPrecisionScalar;

    /**
     * @notice Indicator that this is a FyToken contract, for inspection.
     */
    bool public constant isFyToken = true;
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./Admin.sol";
import "./CarefulMath.sol";
import "./Erc20Interface.sol";
import "./Erc20Recover.sol";
import "./SafeErc20.sol";
import "./ReentrancyGuard.sol";

import "./FintrollerInterface.sol";
import "./RedemptionPoolInterface.sol";

/**
 * @title RedemptionPool
 * @author Hifi
 * @notice Mints 1 fyToken in exhchange for 1 underlying before maturation and burns 1 fyToken
 * in exchange for 1 underlying after maturation.
 * @dev Instantiated by the fyToken in its constructor.
 */
contract RedemptionPool is
    CarefulMath, /* no dependency */
    ReentrancyGuard, /* no dependency */
    RedemptionPoolInterface, /* one dependency */
    Admin, /* two dependencies */
    Erc20Recover /* five dependencies */
{
    using SafeErc20 for Erc20Interface;

    /**
     * @param fintroller_ The address of the Fintroller contract.
     * @param fyToken_ The address of the fyToken contract.
     */
    constructor(FintrollerInterface fintroller_, FyTokenInterface fyToken_) Admin() {
        /* Set the Fintroller contract and sanity check it. */
        fintroller = fintroller_;
        fintroller.isFintroller();

        /**
         * Set the fyToken contract. It cannot be sanity-checked because the fyToken creates this
         * contract in its own constructor and contracts cannot be called while initializing.
         */
        fyToken = fyToken_;
    }

    struct RedeemFyTokensLocalVars {
        MathError mathErr;
        uint256 newUnderlyingTotalSupply;
        uint256 underlyingPrecisionScalar;
        uint256 underlyingAmount;
    }

    /**
     * @notice Pays the token holder the face value at maturation time.
     *
     * @dev Emits a {RedeemFyTokens} event.
     *
     * Requirements:
     *
     * - Must be called after maturation.
     * - The amount to redeem cannot be zero.
     * - The Fintroller must allow this action to be performed.
     * - There must be enough liquidity in the Redemption Pool.
     *
     * @param fyTokenAmount The amount of fyTokens to redeem for the underlying asset.
     * @return bool true = success, otherwise it reverts.
     */
    function redeemFyTokens(uint256 fyTokenAmount) external override nonReentrant returns (bool) {
        RedeemFyTokensLocalVars memory vars;

        /* Checks: maturation time. */
        require(block.timestamp >= fyToken.expirationTime(), "ERR_BOND_NOT_MATURED");

        /* Checks: the zero edge case. */
        require(fyTokenAmount > 0, "ERR_REDEEM_FYTOKENS_ZERO");

        /* Checks: the Fintroller allows this action to be performed. */
        require(fintroller.getRedeemFyTokensAllowed(fyToken), "ERR_REDEEM_FYTOKENS_NOT_ALLOWED");

        /**
         * fyTokens always have 18 decimals so the underlying amount needs to be downscaled.
         * If the precision scalar is 1, it means that the underlying also has 18 decimals.
         */
        vars.underlyingPrecisionScalar = fyToken.underlyingPrecisionScalar();
        if (vars.underlyingPrecisionScalar != 1) {
            (vars.mathErr, vars.underlyingAmount) = divUInt(fyTokenAmount, vars.underlyingPrecisionScalar);
            require(vars.mathErr == MathError.NO_ERROR, "ERR_REDEEM_FYTOKENS_MATH_ERROR");
        } else {
            vars.underlyingAmount = fyTokenAmount;
        }

        /* Checks: there is enough liquidity. */
        require(vars.underlyingAmount <= totalUnderlyingSupply, "ERR_REDEEM_FYTOKENS_INSUFFICIENT_UNDERLYING");

        /* Effects: decrease the remaining supply of underlying. */
        (vars.mathErr, vars.newUnderlyingTotalSupply) = subUInt(totalUnderlyingSupply, vars.underlyingAmount);
        assert(vars.mathErr == MathError.NO_ERROR);
        totalUnderlyingSupply = vars.newUnderlyingTotalSupply;

        /* Interactions: burn the fyTokens. */
        require(fyToken.burn(msg.sender, fyTokenAmount), "ERR_SUPPLY_UNDERLYING_CALL_BURN");

        /* Interactions: perform the Erc20 transfer. */
        fyToken.underlying().safeTransfer(msg.sender, vars.underlyingAmount);

        emit RedeemFyTokens(msg.sender, fyTokenAmount, vars.underlyingAmount);

        return true;
    }

    struct SupplyUnderlyingLocalVars {
        MathError mathErr;
        uint256 fyTokenAmount;
        uint256 newUnderlyingTotalSupply;
        uint256 underlyingPrecisionScalar;
    }

    /**
     * @notice An alternative to the usual minting method that does not involve taking on debt.
     *
     * @dev Emits a {SupplyUnderlying} event.
     *
     * Requirements:
     *
     * - Must be called prior to maturation.
     * - The amount to supply cannot be zero.
     * - The Fintroller must allow this action to be performed.
     * - The caller must have allowed this contract to spend `underlyingAmount` tokens.
     *
     * @param underlyingAmount The amount of underlying to supply to the Redemption Pool.
     * @return bool true = success, otherwise it reverts.
     */
    function supplyUnderlying(uint256 underlyingAmount) external override nonReentrant returns (bool) {
        SupplyUnderlyingLocalVars memory vars;

        /* Checks: maturation time. */
        require(block.timestamp < fyToken.expirationTime(), "ERR_BOND_MATURED");

        /* Checks: the zero edge case. */
        require(underlyingAmount > 0, "ERR_SUPPLY_UNDERLYING_ZERO");

        /* Checks: the Fintroller allows this action to be performed. */
        require(fintroller.getSupplyUnderlyingAllowed(fyToken), "ERR_SUPPLY_UNDERLYING_NOT_ALLOWED");

        /* Effects: update storage. */
        (vars.mathErr, vars.newUnderlyingTotalSupply) = addUInt(totalUnderlyingSupply, underlyingAmount);
        require(vars.mathErr == MathError.NO_ERROR, "ERR_SUPPLY_UNDERLYING_MATH_ERROR");
        totalUnderlyingSupply = vars.newUnderlyingTotalSupply;

        /**
         * fyTokens always have 18 decimals so the underlying amount needs to be upscaled.
         * If the precision scalar is 1, it means that the underlying also has 18 decimals.
         */
        vars.underlyingPrecisionScalar = fyToken.underlyingPrecisionScalar();
        if (vars.underlyingPrecisionScalar != 1) {
            (vars.mathErr, vars.fyTokenAmount) = mulUInt(underlyingAmount, vars.underlyingPrecisionScalar);
            require(vars.mathErr == MathError.NO_ERROR, "ERR_SUPPLY_UNDERLYING_MATH_ERROR");
        } else {
            vars.fyTokenAmount = underlyingAmount;
        }

        /* Interactions: mint the fyTokens. */
        require(fyToken.mint(msg.sender, vars.fyTokenAmount), "ERR_SUPPLY_UNDERLYING_CALL_MINT");

        /* Interactions: perform the Erc20 transfer. */
        fyToken.underlying().safeTransferFrom(msg.sender, address(this), underlyingAmount);

        emit SupplyUnderlying(msg.sender, underlyingAmount, vars.fyTokenAmount);

        return true;
    }
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./RedemptionPoolStorage.sol";

/**
 * @title RedemptionPoolInterface
 * @author Hifi
 */
abstract contract RedemptionPoolInterface is RedemptionPoolStorage {
    /**
     * NON-CONSTANT FUNCTIONS
     */
    function redeemFyTokens(uint256 fyTokenAmount) external virtual returns (bool);

    function supplyUnderlying(uint256 underlyingAmount) external virtual returns (bool);

    /**
     * EVENTS
     */
    event RedeemFyTokens(address indexed account, uint256 fyTokenAmount, uint256 underlyingAmount);

    event SupplyUnderlying(address indexed account, uint256 underlyingAmount, uint256 fyTokenAmount);
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

import "./FintrollerInterface.sol";
import "./FyTokenInterface.sol";

/**
 * @title RedemptionPoolStorage
 * @author Hifi
 */
abstract contract RedemptionPoolStorage {
    /**
     * @notice The unique Fintroller associated with this contract.
     */
    FintrollerInterface public fintroller;

    /**
     * @notice The amount of the underlying asset available to be redeemed after maturation.
     */
    uint256 public totalUnderlyingSupply;

    /**
     * The unique fyToken associated with this Redemption Pool.
     */
    FyTokenInterface public fyToken;

    /**
     * @notice Indicator that this is a Redemption Pool contract, for inspection.
     */
    bool public constant isRedemptionPool = true;
}

/* SPDX-License-Identifier: LGPL-3.0-or-later */
pragma solidity ^0.7.0;

/**
 * @title ReentrancyGuard
 * @author Paul Razvan Berg
 * @notice Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * @dev Forked from OpenZeppelin
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/math/ReentrancyGuard.sol
 */
abstract contract ReentrancyGuard {
    bool private notEntered;

    /*
     * Storing an initial non-zero value makes deployment a bit more expensive
     * but in exchange the refund on every call to nonReentrant will be lower
     * in amount. Since refunds are capped to a percetange of the total
     * transaction's gas, it is best to keep them low in cases like this
     * one, to increase the likelihood of the full refund coming into effect.
     */
    constructor() {
        notEntered = true;
    }

    /**
     * @notice Prevents a contract from calling itself, directly or indirectly.
     * @dev Calling a `nonReentrant` function from another `nonReentrant` function
     * is not supported. It is possible to prevent this from happening by making
     * the `nonReentrant` function external, and make it call a `private`
     * function that does the actual work.
     */
    modifier nonReentrant() {
        /* On the first call to nonReentrant, _notEntered will be true. */
        require(notEntered, "ERR_REENTRANT_CALL");

        /* Any calls to nonReentrant after this point will fail. */
        notEntered = false;

        _;

        /*
         * By storing the original value once again, a refund is triggered (see
         * https://eips.ethereum.org/EIPS/eip-2200).
         */
        notEntered = true;
    }
}

/* SPDX-License-Identifier: MIT */
pragma solidity ^0.7.0;

import "./Erc20Interface.sol";
import "./Address.sol";

/**
 * @title SafeErc20.sol
 * @author Paul Razvan Berg
 * @notice Wraps around Erc20 operations that throw on failure (when the token contract
 * returns false). Tokens that return no value (and instead revert or throw
 * on failure) are also supported, non-reverting calls are assumed to be successful.
 *
 * To use this library you can add a `using SafeErc20 for Erc20Interface;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 *
 * @dev Forked from OpenZeppelin
 * https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v3.1.0/contracts/utils/Address.sol
 */
library SafeErc20 {
    using Address for address;

    /**
     * INTERNAL FUNCTIONS
     */

    function safeTransfer(
        Erc20Interface token,
        address to,
        uint256 amount
    ) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, amount));
    }

    function safeTransferFrom(
        Erc20Interface token,
        address from,
        address to,
        uint256 amount
    ) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, amount));
    }

    /**
     * PRIVATE FUNCTIONS
     */

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it cannot be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function callOptionalReturn(Erc20Interface token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.
        bytes memory returndata = functionCall(address(token), data, "ERR_SAFE_ERC20_LOW_LEVEL_CALL");
        if (returndata.length > 0) {
            /* Return data is optional. */
            require(abi.decode(returndata, (bool)), "ERR_SAFE_ERC20_ERC20_OPERATION");
        }
    }

    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) private returns (bytes memory) {
        require(target.isContract(), "ERR_SAFE_ERC20_CALL_TO_NON_CONTRACT");

        /* solhint-disable-next-line avoid-low-level-calls */
        (bool success, bytes memory returndata) = target.call(data);
        if (success) {
            return returndata;
        } else {
            /* Look for revert reason and bubble it up if present */
            if (returndata.length > 0) {
                /* The easiest way to bubble the revert reason is using memory via assembly. */

                /* solhint-disable-next-line no-inline-assembly */
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):