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