// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)pragmasolidity ^0.8.1;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @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
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/functionisContract(address account) internalviewreturns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0// for contracts in construction, since the code is only stored at the end// of the constructor execution.return account.code.length>0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable recipient, uint256 amount) internal{
require(address(this).balance>= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value
) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(address(this).balance>= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytesmemory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytesmemory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/functionverifyCallResult(bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalpurereturns (bytesmemory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
Contract Source Code
File 2 of 19: AggregatorV3Interface.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;interfaceAggregatorV3Interface{
functiondecimals() externalviewreturns (uint8);
functiondescription() externalviewreturns (stringmemory);
functionversion() externalviewreturns (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.functiongetRoundData(uint80 _roundId)
externalviewreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
functionlatestRoundData()
externalviewreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
Contract Source Code
File 3 of 19: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)pragmasolidity ^0.8.0;/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
}
Contract Source Code
File 4 of 19: ERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)pragmasolidity ^0.8.0;import"./IERC20.sol";
import"./extensions/IERC20Metadata.sol";
import"../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/contractERC20isContext, IERC20, IERC20Metadata{
mapping(address=>uint256) private _balances;
mapping(address=>mapping(address=>uint256)) private _allowances;
uint256private _totalSupply;
stringprivate _name;
stringprivate _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/constructor(stringmemory name_, stringmemory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/functionname() publicviewvirtualoverridereturns (stringmemory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/functionsymbol() publicviewvirtualoverridereturns (stringmemory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless this function is
* overridden;
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/functiondecimals() publicviewvirtualoverridereturns (uint8) {
return18;
}
/**
* @dev See {IERC20-totalSupply}.
*/functiontotalSupply() publicviewvirtualoverridereturns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/functionbalanceOf(address account) publicviewvirtualoverridereturns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/functiontransfer(address to, uint256 amount) publicvirtualoverridereturns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
returntrue;
}
/**
* @dev See {IERC20-allowance}.
*/functionallowance(address owner, address spender) publicviewvirtualoverridereturns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionapprove(address spender, uint256 amount) publicvirtualoverridereturns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
returntrue;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualoverridereturns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
returntrue;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionincreaseAllowance(address spender, uint256 addedValue) publicvirtualreturns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
returntrue;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-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`.
*/functiondecreaseAllowance(address spender, uint256 subtractedValue) publicvirtualreturns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
returntrue;
}
/**
* @dev Moves `amount` of tokens from `sender` to `recipient`.
*
* This 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:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/function_transfer(addressfrom,
address to,
uint256 amount
) internalvirtual{
require(from!=address(0), "ERC20: transfer from the zero address");
require(to !=address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/function_mint(address account, uint256 amount) internalvirtual{
require(account !=address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/function_burn(address account, uint256 amount) internalvirtual{
require(account !=address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This 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_approve(address owner,
address spender,
uint256 amount
) internalvirtual{
require(owner !=address(0), "ERC20: approve from the zero address");
require(spender !=address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/function_spendAllowance(address owner,
address spender,
uint256 amount
) internalvirtual{
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance !=type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/function_beforeTokenTransfer(addressfrom,
address to,
uint256 amount
) internalvirtual{}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/function_afterTokenTransfer(addressfrom,
address to,
uint256 amount
) internalvirtual{}
}
Contract Source Code
File 5 of 19: FraxlendPair.sol
// SPDX-License-Identifier: ISCpragmasolidity ^0.8.16;// ====================================================================// | ______ _______ |// | / _____________ __ __ / ____(_____ ____ _____ ________ |// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |// | |// ====================================================================// ========================== FraxlendPair ============================// ====================================================================// Frax Finance: https://github.com/FraxFinance// Primary Author// Drake Evans: https://github.com/DrakeEvans// Reviewers// Dennis: https://github.com/denett// Sam Kazemian: https://github.com/samkazemian// Travis Moore: https://github.com/FortisFortuna// Jack Corddry: https://github.com/corddry// Rich Gee: https://github.com/zer0blockchain// ====================================================================import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import"@openzeppelin/contracts/security/ReentrancyGuard.sol";
import"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import"./FraxlendPairConstants.sol";
import"./FraxlendPairCore.sol";
import"./libraries/VaultAccount.sol";
import"./libraries/SafeERC20.sol";
import"./interfaces/IERC4626.sol";
import"./interfaces/IFraxlendWhitelist.sol";
import"./interfaces/IRateCalculator.sol";
import"./interfaces/ISwapper.sol";
contractFraxlendPairisIERC20Metadata, FraxlendPairCore{
usingVaultAccountingLibraryforVaultAccount;
usingSafeERC20forIERC20;
constructor(bytesmemory _configData,
bytesmemory _immutables,
uint256 _maxLTV,
uint256 _liquidationFee,
uint256 _maturityDate,
uint256 _penaltyRate,
bool _isBorrowerWhitelistActive,
bool _isLenderWhitelistActive
)
FraxlendPairCore(
_configData,
_immutables,
_maxLTV,
_liquidationFee,
_maturityDate,
_penaltyRate,
_isBorrowerWhitelistActive,
_isLenderWhitelistActive
)
ERC20("", "")
Ownable()
Pausable()
{}
// ============================================================================================// ERC20 Metadata// ============================================================================================functionname() publicviewoverride(ERC20, IERC20Metadata) returns (stringmemory) {
return nameOfContract;
}
functionsymbol() publicviewoverride(ERC20, IERC20Metadata) returns (stringmemory) {
// prettier-ignore// solhint-disable-next-line max-line-lengthreturnstring(abi.encodePacked("FraxlendV1 - ", collateralContract.safeSymbol(), "/", assetContract.safeSymbol()));
}
functiondecimals() publicpureoverride(ERC20, IERC20Metadata) returns (uint8) {
return18;
}
// totalSupply for fToken ERC20 compatibilityfunctiontotalSupply() publicviewoverride(ERC20, IERC20) returns (uint256) {
return totalAsset.shares;
}
// ============================================================================================// Functions: Helpers// ============================================================================================functionasset() externalviewreturns (address) {
returnaddress(assetContract);
}
functiongetConstants()
externalpurereturns (uint256 _LTV_PRECISION,
uint256 _LIQ_PRECISION,
uint256 _UTIL_PREC,
uint256 _FEE_PRECISION,
uint256 _EXCHANGE_PRECISION,
uint64 _DEFAULT_INT,
uint16 _DEFAULT_PROTOCOL_FEE,
uint256 _MAX_PROTOCOL_FEE
)
{
_LTV_PRECISION = LTV_PRECISION;
_LIQ_PRECISION = LIQ_PRECISION;
_UTIL_PREC = UTIL_PREC;
_FEE_PRECISION = FEE_PRECISION;
_EXCHANGE_PRECISION = EXCHANGE_PRECISION;
_DEFAULT_INT = DEFAULT_INT;
_DEFAULT_PROTOCOL_FEE = DEFAULT_PROTOCOL_FEE;
_MAX_PROTOCOL_FEE = MAX_PROTOCOL_FEE;
}
/// @notice The ```getImmutableAddressBool``` function gets all the address and bool configs/// @return _assetContract Address of asset/// @return _collateralContract Address of collateral/// @return _oracleMultiply Address of oracle numerator/// @return _oracleDivide Address of oracle denominator/// @return _rateContract Address of rate contract/// @return _DEPLOYER_CONTRACT Address of deployer contract/// @return _COMPTROLLER_ADDRESS Address of comptroller/// @return _FRAXLEND_WHITELIST Address of whitelist/// @return _borrowerWhitelistActive Boolean is borrower whitelist active/// @return _lenderWhitelistActive Boolean is lender whitelist activefunctiongetImmutableAddressBool()
externalviewreturns (address _assetContract,
address _collateralContract,
address _oracleMultiply,
address _oracleDivide,
address _rateContract,
address _DEPLOYER_CONTRACT,
address _COMPTROLLER_ADDRESS,
address _FRAXLEND_WHITELIST,
bool _borrowerWhitelistActive,
bool _lenderWhitelistActive
)
{
_assetContract =address(assetContract);
_collateralContract =address(collateralContract);
_oracleMultiply = oracleMultiply;
_oracleDivide = oracleDivide;
_rateContract =address(rateContract);
_DEPLOYER_CONTRACT = DEPLOYER_ADDRESS;
_COMPTROLLER_ADDRESS = COMPTROLLER_ADDRESS;
_FRAXLEND_WHITELIST = FRAXLEND_WHITELIST_ADDRESS;
_borrowerWhitelistActive = borrowerWhitelistActive;
_lenderWhitelistActive = lenderWhitelistActive;
}
/// @notice The ```getImmutableUint256``` function gets all uint256 config values/// @return _oracleNormalization Oracle normalization factor/// @return _maxLTV Maximum LTV/// @return _cleanLiquidationFee Clean Liquidation Fee/// @return _maturityDate Maturity Date/// @return _penaltyRate Penalty RatefunctiongetImmutableUint256()
externalviewreturns (uint256 _oracleNormalization,
uint256 _maxLTV,
uint256 _cleanLiquidationFee,
uint256 _maturityDate,
uint256 _penaltyRate
)
{
_oracleNormalization = oracleNormalization;
_maxLTV = maxLTV;
_cleanLiquidationFee = cleanLiquidationFee;
_maturityDate = maturityDate;
_penaltyRate = penaltyRate;
}
/// @notice The ```getUserSnapshot``` function gets user level accounting data/// @param _address The user address/// @return _userAssetShares The user fToken balance/// @return _userBorrowShares The user borrow shares/// @return _userCollateralBalance The user collateral balancefunctiongetUserSnapshot(address _address)
externalviewreturns (uint256 _userAssetShares,
uint256 _userBorrowShares,
uint256 _userCollateralBalance
)
{
_userAssetShares = balanceOf(_address);
_userBorrowShares = userBorrowShares[_address];
_userCollateralBalance = userCollateralBalance[_address];
}
/// @notice The ```getPairAccounting``` function gets all pair level accounting numbers/// @return _totalAssetAmount Total assets deposited and interest accrued, total claims/// @return _totalAssetShares Total fTokens/// @return _totalBorrowAmount Total borrows/// @return _totalBorrowShares Total borrow shares/// @return _totalCollateral Total collateralfunctiongetPairAccounting()
externalviewreturns (uint128 _totalAssetAmount,
uint128 _totalAssetShares,
uint128 _totalBorrowAmount,
uint128 _totalBorrowShares,
uint256 _totalCollateral
)
{
VaultAccount memory _totalAsset = totalAsset;
_totalAssetAmount = _totalAsset.amount;
_totalAssetShares = _totalAsset.shares;
VaultAccount memory _totalBorrow = totalBorrow;
_totalBorrowAmount = _totalBorrow.amount;
_totalBorrowShares = _totalBorrow.shares;
_totalCollateral = totalCollateral;
}
/// @notice The ```toBorrowShares``` function converts a given amount of borrow debt into the number of shares/// @param _amount Amount of borrow/// @param _roundUp Whether to roundup during divisionfunctiontoBorrowShares(uint256 _amount, bool _roundUp) externalviewreturns (uint256) {
return totalBorrow.toShares(_amount, _roundUp);
}
/// @notice The ```toBorrowAmount``` function converts a given amount of borrow debt into the number of shares/// @param _shares Shares of borrow/// @param _roundUp Whether to roundup during division/// @return The amount of assetfunctiontoBorrowAmount(uint256 _shares, bool _roundUp) externalviewreturns (uint256) {
return totalBorrow.toAmount(_shares, _roundUp);
}
/// @notice The ```toAssetAmount``` function converts a given number of shares to an asset amount/// @param _shares Shares of asset (fToken)/// @param _roundUp Whether to round up after division/// @return The amount of assetfunctiontoAssetAmount(uint256 _shares, bool _roundUp) externalviewreturns (uint256) {
return totalAsset.toAmount(_shares, _roundUp);
}
/// @notice The ```toAssetShares``` function converts a given asset amount to a number of asset shares (fTokens)/// @param _amount The amount of asset/// @param _roundUp Whether to round up after division/// @return The number of shares (fTokens)functiontoAssetShares(uint256 _amount, bool _roundUp) externalviewreturns (uint256) {
return totalAsset.toShares(_amount, _roundUp);
}
// ============================================================================================// Functions: Configuration// ============================================================================================/// @notice The ```SetTimeLock``` event fires when the TIME_LOCK_ADDRESS is set/// @param _oldAddress The original address/// @param _newAddress The new addresseventSetTimeLock(address _oldAddress, address _newAddress);
/// @notice The ```setTimeLock``` function sets the TIME_LOCK address/// @param _newAddress the new time lock addressfunctionsetTimeLock(address _newAddress) external{
if (msg.sender!= TIME_LOCK_ADDRESS) revert OnlyTimeLock();
emit SetTimeLock(TIME_LOCK_ADDRESS, _newAddress);
TIME_LOCK_ADDRESS = _newAddress;
}
/// @notice The ```ChangeFee``` event first when the fee is changed/// @param _newFee The new feeeventChangeFee(uint32 _newFee);
/// @notice The ```changeFee``` function changes the protocol fee, max 50%/// @param _newFee The new feefunctionchangeFee(uint32 _newFee) externalwhenNotPaused{
if (msg.sender!= TIME_LOCK_ADDRESS) revert OnlyTimeLock();
if (_newFee > MAX_PROTOCOL_FEE) {
revert BadProtocolFee();
}
_addInterest();
currentRateInfo.feeToProtocolRate = _newFee;
emit ChangeFee(_newFee);
}
/// @notice The ```WithdrawFees``` event fires when the fees are withdrawn/// @param _shares Number of _shares (fTokens) redeemed/// @param _recipient To whom the assets were sent/// @param _amountToTransfer The amount of fees redeemedeventWithdrawFees(uint128 _shares, address _recipient, uint256 _amountToTransfer);
/// @notice The ```withdrawFees``` function withdraws fees accumulated/// @param _shares Number of fTokens to redeem/// @param _recipient Address to send the assets/// @return _amountToTransfer Amount of assets sent to recipientfunctionwithdrawFees(uint128 _shares, address _recipient) externalonlyOwnerreturns (uint256 _amountToTransfer) {
// Grab some data from state to save gas
VaultAccount memory _totalAsset = totalAsset;
VaultAccount memory _totalBorrow = totalBorrow;
// Take all available if 0 value passedif (_shares ==0) _shares =uint128(balanceOf(address(this)));
// We must calculate this before we subtract from _totalAsset or invoke _burn
_amountToTransfer = _totalAsset.toAmount(_shares, true);
// Check for sufficient withdraw liquidityuint256 _assetsAvailable = _totalAssetAvailable(_totalAsset, _totalBorrow);
if (_assetsAvailable < _amountToTransfer) {
revert InsufficientAssetsInContract(_assetsAvailable, _amountToTransfer);
}
// Effects: bookkeeping
_totalAsset.amount -=uint128(_amountToTransfer);
_totalAsset.shares -= _shares;
// Effects: write to states// NOTE: will revert if _shares > balanceOf(address(this))
_burn(address(this), _shares);
totalAsset = _totalAsset;
// Interactions
assetContract.safeTransfer(_recipient, _amountToTransfer);
emit WithdrawFees(_shares, _recipient, _amountToTransfer);
}
/// @notice The ```SetSwapper``` event fires whenever a swapper is black or whitelisted/// @param _swapper The swapper address/// @param _approval The approvaleventSetSwapper(address _swapper, bool _approval);
/// @notice The ```setSwapper``` function is called to black or whitelist a given swapper address/// @dev/// @param _swapper The swapper address/// @param _approval The approvalfunctionsetSwapper(address _swapper, bool _approval) externalonlyOwner{
swappers[_swapper] = _approval;
emit SetSwapper(_swapper, _approval);
}
/// @notice The ```SetApprovedLender``` event fires when a lender is black or whitelisted/// @param _address The address/// @param _approval The approvaleventSetApprovedLender(addressindexed _address, bool _approval);
/// @notice The ```setApprovedLenders``` function sets a given set of addresses to the whitelist/// @dev Cannot black list self/// @param _lenders The addresses who's status will be set/// @param _approval The approval statusfunctionsetApprovedLenders(address[] calldata _lenders, bool _approval) externalapprovedLender(msg.sender) {
for (uint256 i =0; i < _lenders.length; i++) {
// Do not set when _approval == false and _lender == msg.senderif (_approval || _lenders[i] !=msg.sender) {
approvedLenders[_lenders[i]] = _approval;
emit SetApprovedLender(_lenders[i], _approval);
}
}
}
/// @notice The ```SetApprovedBorrower``` event fires when a borrower is black or whitelisted/// @param _address The address/// @param _approval The approvaleventSetApprovedBorrower(addressindexed _address, bool _approval);
/// @notice The ```setApprovedBorrowers``` function sets a given array of addresses to the whitelist/// @dev Cannot black list self/// @param _borrowers The addresses who's status will be set/// @param _approval The approval statusfunctionsetApprovedBorrowers(address[] calldata _borrowers, bool _approval) externalapprovedBorrower{
for (uint256 i =0; i < _borrowers.length; i++) {
// Do not set when _approval == false and _borrower == msg.senderif (_approval || _borrowers[i] !=msg.sender) {
approvedBorrowers[_borrowers[i]] = _approval;
emit SetApprovedBorrower(_borrowers[i], _approval);
}
}
}
functionpause() external{
if (
msg.sender!= CIRCUIT_BREAKER_ADDRESS &&msg.sender!= COMPTROLLER_ADDRESS &&msg.sender!= owner() &&msg.sender!= DEPLOYER_ADDRESS
) {
revert ProtocolOrOwnerOnly();
}
_addInterest(); // accrue any interest prior to pausing as it won't accrue during pause
_pause();
}
functionunpause() external{
if (msg.sender!= COMPTROLLER_ADDRESS &&msg.sender!= owner()) {
revert ProtocolOrOwnerOnly();
}
// Resets the lastTimestamp which has the effect of no interest accruing over the pause period
_addInterest();
_unpause();
}
}
// SPDX-License-Identifier: ISCpragmasolidity ^0.8.16;// ====================================================================// | ______ _______ |// | / _____________ __ __ / ____(_____ ____ _____ ________ |// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |// | |// ====================================================================// ========================= FraxlendPairCore =========================// ====================================================================// Frax Finance: https://github.com/FraxFinance// Primary Author// Drake Evans: https://github.com/DrakeEvans// Reviewers// Dennis: https://github.com/denett// Sam Kazemian: https://github.com/samkazemian// Travis Moore: https://github.com/FortisFortuna// Jack Corddry: https://github.com/corddry// Rich Gee: https://github.com/zer0blockchain// ====================================================================import"@openzeppelin/contracts/token/ERC20/ERC20.sol";
import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
import"@openzeppelin/contracts/security/ReentrancyGuard.sol";
import"@openzeppelin/contracts/security/Pausable.sol";
import"@openzeppelin/contracts/access/Ownable.sol";
import"@openzeppelin/contracts/utils/math/SafeCast.sol";
import"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import"./FraxlendPairConstants.sol";
import"./libraries/VaultAccount.sol";
import"./libraries/SafeERC20.sol";
import"./interfaces/IERC4626.sol";
import"./interfaces/IFraxlendWhitelist.sol";
import"./interfaces/IRateCalculator.sol";
import"./interfaces/ISwapper.sol";
/// @title FraxlendPairCore/// @author Drake Evans (Frax Finance) https://github.com/drakeevans/// @notice An abstract contract which contains the core logic and storage for the FraxlendPairabstractcontractFraxlendPairCoreisFraxlendPairConstants, ERC20, Ownable, Pausable, ReentrancyGuard{
usingVaultAccountingLibraryforVaultAccount;
usingSafeERC20forIERC20;
usingSafeCastforuint256;
stringpublic version ="1.0.0";
// ============================================================================================// Settings set by constructor() & initialize()// ============================================================================================// Asset and collateral contracts
IERC20 internalimmutable assetContract;
IERC20 publicimmutable collateralContract;
// Oracle wrapper contract and oracleDataaddresspublicimmutable oracleMultiply;
addresspublicimmutable oracleDivide;
uint256publicimmutable oracleNormalization;
// LTV Settingsuint256publicimmutable maxLTV;
// Liquidation Feeuint256publicimmutable cleanLiquidationFee;
uint256publicimmutable dirtyLiquidationFee;
// Interest Rate Calculator Contract
IRateCalculator publicimmutable rateContract; // For complex rate calculationsbytespublic rateInitCallData; // Optional extra data from init function to be passed to rate calculator// Swappermapping(address=>bool) public swappers; // approved swapper addresses// Deployeraddresspublicimmutable DEPLOYER_ADDRESS;
// Admin contractsaddresspublicimmutable CIRCUIT_BREAKER_ADDRESS;
addresspublicimmutable COMPTROLLER_ADDRESS;
addresspublic TIME_LOCK_ADDRESS;
// Dependenciesaddresspublicimmutable FRAXLEND_WHITELIST_ADDRESS;
// ERC20 token name, accessible via name()stringinternal nameOfContract;
// Maturity Date & Penalty Interest Rate (per Sec)uint256publicimmutable maturityDate;
uint256publicimmutable penaltyRate;
// ============================================================================================// Storage// ============================================================================================/// @notice Stores information about the current interest rate/// @dev struct is packed to reduce SLOADs. feeToProtocolRate is 1e5 precision, ratePerSec is 1e18 precision
CurrentRateInfo public currentRateInfo;
structCurrentRateInfo {
uint64 lastBlock;
uint64 feeToProtocolRate; // Fee amount 1e5 precisionuint64 lastTimestamp;
uint64 ratePerSec;
}
/// @notice Stores information about the current exchange rate. Collateral:Asset ratio/// @dev Struct packed to save SLOADs. Amount of Collateral Token to buy 1e18 Asset Token
ExchangeRateInfo public exchangeRateInfo;
structExchangeRateInfo {
uint32 lastTimestamp;
uint224 exchangeRate; // collateral:asset ratio. i.e. how much collateral to buy 1e18 asset
}
// Contract Level Accounting
VaultAccount public totalAsset; // amount = total amount of assets, shares = total shares outstanding
VaultAccount public totalBorrow; // amount = total borrow amount with interest accrued, shares = total shares outstandinguint256public totalCollateral; // total amount of collateral in contract// User Level Accounting/// @notice Stores the balance of collateral for each usermapping(address=>uint256) public userCollateralBalance; // amount of collateral each user is backed/// @notice Stores the balance of borrow shares for each usermapping(address=>uint256) public userBorrowShares; // represents the shares held by individuals// NOTE: user shares of assets are represented as ERC-20 tokens and accessible via balanceOf()// Internal Whitelistsboolpublicimmutable borrowerWhitelistActive;
mapping(address=>bool) public approvedBorrowers;
boolpublicimmutable lenderWhitelistActive;
mapping(address=>bool) public approvedLenders;
// ============================================================================================// Initialize// ============================================================================================/// @notice The ```constructor``` function is called on deployment/// @param _configData abi.encode(address _asset, address _collateral, address _oracleMultiply, address _oracleDivide, uint256 _oracleNormalization, address _rateContract, bytes memory _rateInitData)/// @param _maxLTV The Maximum Loan-To-Value for a borrower to be considered solvent (1e5 precision)/// @param _liquidationFee The fee paid to liquidators given as a % of the repayment (1e5 precision)/// @param _maturityDate The maturityDate date of the Pair/// @param _penaltyRate The interest rate after maturity date/// @param _isBorrowerWhitelistActive Enables borrower whitelist/// @param _isLenderWhitelistActive Enables lender whitelistconstructor(bytesmemory _configData,
bytesmemory _immutables,
uint256 _maxLTV,
uint256 _liquidationFee,
uint256 _maturityDate,
uint256 _penaltyRate,
bool _isBorrowerWhitelistActive,
bool _isLenderWhitelistActive
) {
// Handle Immutables Configuration
{
(
address _circuitBreaker,
address _comptrollerAddress,
address _timeLockAddress,
address _fraxlendWhitelistAddress
) =abi.decode(_immutables, (address, address, address, address));
// Deployer contract
DEPLOYER_ADDRESS =msg.sender;
CIRCUIT_BREAKER_ADDRESS = _circuitBreaker;
COMPTROLLER_ADDRESS = _comptrollerAddress;
TIME_LOCK_ADDRESS = _timeLockAddress;
FRAXLEND_WHITELIST_ADDRESS = _fraxlendWhitelistAddress;
}
{
(
address _asset,
address _collateral,
address _oracleMultiply,
address _oracleDivide,
uint256 _oracleNormalization,
address _rateContract,
) =abi.decode(_configData, (address, address, address, address, uint256, address, bytes));
// Pair Settings
assetContract = IERC20(_asset);
collateralContract = IERC20(_collateral);
currentRateInfo.feeToProtocolRate = DEFAULT_PROTOCOL_FEE;
cleanLiquidationFee = _liquidationFee;
dirtyLiquidationFee = (_liquidationFee *90000) / LIQ_PRECISION; // 90% of clean feeif (_maxLTV >= LTV_PRECISION &&!_isBorrowerWhitelistActive) revert BorrowerWhitelistRequired();
maxLTV = _maxLTV;
// Oracle Settings
{
IFraxlendWhitelist _fraxlendWhitelist = IFraxlendWhitelist(FRAXLEND_WHITELIST_ADDRESS);
// Check that oracles are on the whitelistif (_oracleMultiply !=address(0) &&!_fraxlendWhitelist.oracleContractWhitelist(_oracleMultiply)) {
revert NotOnWhitelist(_oracleMultiply);
}
if (_oracleDivide !=address(0) &&!_fraxlendWhitelist.oracleContractWhitelist(_oracleDivide)) {
revert NotOnWhitelist(_oracleDivide);
}
// Write oracleData to storage
oracleMultiply = _oracleMultiply;
oracleDivide = _oracleDivide;
oracleNormalization = _oracleNormalization;
// Rate Settingsif (!_fraxlendWhitelist.rateContractWhitelist(_rateContract)) {
revert NotOnWhitelist(_rateContract);
}
}
rateContract = IRateCalculator(_rateContract);
}
// Set approved borrowers whitelist
borrowerWhitelistActive = _isBorrowerWhitelistActive;
// Set approved lenders whitelist active
lenderWhitelistActive = _isLenderWhitelistActive;
// Set maturity date & penalty interest rate
maturityDate = _maturityDate;
penaltyRate = _penaltyRate;
}
/// @notice The ```initialize``` function is called immediately after deployment/// @dev This function can only be called by the deployer/// @param _name The name of the contract/// @param _approvedBorrowers An array of approved borrower addresses/// @param _approvedLenders An array of approved lender addresses/// @param _rateInitCallData The configuration data for the Rate Calculator contractfunctioninitialize(stringcalldata _name,
address[] calldata _approvedBorrowers,
address[] calldata _approvedLenders,
bytescalldata _rateInitCallData
) external{
if (msg.sender!= DEPLOYER_ADDRESS) {
revert NotDeployer();
}
if (bytes(_name).length==0) {
revert NameEmpty();
}
if (bytes(nameOfContract).length!=0) {
revert AlreadyInitialized();
}
// Set name
nameOfContract = _name;
// Set approved borrowersfor (uint256 i =0; i < _approvedBorrowers.length; ++i) {
approvedBorrowers[_approvedBorrowers[i]] =true;
}
// Set approved lendersfor (uint256 i =0; i < _approvedLenders.length; ++i) {
approvedLenders[_approvedLenders[i]] =true;
}
// Reverts if init data is not valid
IRateCalculator(rateContract).requireValidInitData(_rateInitCallData);
// Set rate init Data
rateInitCallData = _rateInitCallData;
// Instantiate Interest
_addInterest();
// Instantiate Exchange Rate
_updateExchangeRate();
}
// ============================================================================================// Internal Helpers// ============================================================================================/// @notice The ```_totalAssetAvailable``` function returns the total balance of Asset Tokens in the contract/// @param _totalAsset VaultAccount struct which stores total amount and shares for assets/// @param _totalBorrow VaultAccount struct which stores total amount and shares for borrows/// @return The balance of Asset Tokens held by contractfunction_totalAssetAvailable(VaultAccount memory _totalAsset, VaultAccount memory _totalBorrow)
internalpurereturns (uint256)
{
return _totalAsset.amount - _totalBorrow.amount;
}
/// @notice The ```_isSolvent``` function determines if a given borrower is solvent given an exchange rate/// @param _borrower The borrower address to check/// @param _exchangeRate The exchange rate, i.e. the amount of collateral to buy 1e18 asset/// @return Whether borrower is solventfunction_isSolvent(address _borrower, uint256 _exchangeRate) internalviewreturns (bool) {
if (maxLTV ==0) returntrue;
uint256 _borrowerAmount = totalBorrow.toAmount(userBorrowShares[_borrower], true);
if (_borrowerAmount ==0) returntrue;
uint256 _collateralAmount = userCollateralBalance[_borrower];
if (_collateralAmount ==0) returnfalse;
uint256 _ltv = (((_borrowerAmount * _exchangeRate) / EXCHANGE_PRECISION) * LTV_PRECISION) / _collateralAmount;
return _ltv <= maxLTV;
}
/// @notice The ```_isPastMaturity``` function determines if the current block timestamp is past the maturityDate date/// @return Whether or not the debt is past maturityfunction_isPastMaturity() internalviewreturns (bool) {
return maturityDate !=0&&block.timestamp> maturityDate;
}
// ============================================================================================// Modifiers// ============================================================================================/// @notice Checks for solvency AFTER executing contract code/// @param _borrower The borrower whose solvency we will checkmodifierisSolvent(address _borrower) {
_;
if (!_isSolvent(_borrower, exchangeRateInfo.exchangeRate)) {
revert Insolvent(
totalBorrow.toAmount(userBorrowShares[_borrower], true),
userCollateralBalance[_borrower],
exchangeRateInfo.exchangeRate
);
}
}
/// @notice Checks if msg.sender is an approved BorrowermodifierapprovedBorrower() {
if (borrowerWhitelistActive &&!approvedBorrowers[msg.sender]) {
revert OnlyApprovedBorrowers();
}
_;
}
/// @notice Checks if msg.sender and _receiver are both an approved Lender/// @param _receiver An additional receiver address to checkmodifierapprovedLender(address _receiver) {
if (lenderWhitelistActive && (!approvedLenders[msg.sender] ||!approvedLenders[_receiver])) {
revert OnlyApprovedLenders();
}
_;
}
/// @notice Ensure function is not called when passed maturitymodifierisNotPastMaturity() {
if (_isPastMaturity()) {
revert PastMaturity();
}
_;
}
// ============================================================================================// Functions: Interest Accumulation and Adjustment// ============================================================================================/// @notice The ```AddInterest``` event is emitted when interest is accrued by borrowers/// @param _interestEarned The total interest accrued by all borrowers/// @param _rate The interest rate used to calculate accrued interest/// @param _deltaTime The time elapsed since last interest accrual/// @param _feesAmount The amount of fees paid to protocol/// @param _feesShare The amount of shares distributed to protocoleventAddInterest(uint256 _interestEarned,
uint256 _rate,
uint256 _deltaTime,
uint256 _feesAmount,
uint256 _feesShare
);
/// @notice The ```UpdateRate``` event is emitted when the interest rate is updated/// @param _ratePerSec The old interest rate (per second)/// @param _deltaTime The time elapsed since last update/// @param _utilizationRate The utilization of assets in the Pair/// @param _newRatePerSec The new interest rate (per second)eventUpdateRate(uint256 _ratePerSec, uint256 _deltaTime, uint256 _utilizationRate, uint256 _newRatePerSec);
/// @notice The ```addInterest``` function is a public implementation of _addInterest and allows 3rd parties to trigger interest accrual/// @return _interestEarned The amount of interest accrued by all borrowersfunctionaddInterest()
externalnonReentrantreturns (uint256 _interestEarned,
uint256 _feesAmount,
uint256 _feesShare,
uint64 _newRate
)
{
return _addInterest();
}
/// @notice The ```_addInterest``` function is invoked prior to every external function and is used to accrue interest and update interest rate/// @dev Can only called once per block/// @return _interestEarned The amount of interest accrued by all borrowersfunction_addInterest()
internalreturns (uint256 _interestEarned,
uint256 _feesAmount,
uint256 _feesShare,
uint64 _newRate
)
{
// Add interest only once per block
CurrentRateInfo memory _currentRateInfo = currentRateInfo;
if (_currentRateInfo.lastTimestamp ==block.timestamp) {
_newRate = _currentRateInfo.ratePerSec;
return (_interestEarned, _feesAmount, _feesShare, _newRate);
}
// Pull some data from storage to save gas
VaultAccount memory _totalAsset = totalAsset;
VaultAccount memory _totalBorrow = totalBorrow;
// If there are no borrows or contract is paused, no interest accrues and we reset interest rateif (_totalBorrow.shares ==0|| paused()) {
if (!paused()) {
_currentRateInfo.ratePerSec = DEFAULT_INT;
}
_currentRateInfo.lastTimestamp =uint64(block.timestamp);
_currentRateInfo.lastBlock =uint64(block.number);
// Effects: write to storage
currentRateInfo = _currentRateInfo;
} else {
// We know totalBorrow.shares > 0uint256 _deltaTime =block.timestamp- _currentRateInfo.lastTimestamp;
// NOTE: Violates Checks-Effects-Interactions pattern// Be sure to mark external version NONREENTRANT (even though rateContract is trusted)// Calc new rateuint256 _utilizationRate = (UTIL_PREC * _totalBorrow.amount) / _totalAsset.amount;
if (_isPastMaturity()) {
_newRate =uint64(penaltyRate);
} else {
bytesmemory _rateData =abi.encode(
_currentRateInfo.ratePerSec,
_deltaTime,
_utilizationRate,
block.number- _currentRateInfo.lastBlock
);
_newRate = IRateCalculator(rateContract).getNewRate(_rateData, rateInitCallData);
}
// Event must be here to use non-mutated valuesemit UpdateRate(_currentRateInfo.ratePerSec, _deltaTime, _utilizationRate, _newRate);
// Effects: bookkeeping
_currentRateInfo.ratePerSec = _newRate;
_currentRateInfo.lastTimestamp =uint64(block.timestamp);
_currentRateInfo.lastBlock =uint64(block.number);
// Calculate interest accrued
_interestEarned = (_deltaTime * _totalBorrow.amount * _currentRateInfo.ratePerSec) /1e18;
// Accumulate interest and fees, only if no overflow upon castingif (
_interestEarned + _totalBorrow.amount <=type(uint128).max&&
_interestEarned + _totalAsset.amount <=type(uint128).max
) {
_totalBorrow.amount +=uint128(_interestEarned);
_totalAsset.amount +=uint128(_interestEarned);
if (_currentRateInfo.feeToProtocolRate >0) {
_feesAmount = (_interestEarned * _currentRateInfo.feeToProtocolRate) / FEE_PRECISION;
_feesShare = (_feesAmount * _totalAsset.shares) / (_totalAsset.amount - _feesAmount);
// Effects: Give new shares to this contract, effectively diluting lenders an amount equal to the fees// We can safely cast because _feesShare < _feesAmount < interestEarned which is always less than uint128
_totalAsset.shares +=uint128(_feesShare);
// Effects: write to storage
_mint(address(this), _feesShare);
}
emit AddInterest(_interestEarned, _currentRateInfo.ratePerSec, _deltaTime, _feesAmount, _feesShare);
}
// Effects: write to storage
totalAsset = _totalAsset;
currentRateInfo = _currentRateInfo;
totalBorrow = _totalBorrow;
}
}
// ============================================================================================// Functions: ExchangeRate// ============================================================================================/// @notice The ```UpdateExchangeRate``` event is emitted when the Collateral:Asset exchange rate is updated/// @param _rate The new rate given as the amount of Collateral Token to buy 1e18 Asset TokeneventUpdateExchangeRate(uint256 _rate);
/// @notice The ```updateExchangeRate``` function is the external implementation of _updateExchangeRate./// @dev This function is invoked at most once per block as these queries can be expensive/// @return _exchangeRate The new exchange ratefunctionupdateExchangeRate() externalnonReentrantreturns (uint256 _exchangeRate) {
_exchangeRate = _updateExchangeRate();
}
/// @notice The ```_updateExchangeRate``` function retrieves the latest exchange rate. i.e how much collateral to buy 1e18 asset./// @dev This function is invoked at most once per block as these queries can be expensive/// @return _exchangeRate The new exchange ratefunction_updateExchangeRate() internalreturns (uint256 _exchangeRate) {
ExchangeRateInfo memory _exchangeRateInfo = exchangeRateInfo;
if (_exchangeRateInfo.lastTimestamp ==block.timestamp) {
return _exchangeRate = _exchangeRateInfo.exchangeRate;
}
uint256 _price =uint256(1e36);
if (oracleMultiply !=address(0)) {
(, int256 _answer, , , ) = AggregatorV3Interface(oracleMultiply).latestRoundData();
if (_answer <=0) {
revert OracleLTEZero(oracleMultiply);
}
_price = _price *uint256(_answer);
}
if (oracleDivide !=address(0)) {
(, int256 _answer, , , ) = AggregatorV3Interface(oracleDivide).latestRoundData();
if (_answer <=0) {
revert OracleLTEZero(oracleDivide);
}
_price = _price /uint256(_answer);
}
_exchangeRate = _price / oracleNormalization;
// write to storage, if no overflowif (_exchangeRate >type(uint224).max) revert PriceTooLarge();
_exchangeRateInfo.exchangeRate =uint224(_exchangeRate);
_exchangeRateInfo.lastTimestamp =uint32(block.timestamp);
exchangeRateInfo = _exchangeRateInfo;
emit UpdateExchangeRate(_exchangeRate);
}
// ============================================================================================// Functions: Lending// ============================================================================================/// @notice The ```Deposit``` event fires when a user deposits assets to the pair/// @param caller the msg.sender/// @param owner the account the fTokens are sent to/// @param assets the amount of assets deposited/// @param shares the number of fTokens mintedeventDeposit(addressindexed caller, addressindexed owner, uint256 assets, uint256 shares);
/// @notice The ```_deposit``` function is the internal implementation for lending assets/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling function/// @param _totalAsset An in memory VaultAccount struct representing the total amounts and shares for the Asset Token/// @param _amount The amount of Asset Token to be transferred/// @param _shares The amount of Asset Shares (fTokens) to be minted/// @param _receiver The address to receive the Asset Shares (fTokens)function_deposit(
VaultAccount memory _totalAsset,
uint128 _amount,
uint128 _shares,
address _receiver
) internal{
// Effects: bookkeeping
_totalAsset.amount += _amount;
_totalAsset.shares += _shares;
// Effects: write back to storage
_mint(_receiver, _shares);
totalAsset = _totalAsset;
// Interactions
assetContract.safeTransferFrom(msg.sender, address(this), _amount);
emit Deposit(msg.sender, _receiver, _amount, _shares);
}
/// @notice The ```deposit``` function allows a user to Lend Assets by specifying the amount of Asset Tokens to lend/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling function/// @param _amount The amount of Asset Token to transfer to Pair/// @param _receiver The address to receive the Asset Shares (fTokens)/// @return _sharesReceived The number of fTokens received for the depositfunctiondeposit(uint256 _amount, address _receiver)
externalnonReentrantisNotPastMaturitywhenNotPausedapprovedLender(_receiver)
returns (uint256 _sharesReceived)
{
_addInterest();
VaultAccount memory _totalAsset = totalAsset;
_sharesReceived = _totalAsset.toShares(_amount, false);
_deposit(_totalAsset, _amount.toUint128(), _sharesReceived.toUint128(), _receiver);
}
/// @notice The ```Withdraw``` event fires when a user redeems their fTokens for the underlying asset/// @param caller the msg.sender/// @param receiver The address to which the underlying asset will be transferred to/// @param owner The owner of the fTokens/// @param assets The assets transferred/// @param shares The number of fTokens burnedeventWithdraw(addressindexed caller,
addressindexed receiver,
addressindexed owner,
uint256 assets,
uint256 shares
);
/// @notice The ```_redeem``` function is an internal implementation which allows a Lender to pull their Asset Tokens out of the Pair/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling function/// @param _totalAsset An in-memory VaultAccount struct which holds the total amount of Asset Tokens and the total number of Asset Shares (fTokens)/// @param _amountToReturn The number of Asset Tokens to return/// @param _shares The number of Asset Shares (fTokens) to burn/// @param _receiver The address to which the Asset Tokens will be transferred/// @param _owner The owner of the Asset Shares (fTokens)function_redeem(
VaultAccount memory _totalAsset,
uint128 _amountToReturn,
uint128 _shares,
address _receiver,
address _owner
) internal{
if (msg.sender!= _owner) {
uint256 allowed = allowance(_owner, msg.sender);
// NOTE: This will revert on underflow ensuring that allowance > sharesif (allowed !=type(uint256).max) _approve(_owner, msg.sender, allowed - _shares);
}
// Check for sufficient withdraw liquidityuint256 _assetsAvailable = _totalAssetAvailable(_totalAsset, totalBorrow);
if (_assetsAvailable < _amountToReturn) {
revert InsufficientAssetsInContract(_assetsAvailable, _amountToReturn);
}
// Effects: bookkeeping
_totalAsset.amount -= _amountToReturn;
_totalAsset.shares -= _shares;
// Effects: write to storage
totalAsset = _totalAsset;
_burn(_owner, _shares);
// Interactions
assetContract.safeTransfer(_receiver, _amountToReturn);
emit Withdraw(msg.sender, _receiver, _owner, _amountToReturn, _shares);
}
/// @notice The ```redeem``` function allows the caller to redeem their Asset Shares for Asset Tokens/// @param _shares The number of Asset Shares (fTokens) to burn for Asset Tokens/// @param _receiver The address to which the Asset Tokens will be transferred/// @param _owner The owner of the Asset Shares (fTokens)/// @return _amountToReturn The amount of Asset Tokens to be transferredfunctionredeem(uint256 _shares,
address _receiver,
address _owner
) externalnonReentrantreturns (uint256 _amountToReturn) {
_addInterest();
VaultAccount memory _totalAsset = totalAsset;
_amountToReturn = _totalAsset.toAmount(_shares, false);
_redeem(_totalAsset, _amountToReturn.toUint128(), _shares.toUint128(), _receiver, _owner);
}
// ============================================================================================// Functions: Borrowing// ============================================================================================/// @notice The ```BorrowAsset``` event is emitted when a borrower increases their position/// @param _borrower The borrower whose account was debited/// @param _receiver The address to which the Asset Tokens were transferred/// @param _borrowAmount The amount of Asset Tokens transferred/// @param _sharesAdded The number of Borrow Shares the borrower was debitedeventBorrowAsset(addressindexed _borrower,
addressindexed _receiver,
uint256 _borrowAmount,
uint256 _sharesAdded
);
/// @notice The ```_borrowAsset``` function is the internal implementation for borrowing assets/// @param _borrowAmount The amount of the Asset Token to borrow/// @param _receiver The address to receive the Asset Tokens/// @return _sharesAdded The amount of borrow shares the msg.sender will be debitedfunction_borrowAsset(uint128 _borrowAmount, address _receiver) internalreturns (uint256 _sharesAdded) {
VaultAccount memory _totalBorrow = totalBorrow;
// Check available capitaluint256 _assetsAvailable = _totalAssetAvailable(totalAsset, _totalBorrow);
if (_assetsAvailable < _borrowAmount) {
revert InsufficientAssetsInContract(_assetsAvailable, _borrowAmount);
}
// Effects: Bookkeeping to add shares & amounts to total Borrow accounting
_sharesAdded = _totalBorrow.toShares(_borrowAmount, true);
_totalBorrow.amount += _borrowAmount;
_totalBorrow.shares +=uint128(_sharesAdded);
// NOTE: we can safely cast here because shares are always less than amount and _borrowAmount is uint128// Effects: write back to storage
totalBorrow = _totalBorrow;
userBorrowShares[msg.sender] += _sharesAdded;
// Interactionsif (_receiver !=address(this)) {
assetContract.safeTransfer(_receiver, _borrowAmount);
}
emit BorrowAsset(msg.sender, _receiver, _borrowAmount, _sharesAdded);
}
/// @notice The ```borrowAsset``` function allows a user to open/increase a borrow position/// @dev Borrower must call ```ERC20.approve``` on the Collateral Token contract if applicable/// @param _borrowAmount The amount of Asset Token to borrow/// @param _collateralAmount The amount of Collateral Token to transfer to Pair/// @param _receiver The address which will receive the Asset Tokens/// @return _shares The number of borrow Shares the msg.sender will be debitedfunctionborrowAsset(uint256 _borrowAmount,
uint256 _collateralAmount,
address _receiver
)
externalisNotPastMaturitywhenNotPausednonReentrantisSolvent(msg.sender)
approvedBorrowerreturns (uint256 _shares)
{
_addInterest();
_updateExchangeRate();
if (_collateralAmount >0) {
_addCollateral(msg.sender, _collateralAmount, msg.sender);
}
_shares = _borrowAsset(_borrowAmount.toUint128(), _receiver);
}
eventAddCollateral(addressindexed _sender, addressindexed _borrower, uint256 _collateralAmount);
/// @notice The ```_addCollateral``` function is an internal implementation for adding collateral to a borrowers position/// @param _sender The source of funds for the new collateral/// @param _collateralAmount The amount of Collateral Token to be transferred/// @param _borrower The borrower account for which the collateral should be creditedfunction_addCollateral(address _sender,
uint256 _collateralAmount,
address _borrower
) internal{
// Effects: write to state
userCollateralBalance[_borrower] += _collateralAmount;
totalCollateral += _collateralAmount;
// Interactionsif (_sender !=address(this)) {
collateralContract.safeTransferFrom(_sender, address(this), _collateralAmount);
}
emit AddCollateral(_sender, _borrower, _collateralAmount);
}
/// @notice The ```addCollateral``` function allows the caller to add Collateral Token to a borrowers position/// @dev msg.sender must call ERC20.approve() on the Collateral Token contract prior to invocation/// @param _collateralAmount The amount of Collateral Token to be added to borrower's position/// @param _borrower The account to be creditedfunctionaddCollateral(uint256 _collateralAmount, address _borrower) externalnonReentrantisNotPastMaturity{
_addInterest();
_addCollateral(msg.sender, _collateralAmount, _borrower);
}
/// @notice The ```RemoveCollateral``` event is emitted when collateral is removed from a borrower's position/// @param _sender The account from which funds are transferred/// @param _collateralAmount The amount of Collateral Token to be transferred/// @param _receiver The address to which Collateral Tokens will be transferredeventRemoveCollateral(addressindexed _sender,
uint256 _collateralAmount,
addressindexed _receiver,
addressindexed _borrower
);
/// @notice The ```_removeCollateral``` function is the internal implementation for removing collateral from a borrower's position/// @param _collateralAmount The amount of Collateral Token to remove from the borrower's position/// @param _receiver The address to receive the Collateral Token transferred/// @param _borrower The borrower whose account will be debited the Collateral amountfunction_removeCollateral(uint256 _collateralAmount,
address _receiver,
address _borrower
) internal{
// Effects: write to state// Following line will revert on underflow if _collateralAmount > userCollateralBalance
userCollateralBalance[_borrower] -= _collateralAmount;
// Following line will revert on underflow if totalCollateral < _collateralAmount
totalCollateral -= _collateralAmount;
// Interactionsif (_receiver !=address(this)) {
collateralContract.safeTransfer(_receiver, _collateralAmount);
}
emit RemoveCollateral(msg.sender, _collateralAmount, _receiver, _borrower);
}
/// @notice The ```removeCollateral``` function is used to remove collateral from msg.sender's borrow position/// @dev msg.sender must be solvent after invocation or transaction will revert/// @param _collateralAmount The amount of Collateral Token to transfer/// @param _receiver The address to receive the transferred fundsfunctionremoveCollateral(uint256 _collateralAmount, address _receiver)
externalnonReentrantisSolvent(msg.sender)
{
_addInterest();
// Note: exchange rate is irrelevant when borrower has no debt sharesif (userBorrowShares[msg.sender] >0) {
_updateExchangeRate();
}
_removeCollateral(_collateralAmount, _receiver, msg.sender);
}
/// @notice The ```RepayAsset``` event is emitted whenever a debt position is repaid/// @param _payer The address paying for the repayment/// @param _borrower The borrower whose account will be credited/// @param _amountToRepay The amount of Asset token to be transferred/// @param _shares The amount of Borrow Shares which will be debited from the borrower after repaymenteventRepayAsset(addressindexed _payer, addressindexed _borrower, uint256 _amountToRepay, uint256 _shares);
/// @notice The ```_repayAsset``` function is the internal implementation for repaying a borrow position/// @dev The payer must have called ERC20.approve() on the Asset Token contract prior to invocation/// @param _totalBorrow An in memory copy of the totalBorrow VaultAccount struct/// @param _amountToRepay The amount of Asset Token to transfer/// @param _shares The number of Borrow Shares the sender is repaying/// @param _payer The address from which funds will be transferred/// @param _borrower The borrower account which will be creditedfunction_repayAsset(
VaultAccount memory _totalBorrow,
uint128 _amountToRepay,
uint128 _shares,
address _payer,
address _borrower
) internal{
// Effects: Bookkeeping
_totalBorrow.amount -= _amountToRepay;
_totalBorrow.shares -= _shares;
// Effects: write to state
userBorrowShares[_borrower] -= _shares;
totalBorrow = _totalBorrow;
// Interactionsif (_payer !=address(this)) {
assetContract.safeTransferFrom(_payer, address(this), _amountToRepay);
}
emit RepayAsset(_payer, _borrower, _amountToRepay, _shares);
}
/// @notice The ```repayAsset``` function allows the caller to pay down the debt for a given borrower./// @dev Caller must first invoke ```ERC20.approve()``` for the Asset Token contract/// @param _shares The number of Borrow Shares which will be repaid by the call/// @param _borrower The account for which the debt will be reduced/// @return _amountToRepay The amount of Asset Tokens which were transferred in order to repay the Borrow SharesfunctionrepayAsset(uint256 _shares, address _borrower) externalnonReentrantreturns (uint256 _amountToRepay) {
_addInterest();
VaultAccount memory _totalBorrow = totalBorrow;
_amountToRepay = _totalBorrow.toAmount(_shares, true);
_repayAsset(_totalBorrow, _amountToRepay.toUint128(), _shares.toUint128(), msg.sender, _borrower);
}
// ============================================================================================// Functions: Liquidations// ============================================================================================/// @notice The ```Liquidate``` event is emitted when a liquidation occurs/// @param _borrower The borrower account for which the liquidation occurred/// @param _collateralForLiquidator The amount of Collateral Token transferred to the liquidator/// @param _sharesToLiquidate The number of Borrow Shares the liquidator repaid on behalf of the borrower/// @param _sharesToAdjust The number of Borrow Shares that were adjusted on liabilities and assets (a writeoff)eventLiquidate(addressindexed _borrower,
uint256 _collateralForLiquidator,
uint256 _sharesToLiquidate,
uint256 _amountLiquidatorToRepay,
uint256 _sharesToAdjust,
uint256 _amountToAdjust
);
/// @notice The ```liquidate``` function allows a third party to repay a borrower's debt if they have become insolvent/// @dev Caller must invoke ```ERC20.approve``` on the Asset Token contract prior to calling ```Liquidate()```/// @param _sharesToLiquidate The number of Borrow Shares repaid by the liquidator/// @param _deadline The timestamp after which tx will revert/// @param _borrower The account for which the repayment is credited and from whom collateral will be taken/// @return _collateralForLiquidator The amount of Collateral Token transferred to the liquidatorfunctionliquidate(uint128 _sharesToLiquidate,
uint256 _deadline,
address _borrower
) externalwhenNotPausednonReentrantapprovedLender(msg.sender) returns (uint256 _collateralForLiquidator) {
if (block.timestamp> _deadline) revert PastDeadline(block.timestamp, _deadline);
_addInterest();
uint256 _exchangeRate = _updateExchangeRate();
if (_isSolvent(_borrower, _exchangeRate)) {
revert BorrowerSolvent();
}
// Read from state
VaultAccount memory _totalBorrow = totalBorrow;
uint256 _userCollateralBalance = userCollateralBalance[_borrower];
uint128 _borrowerShares = userBorrowShares[_borrower].toUint128();
// Prevent stack-too-deepint256 _leftoverCollateral;
{
// Checks & Calculations// Determine the liquidation amount in collateral units (i.e. how much debt is liquidator going to repay)uint256 _liquidationAmountInCollateralUnits = ((_totalBorrow.toAmount(_sharesToLiquidate, false) *
_exchangeRate) / EXCHANGE_PRECISION);
// We first optimistically calculate the amount of collateral to give the liquidator based on the higher clean liquidation fee// This fee only applies if the liquidator does a full liquidationuint256 _optimisticCollateralForLiquidator = (_liquidationAmountInCollateralUnits *
(LIQ_PRECISION + cleanLiquidationFee)) / LIQ_PRECISION;
// Because interest accrues every block, _liquidationAmountInCollateralUnits from a few lines up is an ever increasing value// This means that leftoverCollateral can occasionally go negative by a few hundred wei (cleanLiqFee premium covers this for liquidator)
_leftoverCollateral = (_userCollateralBalance.toInt256() - _optimisticCollateralForLiquidator.toInt256());
// If cleanLiquidation fee results in no leftover collateral, give liquidator all the collateral// This will only be true when there liquidator is cleaning out the position
_collateralForLiquidator = _leftoverCollateral <=0
? _userCollateralBalance
: (_liquidationAmountInCollateralUnits * (LIQ_PRECISION + dirtyLiquidationFee)) / LIQ_PRECISION;
}
// Calculated here for use during repayment, grouped with other calcs before effects startuint128 _amountLiquidatorToRepay = (_totalBorrow.toAmount(_sharesToLiquidate, true)).toUint128();
// Determine if and how much debt to adjustuint128 _sharesToAdjust;
{
uint128 _amountToAdjust;
if (_leftoverCollateral <=0) {
// Determine if we need to adjust any shares
_sharesToAdjust = _borrowerShares - _sharesToLiquidate;
if (_sharesToAdjust >0) {
// Write off bad debt
_amountToAdjust = (_totalBorrow.toAmount(_sharesToAdjust, false)).toUint128();
// Note: Ensure this memory struct will be passed to _repayAsset for write to state
_totalBorrow.amount -= _amountToAdjust;
// Effects: write to state
totalAsset.amount -= _amountToAdjust;
}
}
emit Liquidate(
_borrower,
_collateralForLiquidator,
_sharesToLiquidate,
_amountLiquidatorToRepay,
_sharesToAdjust,
_amountToAdjust
);
}
// Effects & Interactions// NOTE: reverts if _shares > userBorrowShares
_repayAsset(
_totalBorrow,
_amountLiquidatorToRepay,
_sharesToLiquidate + _sharesToAdjust,
msg.sender,
_borrower
); // liquidator repays shares on behalf of borrower// NOTE: reverts if _collateralForLiquidator > userCollateralBalance// Collateral is removed on behalf of borrower and sent to liquidator// NOTE: reverts if _collateralForLiquidator > userCollateralBalance
_removeCollateral(_collateralForLiquidator, msg.sender, _borrower);
}
// ============================================================================================// Functions: Leverage// ============================================================================================/// @notice The ```LeveragedPosition``` event is emitted when a borrower takes out a new leveraged position/// @param _borrower The account for which the debt is debited/// @param _swapperAddress The address of the swapper which conforms the FraxSwap interface/// @param _borrowAmount The amount of Asset Token to be borrowed to be borrowed/// @param _borrowShares The number of Borrow Shares the borrower is credited/// @param _initialCollateralAmount The amount of initial Collateral Tokens supplied by the borrower/// @param _amountCollateralOut The amount of Collateral Token which was received for the Asset TokenseventLeveragedPosition(addressindexed _borrower,
address _swapperAddress,
uint256 _borrowAmount,
uint256 _borrowShares,
uint256 _initialCollateralAmount,
uint256 _amountCollateralOut
);
/// @notice The ```leveragedPosition``` function allows a user to enter a leveraged borrow position with minimal upfront Collateral/// @dev Caller must invoke ```ERC20.approve()``` on the Collateral Token contract prior to calling function/// @param _swapperAddress The address of the whitelisted swapper to use to swap borrowed Asset Tokens for Collateral Tokens/// @param _borrowAmount The amount of Asset Tokens borrowed/// @param _initialCollateralAmount The initial amount of Collateral Tokens supplied by the borrower/// @param _amountCollateralOutMin The minimum amount of Collateral Tokens to be received in exchange for the borrowed Asset Tokens/// @param _path An array containing the addresses of ERC20 tokens to swap. Adheres to UniV2 style path params./// @return _totalCollateralBalance The total amount of Collateral Tokens added to a users account (initial + swap)functionleveragedPosition(address _swapperAddress,
uint256 _borrowAmount,
uint256 _initialCollateralAmount,
uint256 _amountCollateralOutMin,
address[] memory _path
)
externalisNotPastMaturitynonReentrantwhenNotPausedapprovedBorrowerisSolvent(msg.sender)
returns (uint256 _totalCollateralBalance)
{
_addInterest();
_updateExchangeRate();
IERC20 _assetContract = assetContract;
IERC20 _collateralContract = collateralContract;
if (!swappers[_swapperAddress]) {
revert BadSwapper();
}
if (_path[0] !=address(_assetContract)) {
revert InvalidPath(address(_assetContract), _path[0]);
}
if (_path[_path.length-1] !=address(_collateralContract)) {
revert InvalidPath(address(_collateralContract), _path[_path.length-1]);
}
// Add initial collateralif (_initialCollateralAmount >0) {
_addCollateral(msg.sender, _initialCollateralAmount, msg.sender);
}
// Debit borrowers account// setting recipient to address(this) means no transfer will happenuint256 _borrowShares = _borrowAsset(_borrowAmount.toUint128(), address(this));
// Interactions
_assetContract.approve(_swapperAddress, _borrowAmount);
// Even though swappers are trusted, we verify the balance before and after swapuint256 _initialCollateralBalance = _collateralContract.balanceOf(address(this));
ISwapper(_swapperAddress).swapExactTokensForTokens(
_borrowAmount,
_amountCollateralOutMin,
_path,
address(this),
block.timestamp
);
uint256 _finalCollateralBalance = _collateralContract.balanceOf(address(this));
// Note: VIOLATES CHECKS-EFFECTS-INTERACTION pattern, make sure function is NONREENTRANT// Effects: bookkeeping & write to stateuint256 _amountCollateralOut = _finalCollateralBalance - _initialCollateralBalance;
if (_amountCollateralOut < _amountCollateralOutMin) {
revert SlippageTooHigh(_amountCollateralOutMin, _amountCollateralOut);
}
// address(this) as _sender means no transfer occurs as the pair has already received the collateral during swap
_addCollateral(address(this), _amountCollateralOut, msg.sender);
_totalCollateralBalance = _initialCollateralAmount + _amountCollateralOut;
emit LeveragedPosition(
msg.sender,
_swapperAddress,
_borrowAmount,
_borrowShares,
_initialCollateralAmount,
_amountCollateralOut
);
}
/// @notice The ```RepayAssetWithCollateral``` event is emitted whenever ```repayAssetWithCollateral()``` is invoked/// @param _borrower The borrower account for which the repayment is taking place/// @param _swapperAddress The address of the whitelisted swapper to use for token swaps/// @param _collateralToSwap The amount of Collateral Token to swap and use for repayment/// @param _amountAssetOut The amount of Asset Token which was repaid/// @param _sharesRepaid The number of Borrow Shares which were repaideventRepayAssetWithCollateral(addressindexed _borrower,
address _swapperAddress,
uint256 _collateralToSwap,
uint256 _amountAssetOut,
uint256 _sharesRepaid
);
/// @notice The ```repayAssetWithCollateral``` function allows a borrower to repay their debt using existing collateral in contract/// @param _swapperAddress The address of the whitelisted swapper to use for token swaps/// @param _collateralToSwap The amount of Collateral Tokens to swap for Asset Tokens/// @param _amountAssetOutMin The minimum amount of Asset Tokens to receive during the swap/// @param _path An array containing the addresses of ERC20 tokens to swap. Adheres to UniV2 style path params./// @return _amountAssetOut The amount of Asset Tokens received for the Collateral Tokens, the amount the borrowers account was creditedfunctionrepayAssetWithCollateral(address _swapperAddress,
uint256 _collateralToSwap,
uint256 _amountAssetOutMin,
address[] calldata _path
) externalnonReentrantisSolvent(msg.sender) returns (uint256 _amountAssetOut) {
_addInterest();
_updateExchangeRate();
IERC20 _assetContract = assetContract;
IERC20 _collateralContract = collateralContract;
if (!swappers[_swapperAddress]) {
revert BadSwapper();
}
if (_path[0] !=address(_collateralContract)) {
revert InvalidPath(address(_collateralContract), _path[0]);
}
if (_path[_path.length-1] !=address(_assetContract)) {
revert InvalidPath(address(_assetContract), _path[_path.length-1]);
}
// Effects: bookkeeping & write to state// Debit users collateral balance in preparation for swap, setting _recipient to address(this) means no transfer occurs
_removeCollateral(_collateralToSwap, address(this), msg.sender);
// Interactions
_collateralContract.approve(_swapperAddress, _collateralToSwap);
// Even though swappers are trusted, we verify the balance before and after swapuint256 _initialAssetBalance = _assetContract.balanceOf(address(this));
ISwapper(_swapperAddress).swapExactTokensForTokens(
_collateralToSwap,
_amountAssetOutMin,
_path,
address(this),
block.timestamp
);
uint256 _finalAssetBalance = _assetContract.balanceOf(address(this));
// Note: VIOLATES CHECKS-EFFECTS-INTERACTION pattern, make sure function is NONREENTRANT// Effects: bookkeeping
_amountAssetOut = _finalAssetBalance - _initialAssetBalance;
if (_amountAssetOut < _amountAssetOutMin) {
revert SlippageTooHigh(_amountAssetOutMin, _amountAssetOut);
}
VaultAccount memory _totalBorrow = totalBorrow;
uint256 _sharesToRepay = _totalBorrow.toShares(_amountAssetOut, false);
// Effects: write to state// Note: setting _payer to address(this) means no actual transfer will occur. Contract already has funds
_repayAsset(_totalBorrow, _amountAssetOut.toUint128(), _sharesToRepay.toUint128(), address(this), msg.sender);
emit RepayAssetWithCollateral(msg.sender, _swapperAddress, _collateralToSwap, _amountAssetOut, _sharesToRepay);
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/interfaceIERC20MetadataisIERC20{
/**
* @dev Returns the name of the token.
*/functionname() externalviewreturns (stringmemory);
/**
* @dev Returns the symbol of the token.
*/functionsymbol() externalviewreturns (stringmemory);
/**
* @dev Returns the decimals places of the token.
*/functiondecimals() externalviewreturns (uint8);
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)pragmasolidity ^0.8.0;import"../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/abstractcontractOwnableisContext{
addressprivate _owner;
eventOwnershipTransferred(addressindexed previousOwner, addressindexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/modifieronlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/functionrenounceOwnership() publicvirtualonlyOwner{
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/functiontransferOwnership(address newOwner) publicvirtualonlyOwner{
require(newOwner !=address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/function_transferOwnership(address newOwner) internalvirtual{
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Contract Source Code
File 15 of 19: Pausable.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)pragmasolidity ^0.8.0;import"../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/abstractcontractPausableisContext{
/**
* @dev Emitted when the pause is triggered by `account`.
*/eventPaused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/eventUnpaused(address account);
boolprivate _paused;
/**
* @dev Initializes the contract in unpaused state.
*/constructor() {
_paused =false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/functionpaused() publicviewvirtualreturns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/modifierwhenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/modifierwhenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/function_pause() internalvirtualwhenNotPaused{
_paused =true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/function_unpause() internalvirtualwhenPaused{
_paused =false;
emit Unpaused(_msgSender());
}
}
Contract Source Code
File 16 of 19: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)pragmasolidity ^0.8.0;/**
* @dev 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.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/abstractcontractReentrancyGuard{
// Booleans are more expensive than uint256 or any type that takes up a full// word because each write operation emits an extra SLOAD to first read the// slot's contents, replace the bits taken up by the boolean, and then write// back. This is the compiler's defense against contract upgrades and// pointer aliasing, and it cannot be disabled.// The values being 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 percentage 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.uint256privateconstant _NOT_ENTERED =1;
uint256privateconstant _ENTERED =2;
uint256private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* 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 making it call a
* `private` function that does the actual work.
*/modifiernonReentrant() {
// On the first call to nonReentrant, _notEntered will be truerequire(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
Contract Source Code
File 17 of 19: SafeCast.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)pragmasolidity ^0.8.0;/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/librarySafeCast{
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/functiontoUint224(uint256 value) internalpurereturns (uint224) {
require(value <=type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
returnuint224(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/functiontoUint128(uint256 value) internalpurereturns (uint128) {
require(value <=type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
returnuint128(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/functiontoUint96(uint256 value) internalpurereturns (uint96) {
require(value <=type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
returnuint96(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/functiontoUint64(uint256 value) internalpurereturns (uint64) {
require(value <=type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
returnuint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/functiontoUint32(uint256 value) internalpurereturns (uint32) {
require(value <=type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
returnuint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/functiontoUint16(uint256 value) internalpurereturns (uint16) {
require(value <=type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
returnuint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/functiontoUint8(uint256 value) internalpurereturns (uint8) {
require(value <=type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
returnuint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/functiontoUint256(int256 value) internalpurereturns (uint256) {
require(value >=0, "SafeCast: value must be positive");
returnuint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/functiontoInt128(int256 value) internalpurereturns (int128) {
require(value >=type(int128).min&& value <=type(int128).max, "SafeCast: value doesn't fit in 128 bits");
returnint128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/functiontoInt64(int256 value) internalpurereturns (int64) {
require(value >=type(int64).min&& value <=type(int64).max, "SafeCast: value doesn't fit in 64 bits");
returnint64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/functiontoInt32(int256 value) internalpurereturns (int32) {
require(value >=type(int32).min&& value <=type(int32).max, "SafeCast: value doesn't fit in 32 bits");
returnint32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/functiontoInt16(int256 value) internalpurereturns (int16) {
require(value >=type(int16).min&& value <=type(int16).max, "SafeCast: value doesn't fit in 16 bits");
returnint16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/functiontoInt8(int256 value) internalpurereturns (int8) {
require(value >=type(int8).min&& value <=type(int8).max, "SafeCast: value doesn't fit in 8 bits");
returnint8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/functiontoInt256(uint256 value) internalpurereturns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positiverequire(value <=uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
returnint256(value);
}
}
Contract Source Code
File 18 of 19: SafeERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
import"../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers 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 IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/librarySafeERC20{
usingAddressforaddress;
functionsafeTransfer(
IERC20 token,
address to,
uint256 value
) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
functionsafeTransferFrom(
IERC20 token,
addressfrom,
address to,
uint256 value
) internal{
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/functionsafeApprove(
IERC20 token,
address spender,
uint256 value
) internal{
// safeApprove should only be called when setting an initial allowance,// or when resetting it to zero. To increase and decrease it, use// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'require(
(value ==0) || (token.allowance(address(this), spender) ==0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
functionsafeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal{
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
functionsafeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal{
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @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 must not 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(IERC20 token, bytesmemory 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.bytesmemory returndata =address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length>0) {
// Return data is optionalrequire(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
Contract Source Code
File 19 of 19: VaultAccount.sol
// SPDX-License-Identifier: ISCpragmasolidity ^0.8.16;structVaultAccount {
uint128 amount; // Total amount, analogous to market capuint128 shares; // Total shares, analogous to shares outstanding
}
/// @title VaultAccount Library/// @author Drake Evans (Frax Finance) github.com/drakeevans, modified from work by @Boring_Crypto github.com/boring_crypto/// @notice Provides a library for use with the VaultAccount struct, provides convenient math implementations/// @dev Uses uint128 to save on storagelibraryVaultAccountingLibrary{
/// @notice Calculates the shares value in relationship to `amount` and `total`/// @dev Given an amount, return the appropriate number of sharesfunctiontoShares(
VaultAccount memory total,
uint256 amount,
bool roundUp
) internalpurereturns (uint256 shares) {
if (total.amount ==0) {
shares = amount;
} else {
shares = (amount * total.shares) / total.amount;
if (roundUp && (shares * total.amount) / total.shares < amount) {
shares = shares +1;
}
}
}
/// @notice Calculates the amount value in relationship to `shares` and `total`/// @dev Given a number of shares, returns the appropriate amountfunctiontoAmount(
VaultAccount memory total,
uint256 shares,
bool roundUp
) internalpurereturns (uint256 amount) {
if (total.shares ==0) {
amount = shares;
} else {
amount = (shares * total.amount) / total.shares;
if (roundUp && (amount * total.shares) / total.amount < shares) {
amount = amount +1;
}
}
}
}