// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {IACL} from "@gearbox-protocol/core-v2/contracts/interfaces/IACL.sol";
import {
CallerNotControllerException,
CallerNotPausableAdminException,
CallerNotUnpausableAdminException
} from "../interfaces/IExceptions.sol";
import {ACLTrait} from "./ACLTrait.sol";
import {ReentrancyGuardTrait} from "./ReentrancyGuardTrait.sol";
/// @title ACL non-reentrant trait
/// @notice Extended version of `ACLTrait` that implements pausable functionality,
/// reentrancy protection and external controller role
abstract contract ACLNonReentrantTrait is ACLTrait, Pausable, ReentrancyGuardTrait {
/// @notice Emitted when new external controller is set
event NewController(address indexed newController);
/// @notice External controller address
address public controller;
/// @dev Ensures that function caller is external controller or configurator
modifier controllerOnly() {
_ensureCallerIsControllerOrConfigurator();
_;
}
/// @dev Reverts if the caller is not controller or configurator
/// @dev Used to cut contract size on modifiers
function _ensureCallerIsControllerOrConfigurator() internal view {
if (msg.sender != controller && !_isConfigurator({account: msg.sender})) {
revert CallerNotControllerException();
}
}
/// @dev Ensures that function caller has pausable admin role
modifier pausableAdminsOnly() {
_ensureCallerIsPausableAdmin();
_;
}
/// @dev Reverts if the caller is not pausable admin
/// @dev Used to cut contract size on modifiers
function _ensureCallerIsPausableAdmin() internal view {
if (!_isPausableAdmin({account: msg.sender})) {
revert CallerNotPausableAdminException();
}
}
/// @dev Ensures that function caller has unpausable admin role
modifier unpausableAdminsOnly() {
_ensureCallerIsUnpausableAdmin();
_;
}
/// @dev Reverts if the caller is not unpausable admin
/// @dev Used to cut contract size on modifiers
function _ensureCallerIsUnpausableAdmin() internal view {
if (!_isUnpausableAdmin({account: msg.sender})) {
revert CallerNotUnpausableAdminException();
}
}
/// @notice Constructor
/// @param addressProvider Address provider contract address
constructor(address addressProvider) ACLTrait(addressProvider) {
controller = IACL(acl).owner();
}
/// @notice Pauses contract, can only be called by an account with pausable admin role
function pause() external virtual pausableAdminsOnly {
_pause();
}
/// @notice Unpauses contract, can only be called by an account with unpausable admin role
function unpause() external virtual unpausableAdminsOnly {
_unpause();
}
/// @notice Sets new external controller, can only be called by configurator
function setController(address newController) external configuratorOnly {
if (controller == newController) return;
controller = newController;
emit NewController(newController);
}
/// @dev Checks whether given account has pausable admin role
function _isPausableAdmin(address account) internal view returns (bool) {
return IACL(acl).isPausableAdmin(account);
}
/// @dev Checks whether given account has unpausable admin role
function _isUnpausableAdmin(address account) internal view returns (bool) {
return IACL(acl).isUnpausableAdmin(account);
}
}
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IACL} from "@gearbox-protocol/core-v2/contracts/interfaces/IACL.sol";
import {AP_ACL, IAddressProviderV3, NO_VERSION_CONTROL} from "../interfaces/IAddressProviderV3.sol";
import {CallerNotConfiguratorException} from "../interfaces/IExceptions.sol";
import {SanityCheckTrait} from "./SanityCheckTrait.sol";
/// @title ACL trait
/// @notice Utility class for ACL (access-control list) consumers
abstract contract ACLTrait is SanityCheckTrait {
/// @notice ACL contract address
address public immutable acl;
/// @notice Constructor
/// @param addressProvider Address provider contract address
constructor(address addressProvider) nonZeroAddress(addressProvider) {
acl = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_ACL, NO_VERSION_CONTROL);
}
/// @dev Ensures that function caller has configurator role
modifier configuratorOnly() {
_ensureCallerIsConfigurator();
_;
}
/// @dev Reverts if the caller is not the configurator
/// @dev Used to cut contract size on modifiers
function _ensureCallerIsConfigurator() internal view {
if (!_isConfigurator({account: msg.sender})) {
revert CallerNotConfiguratorException();
}
}
/// @dev Checks whether given account has configurator role
function _isConfigurator(address account) internal view returns (bool) {
return IACL(acl).isConfigurator(account);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [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.
* ====
*/
function isContract(address account) internal view returns (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://consensys.net/diligence/blog/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.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable 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._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "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._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
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._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
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._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
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._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
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._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;
struct Balance {
address token;
uint256 balance;
}
library BalanceOps {
error UnknownToken(address);
function copyBalance(Balance memory b)
internal
pure
returns (Balance memory)
{
return Balance({ token: b.token, balance: b.balance });
}
function addBalance(
Balance[] memory b,
address token,
uint256 amount
) internal pure {
b[getIndex(b, token)].balance += amount;
}
function subBalance(
Balance[] memory b,
address token,
uint256 amount
) internal pure {
b[getIndex(b, token)].balance -= amount;
}
function getBalance(Balance[] memory b, address token)
internal
pure
returns (uint256 amount)
{
return b[getIndex(b, token)].balance;
}
function setBalance(
Balance[] memory b,
address token,
uint256 amount
) internal pure {
b[getIndex(b, token)].balance = amount;
}
function getIndex(Balance[] memory b, address token)
internal
pure
returns (uint256 index)
{
for (uint256 i; i < b.length; ) {
if (b[i].token == token) {
return i;
}
unchecked {
++i;
}
}
revert UnknownToken(token);
}
function copy(Balance[] memory b, uint256 len)
internal
pure
returns (Balance[] memory res)
{
res = new Balance[](len);
for (uint256 i; i < len; ) {
res[i] = copyBalance(b[i]);
unchecked {
++i;
}
}
}
function clone(Balance[] memory b)
internal
pure
returns (Balance[] memory)
{
return copy(b, b.length);
}
function getModifiedAfterSwap(
Balance[] memory b,
address tokenFrom,
uint256 amountFrom,
address tokenTo,
uint256 amountTo
) internal pure returns (Balance[] memory res) {
res = copy(b, b.length);
setBalance(res, tokenFrom, getBalance(b, tokenFrom) - amountFrom);
setBalance(res, tokenTo, getBalance(b, tokenTo) + amountTo);
}
}
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {SafeERC20} from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol";
import {BitMask} from "./BitMask.sol";
import {Balance} from "@gearbox-protocol/core-v2/contracts/libraries/Balances.sol";
struct BalanceWithMask {
address token;
uint256 tokenMask;
uint256 balance;
}
struct BalanceDelta {
address token;
int256 amount;
}
enum Comparison {
GREATER,
LESS
}
/// @title Balances logic library
/// @notice Implements functions for before-and-after balance comparisons
library BalancesLogic {
using BitMask for uint256;
using SafeCast for int256;
using SafeCast for uint256;
using SafeERC20 for IERC20;
/// @dev Compares current `token` balance with `value`
/// @param token Token to check balance for
/// @param value Value to compare current token balance with
/// @param comparison Whether current balance must be greater/less than or equal to `value`
function checkBalance(address creditAccount, address token, uint256 value, Comparison comparison)
internal
view
returns (bool)
{
uint256 current = IERC20(token).safeBalanceOf(creditAccount);
return (comparison == Comparison.GREATER && current >= value)
|| (comparison == Comparison.LESS && current <= value); // U:[BLL-1]
}
/// @dev Returns an array of expected token balances after operations
/// @param creditAccount Credit account to compute balances for
/// @param deltas Array of expected token balance changes
function storeBalances(address creditAccount, BalanceDelta[] memory deltas)
internal
view
returns (Balance[] memory balances)
{
uint256 len = deltas.length;
balances = new Balance[](len); // U:[BLL-2]
for (uint256 i = 0; i < len;) {
int256 balance = IERC20(deltas[i].token).safeBalanceOf(creditAccount).toInt256();
balances[i] = Balance({token: deltas[i].token, balance: (balance + deltas[i].amount).toUint256()}); // U:[BLL-2]
unchecked {
++i;
}
}
}
/// @dev Compares current balances with the previously stored ones
/// @param creditAccount Credit account to compare balances for
/// @param balances Array of previously stored balances
/// @param comparison Whether current balances must be greater/less than or equal to stored ones
/// @return success True if condition specified by `comparison` holds for all tokens, false otherwise
function compareBalances(address creditAccount, Balance[] memory balances, Comparison comparison)
internal
view
returns (bool success)
{
uint256 len = balances.length;
unchecked {
for (uint256 i = 0; i < len; ++i) {
if (!BalancesLogic.checkBalance(creditAccount, balances[i].token, balances[i].balance, comparison)) {
return false; // U:[BLL-3]
}
}
}
return true; // U:[BLL-3]
}
/// @dev Returns balances of specified tokens on the credit account
/// @param creditAccount Credit account to compute balances for
/// @param tokensMask Bit mask of tokens to compute balances for
/// @param getTokenByMaskFn Function that returns token's address by its mask
function storeBalances(
address creditAccount,
uint256 tokensMask,
function (uint256) view returns (address) getTokenByMaskFn
) internal view returns (BalanceWithMask[] memory balances) {
if (tokensMask == 0) return balances;
balances = new BalanceWithMask[](tokensMask.calcEnabledTokens()); // U:[BLL-4]
unchecked {
uint256 i;
while (tokensMask != 0) {
uint256 tokenMask = tokensMask & uint256(-int256(tokensMask));
tokensMask ^= tokenMask;
address token = getTokenByMaskFn(tokenMask);
balances[i] = BalanceWithMask({
token: token,
tokenMask: tokenMask,
balance: IERC20(token).safeBalanceOf(creditAccount)
}); // U:[BLL-4]
++i;
}
}
}
/// @dev Compares current balances of specified tokens with the previously stored ones
/// @param creditAccount Credit account to compare balances for
/// @param tokensMask Bit mask of tokens to compare balances for
/// @param balances Array of previously stored balances
/// @param comparison Whether current balances must be greater/less than or equal to stored ones
/// @return success True if condition specified by `comparison` holds for all tokens, false otherwise
function compareBalances(
address creditAccount,
uint256 tokensMask,
BalanceWithMask[] memory balances,
Comparison comparison
) internal view returns (bool) {
if (tokensMask == 0) return true;
unchecked {
uint256 len = balances.length;
for (uint256 i; i < len; ++i) {
if (tokensMask & balances[i].tokenMask != 0) {
if (!BalancesLogic.checkBalance(creditAccount, balances[i].token, balances[i].balance, comparison))
{
return false; // U:[BLL-5]
}
}
}
}
return true; // U:[BLL-5]
}
}
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IncorrectParameterException} from "../interfaces/IExceptions.sol";
uint256 constant UNDERLYING_TOKEN_MASK = 1;
/// @title Bit mask library
/// @notice Implements functions that manipulate bit masks
/// Bit masks are utilized extensively by Gearbox to efficiently store token sets (enabled tokens on accounts
/// or forbidden tokens) and check for set inclusion. A mask is a uint256 number that has its i-th bit set to
/// 1 if i-th item is included into the set. For example, each token has a mask equal to 2**i, so set inclusion
/// can be checked by checking tokenMask & setMask != 0.
library BitMask {
/// @dev Calculates an index of an item based on its mask (using a binary search)
/// @dev The input should always have only 1 bit set, otherwise the result may be unpredictable
function calcIndex(uint256 mask) internal pure returns (uint8 index) {
if (mask == 0) revert IncorrectParameterException(); // U:[BM-1]
uint16 lb = 0; // U:[BM-2]
uint16 ub = 256; // U:[BM-2]
uint16 mid = 128; // U:[BM-2]
unchecked {
while (true) {
uint256 newMask = 1 << mid;
if (newMask & mask != 0) return uint8(mid); // U:[BM-2]
if (newMask > mask) ub = mid; // U:[BM-2]
else lb = mid; // U:[BM-2]
mid = (lb + ub) >> 1; // U:[BM-2]
}
}
}
/// @dev Calculates the number of `1` bits
/// @param enabledTokensMask Bit mask to compute the number of `1` bits in
function calcEnabledTokens(uint256 enabledTokensMask) internal pure returns (uint256 totalTokensEnabled) {
unchecked {
while (enabledTokensMask > 0) {
enabledTokensMask &= enabledTokensMask - 1; // U:[BM-3]
++totalTokensEnabled; // U:[BM-3]
}
}
}
/// @dev Enables bits from the second mask in the first mask
/// @param enabledTokenMask The initial mask
/// @param bitsToEnable Mask of bits to enable
function enable(uint256 enabledTokenMask, uint256 bitsToEnable) internal pure returns (uint256) {
return enabledTokenMask | bitsToEnable; // U:[BM-4]
}
/// @dev Disables bits from the second mask in the first mask
/// @param enabledTokenMask The initial mask
/// @param bitsToDisable Mask of bits to disable
function disable(uint256 enabledTokenMask, uint256 bitsToDisable) internal pure returns (uint256) {
return enabledTokenMask & ~bitsToDisable; // U:[BM-4]
}
/// @dev Computes a new mask with sets of new enabled and disabled bits
/// @dev bitsToEnable and bitsToDisable are applied sequentially to original mask
/// @param enabledTokensMask The initial mask
/// @param bitsToEnable Mask with bits to enable
/// @param bitsToDisable Mask with bits to disable
function enableDisable(uint256 enabledTokensMask, uint256 bitsToEnable, uint256 bitsToDisable)
internal
pure
returns (uint256)
{
return (enabledTokensMask | bitsToEnable) & (~bitsToDisable); // U:[BM-5]
}
/// @dev Enables bits from the second mask in the first mask, skipping specified bits
/// @param enabledTokenMask The initial mask
/// @param bitsToEnable Mask with bits to enable
/// @param invertedSkipMask An inversion of mask of immutable bits
function enable(uint256 enabledTokenMask, uint256 bitsToEnable, uint256 invertedSkipMask)
internal
pure
returns (uint256)
{
return enabledTokenMask | (bitsToEnable & invertedSkipMask); // U:[BM-6]
}
/// @dev Disables bits from the second mask in the first mask, skipping specified bits
/// @param enabledTokenMask The initial mask
/// @param bitsToDisable Mask with bits to disable
/// @param invertedSkipMask An inversion of mask of immutable bits
function disable(uint256 enabledTokenMask, uint256 bitsToDisable, uint256 invertedSkipMask)
internal
pure
returns (uint256)
{
return enabledTokenMask & (~(bitsToDisable & invertedSkipMask)); // U:[BM-6]
}
/// @dev Computes a new mask with sets of new enabled and disabled bits, skipping some bits
/// @dev bitsToEnable and bitsToDisable are applied sequentially to original mask. Skipmask is applied in both cases.
/// @param enabledTokensMask The initial mask
/// @param bitsToEnable Mask with bits to enable
/// @param bitsToDisable Mask with bits to disable
/// @param invertedSkipMask An inversion of mask of immutable bits
function enableDisable(
uint256 enabledTokensMask,
uint256 bitsToEnable,
uint256 bitsToDisable,
uint256 invertedSkipMask
) internal pure returns (uint256) {
return (enabledTokensMask | (bitsToEnable & invertedSkipMask)) & (~(bitsToDisable & invertedSkipMask)); // U:[BM-7]
}
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;
// Denominations
uint256 constant WAD = 1e18;
uint256 constant RAY = 1e27;
uint16 constant PERCENTAGE_FACTOR = 1e4; //percentage plus two decimals
// 25% of type(uint256).max
uint256 constant ALLOWANCE_THRESHOLD = type(uint96).max >> 3;
// FEE = 50%
uint16 constant DEFAULT_FEE_INTEREST = 50_00; // 50%
// LIQUIDATION_FEE 1.5%
uint16 constant DEFAULT_FEE_LIQUIDATION = 1_50; // 1.5%
// LIQUIDATION PREMIUM 4%
uint16 constant DEFAULT_LIQUIDATION_PREMIUM = 4_00; // 4%
// LIQUIDATION_FEE_EXPIRED 2%
uint16 constant DEFAULT_FEE_LIQUIDATION_EXPIRED = 1_00; // 2%
// LIQUIDATION PREMIUM EXPIRED 2%
uint16 constant DEFAULT_LIQUIDATION_PREMIUM_EXPIRED = 2_00; // 2%
// DEFAULT PROPORTION OF MAX BORROWED PER BLOCK TO MAX BORROWED PER ACCOUNT
uint16 constant DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER = 2;
// Seconds in a year
uint256 constant SECONDS_PER_YEAR = 365 days;
uint256 constant SECONDS_PER_ONE_AND_HALF_YEAR = (SECONDS_PER_YEAR * 3) / 2;
// OPERATIONS
// Leverage decimals - 100 is equal to 2x leverage (100% * collateral amount + 100% * borrowed amount)
uint8 constant LEVERAGE_DECIMALS = 100;
// Maximum withdraw fee for pool in PERCENTAGE_FACTOR format
uint8 constant MAX_WITHDRAW_FEE = 100;
uint256 constant EXACT_INPUT = 1;
uint256 constant EXACT_OUTPUT = 2;
address constant UNIVERSAL_CONTRACT = 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC;
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^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.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
// THIRD-PARTY
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {SafeERC20} from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol";
// LIBS & TRAITS
import {BalancesLogic, Balance, BalanceDelta, BalanceWithMask, Comparison} from "../libraries/BalancesLogic.sol";
import {ACLNonReentrantTrait} from "../traits/ACLNonReentrantTrait.sol";
import {BitMask, UNDERLYING_TOKEN_MASK} from "../libraries/BitMask.sol";
// INTERFACES
import "../interfaces/ICreditFacadeV3.sol";
import "../interfaces/IAddressProviderV3.sol";
import {
ICreditManagerV3,
ManageDebtAction,
RevocationPair,
CollateralDebtData,
CollateralCalcTask,
BOT_PERMISSIONS_SET_FLAG,
INACTIVE_CREDIT_ACCOUNT_ADDRESS
} from "../interfaces/ICreditManagerV3.sol";
import {AllowanceAction} from "../interfaces/ICreditConfiguratorV3.sol";
import {IPriceOracleV3} from "../interfaces/IPriceOracleV3.sol";
import {IUpdatablePriceFeed} from "@gearbox-protocol/core-v2/contracts/interfaces/IPriceFeed.sol";
import {IPoolV3} from "../interfaces/IPoolV3.sol";
import {IDegenNFTV2} from "@gearbox-protocol/core-v2/contracts/interfaces/IDegenNFTV2.sol";
import {IWETH} from "@gearbox-protocol/core-v2/contracts/interfaces/external/IWETH.sol";
import {IBotListV3} from "../interfaces/IBotListV3.sol";
// CONSTANTS
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
// EXCEPTIONS
import "../interfaces/IExceptions.sol";
uint256 constant OPEN_CREDIT_ACCOUNT_FLAGS = ALL_PERMISSIONS & ~DECREASE_DEBT_PERMISSION;
uint256 constant CLOSE_CREDIT_ACCOUNT_FLAGS = ALL_PERMISSIONS & ~INCREASE_DEBT_PERMISSION;
uint256 constant LIQUIDATE_CREDIT_ACCOUNT_FLAGS =
EXTERNAL_CALLS_PERMISSION | ADD_COLLATERAL_PERMISSION | WITHDRAW_COLLATERAL_PERMISSION;
/// @title Credit facade V3
/// @notice Provides a user interface to open, close and liquidate leveraged positions in the credit manager,
/// and implements the main entry-point for credit accounts management: multicall.
/// @notice Multicall allows account owners to batch all the desired operations (adding or withdrawing collateral,
/// changing debt size, interacting with external protocols via adapters or increasing quotas) into one call,
/// followed by the collateral check that ensures that account is sufficiently collateralized.
/// For more details on what one can achieve with multicalls, see `_multicall` and `ICreditFacadeV3Multicall`.
/// @notice Users can also let external bots manage their accounts via `botMulticall`. Bots can be relatively general,
/// the facade only ensures that they can do no harm to the protocol by running the collateral check after the
/// multicall and checking the permissions given to them by users. See `BotListV3` for additional details.
/// @notice Credit facade implements a few safeguards on top of those present in the credit manager, including debt and
/// quota size validation, pausing on large protocol losses, Degen NFT whitelist mode, and forbidden tokens
/// (they count towards account value, but having them enabled as collateral restricts available actions and
/// activates a safer version of collateral check).
contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
using Address for address;
using Address for address payable;
using BitMask for uint256;
using SafeCast for uint256;
using SafeERC20 for IERC20;
/// @notice Contract version
uint256 public constant override version = 3_01;
/// @notice Maximum quota size, as a multiple of `maxDebt`
uint256 public constant override maxQuotaMultiplier = 2;
/// @notice Credit manager connected to this credit facade
address public immutable override creditManager;
/// @notice Whether credit facade is expirable
bool public immutable override expirable;
/// @notice WETH token address
address public immutable override weth;
/// @notice Degen NFT address
address public immutable override degenNFT;
/// @notice Expiration timestamp
uint40 public override expirationDate;
/// @notice Maximum amount that can be borrowed by a credit manager in a single block, as a multiple of `maxDebt`
uint8 public override maxDebtPerBlockMultiplier;
/// @notice Last block when underlying was borrowed by a credit manager
uint64 internal lastBlockBorrowed;
/// @notice The total amount borrowed by a credit manager in `lastBlockBorrowed`
uint128 internal totalBorrowedInBlock;
/// @notice Bot list address
address public override botList;
/// @notice Credit account debt limits packed into a single slot
DebtLimits public override debtLimits;
/// @notice Bit mask encoding a set of forbidden tokens
uint256 public override forbiddenTokenMask;
/// @notice Info on bad debt liquidation losses packed into a single slot
CumulativeLossParams public override lossParams;
/// @notice Mapping account => emergency liquidator status
mapping(address => bool) public override canLiquidateWhilePaused;
/// @dev Ensures that function caller is credit configurator
modifier creditConfiguratorOnly() {
_checkCreditConfigurator();
_;
}
/// @dev Ensures that function caller is `creditAccount`'s owner
modifier creditAccountOwnerOnly(address creditAccount) {
_checkCreditAccountOwner(creditAccount);
_;
}
/// @dev Ensures that function can't be called when the contract is paused, unless caller is an emergency liquidator
modifier whenNotPausedOrEmergency() {
require(!paused() || canLiquidateWhilePaused[msg.sender], "Pausable: paused");
_;
}
/// @dev Ensures that function can't be called when the contract is expired
modifier whenNotExpired() {
_checkExpired();
_;
}
/// @dev Wraps any ETH sent in a function call and sends it back to the caller
modifier wrapETH() {
_wrapETH();
_;
}
/// @notice Constructor
/// @param _creditManager Credit manager to connect this facade to
/// @param _degenNFT Degen NFT address or `address(0)`
/// @param _expirable Whether this facade should be expirable
constructor(address _creditManager, address _degenNFT, bool _expirable)
ACLNonReentrantTrait(ICreditManagerV3(_creditManager).addressProvider())
{
creditManager = _creditManager; // U:[FA-1]
address addressProvider = ICreditManagerV3(_creditManager).addressProvider();
weth = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_WETH_TOKEN, NO_VERSION_CONTROL); // U:[FA-1]
botList = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_BOT_LIST, 3_00); // U:[FA-1]
degenNFT = _degenNFT; // U:[FA-1]
expirable = _expirable; // U:[FA-1]
}
// ------------------ //
// ACCOUNT MANAGEMENT //
// ------------------ //
/// @notice Opens a new credit account
/// - Wraps any ETH sent in the function call and sends it back to the caller
/// - If Degen NFT is enabled, burns one from the caller
/// - Opens an account in the credit manager
/// - Performs a multicall (all calls allowed except debt decrease and withdrawals)
/// - Runs the collateral check
/// @param onBehalfOf Address on whose behalf to open the account
/// @param calls List of calls to perform after opening the account
/// @param referralCode Referral code to use for potential rewards, 0 if no referral code is provided
/// @return creditAccount Address of the newly opened account
/// @dev Reverts if credit facade is paused or expired
/// @dev Reverts if `onBehalfOf` is not caller while Degen NFT is enabled
function openCreditAccount(address onBehalfOf, MultiCall[] calldata calls, uint256 referralCode)
external
payable
override
whenNotPaused // U:[FA-2]
whenNotExpired // U:[FA-3]
nonReentrant // U:[FA-4]
wrapETH // U:[FA-7]
returns (address creditAccount)
{
if (degenNFT != address(0)) {
if (msg.sender != onBehalfOf) {
revert ForbiddenInWhitelistedModeException(); // U:[FA-9]
}
IDegenNFTV2(degenNFT).burn(onBehalfOf, 1); // U:[FA-9]
}
creditAccount = ICreditManagerV3(creditManager).openCreditAccount({onBehalfOf: onBehalfOf}); // U:[FA-10]
emit OpenCreditAccount(creditAccount, onBehalfOf, msg.sender, referralCode); // U:[FA-10]
if (calls.length != 0) {
// same as `_multicallFullCollateralCheck` but leverages the fact that account is freshly opened to save gas
BalanceWithMask[] memory forbiddenBalances;
uint256 skipCalls = _applyOnDemandPriceUpdates(calls);
FullCheckParams memory fullCheckParams = _multicall({
creditAccount: creditAccount,
calls: calls,
enabledTokensMask: 0,
flags: OPEN_CREDIT_ACCOUNT_FLAGS,
skip: skipCalls
}); // U:[FA-10]
_fullCollateralCheck({
creditAccount: creditAccount,
enabledTokensMaskBefore: 0,
fullCheckParams: fullCheckParams,
forbiddenBalances: forbiddenBalances,
forbiddenTokensMask: forbiddenTokenMask
}); // U:[FA-10]
}
}
/// @notice Closes a credit account
/// - Wraps any ETH sent in the function call and sends it back to the caller
/// - Performs a multicall (all calls are allowed except debt increase)
/// - Closes a credit account in the credit manager
/// - Erases all bots permissions
/// @param creditAccount Account to close
/// @param calls List of calls to perform before closing the account
/// @dev Reverts if `creditAccount` is not opened in connected credit manager by caller
/// @dev Reverts if facade is paused
/// @dev Reverts if account has enabled tokens after executing `calls`
/// @dev Reverts if account's debt is not zero after executing `calls`
function closeCreditAccount(address creditAccount, MultiCall[] calldata calls)
external
payable
override
creditAccountOwnerOnly(creditAccount) // U:[FA-5]
whenNotPaused // U:[FA-2]
nonReentrant // U:[FA-4]
wrapETH // U:[FA-7]
{
uint256 enabledTokensMask = _enabledTokensMaskOf(creditAccount);
if (calls.length != 0) {
FullCheckParams memory fullCheckParams =
_multicall(creditAccount, calls, enabledTokensMask, CLOSE_CREDIT_ACCOUNT_FLAGS, 0); // U:[FA-11]
enabledTokensMask = fullCheckParams.enabledTokensMaskAfter;
}
if (enabledTokensMask != 0) revert CloseAccountWithEnabledTokensException(); // U:[FA-11]
if (_flagsOf(creditAccount) & BOT_PERMISSIONS_SET_FLAG != 0) {
IBotListV3(botList).eraseAllBotPermissions(creditManager, creditAccount); // U:[FA-11]
}
ICreditManagerV3(creditManager).closeCreditAccount(creditAccount); // U:[FA-11]
emit CloseCreditAccount(creditAccount, msg.sender); // U:[FA-11]
}
/// @notice Liquidates a credit account
/// - Updates price feeds before running all computations if such calls are present in the multicall
/// - Evaluates account's collateral and debt to determine whether liquidated account is unhealthy or expired
/// - Performs a multicall (only `addCollateral`, `withdrawCollateral` and adapter calls are allowed)
/// - Liquidates a credit account in the credit manager, which repays debt to the pool, removes quotas, and
/// transfers underlying to the liquidator
/// - If pool incurs a loss on liquidation, further borrowing through the facade is forbidden
/// - If cumulative loss from bad debt liquidations exceeds the threshold, the facade is paused
/// @notice The function computes account’s total value (oracle value of enabled tokens), discounts it by liquidator’s
/// premium, and uses this value to compute funds due to the pool and owner.
/// Debt to the pool must be repaid in underlying, while funds due to owner might be covered by underlying
/// as well as by tokens that counted towards total value calculation, with the only condition that balance
/// of such tokens can’t be increased in the multicall.
/// Typically, a liquidator would swap all holdings on the account to underlying via multicall and receive
/// the premium in underlying.
/// An alternative strategy would be to add underlying collateral to repay debt and withdraw desired tokens
/// to handle them in another way, while remaining tokens would cover funds due to owner.
/// @param creditAccount Account to liquidate
/// @param to Address to transfer underlying left after liquidation
/// @param calls List of calls to perform before liquidating the account
/// @dev When the credit facade is paused, reverts if caller is not an approved emergency liquidator
/// @dev Reverts if `creditAccount` is not opened in connected credit manager
/// @dev Reverts if account has no debt or is neither unhealthy nor expired
/// @dev Reverts if remaining token balances increase during the multicall
function liquidateCreditAccount(address creditAccount, address to, MultiCall[] calldata calls)
external
override
whenNotPausedOrEmergency // U:[FA-2,12]
nonReentrant // U:[FA-4]
{
uint256 skipCalls = _applyOnDemandPriceUpdates(calls);
CollateralDebtData memory collateralDebtData =
ICreditManagerV3(creditManager).calcDebtAndCollateral(creditAccount, CollateralCalcTask.DEBT_COLLATERAL); // U:[FA-16]
bool isUnhealthy = collateralDebtData.twvUSD < collateralDebtData.totalDebtUSD;
if (collateralDebtData.debt == 0 || !isUnhealthy && !_isExpired()) {
revert CreditAccountNotLiquidatableException(); // U:[FA-13]
}
collateralDebtData.enabledTokensMask = collateralDebtData.enabledTokensMask.disable(UNDERLYING_TOKEN_MASK); // U:[FA-14]
BalanceWithMask[] memory initialBalances = BalancesLogic.storeBalances({
creditAccount: creditAccount,
tokensMask: collateralDebtData.enabledTokensMask,
getTokenByMaskFn: _getTokenByMask
});
FullCheckParams memory fullCheckParams = _multicall(
creditAccount, calls, collateralDebtData.enabledTokensMask, LIQUIDATE_CREDIT_ACCOUNT_FLAGS, skipCalls
); // U:[FA-16]
collateralDebtData.enabledTokensMask &= fullCheckParams.enabledTokensMaskAfter; // U:[FA-16]
bool success = BalancesLogic.compareBalances({
creditAccount: creditAccount,
tokensMask: collateralDebtData.enabledTokensMask,
balances: initialBalances,
comparison: Comparison.LESS
});
if (!success) revert RemainingTokenBalanceIncreasedException(); // U:[FA-14]
collateralDebtData.enabledTokensMask = collateralDebtData.enabledTokensMask.enable(UNDERLYING_TOKEN_MASK); // U:[FA-16]
(uint256 remainingFunds, uint256 reportedLoss) = ICreditManagerV3(creditManager).liquidateCreditAccount({
creditAccount: creditAccount,
collateralDebtData: collateralDebtData,
to: to,
isExpired: !isUnhealthy
}); // U:[FA-15,16]
emit LiquidateCreditAccount(creditAccount, msg.sender, to, remainingFunds); // U:[FA-16]
if (reportedLoss != 0) {
maxDebtPerBlockMultiplier = 0; // U:[FA-17]
// both cast and addition are safe because amounts are of much smaller scale
lossParams.currentCumulativeLoss += uint128(reportedLoss); // U:[FA-17]
// can't pause an already paused contract
if (!paused() && lossParams.currentCumulativeLoss > lossParams.maxCumulativeLoss) {
_pause(); // U:[FA-17]
}
}
}
/// @notice Executes a batch of calls allowing user to manage their credit account
/// - Wraps any ETH sent in the function call and sends it back to the caller
/// - Performs a multicall (all calls are allowed)
/// - Runs the collateral check
/// @param creditAccount Account to perform the calls on
/// @param calls List of calls to perform
/// @dev Reverts if `creditAccount` is not opened in connected credit manager by caller
/// @dev Reverts if credit facade is paused or expired
function multicall(address creditAccount, MultiCall[] calldata calls)
external
payable
override
creditAccountOwnerOnly(creditAccount) // U:[FA-5]
whenNotPaused // U:[FA-2]
whenNotExpired // U:[FA-3]
nonReentrant // U:[FA-4]
wrapETH // U:[FA-7]
{
_multicallFullCollateralCheck(creditAccount, calls, ALL_PERMISSIONS); // U:[FA-18]
}
/// @notice Executes a batch of calls allowing bot to manage a credit account
/// - Performs a multicall (allowed calls are determined by permissions given by account's owner
/// or by DAO in case bot has special permissions in the credit manager)
/// - Runs the collateral check
/// @param creditAccount Account to perform the calls on
/// @param calls List of calls to perform
/// @dev Reverts if credit facade is paused or expired
/// @dev Reverts if `creditAccount` is not opened in connected credit manager
/// @dev Reverts if calling bot is forbidden or has no permissions to manage `creditAccount`
function botMulticall(address creditAccount, MultiCall[] calldata calls)
external
override
whenNotPaused // U:[FA-2]
whenNotExpired // U:[FA-3]
nonReentrant // U:[FA-4]
{
_getBorrowerOrRevert(creditAccount); // U:[FA-5]
(uint256 botPermissions, bool forbidden, bool hasSpecialPermissions) = IBotListV3(botList).getBotStatus({
bot: msg.sender,
creditManager: creditManager,
creditAccount: creditAccount
});
if (
botPermissions == 0 || forbidden
|| (!hasSpecialPermissions && (_flagsOf(creditAccount) & BOT_PERMISSIONS_SET_FLAG == 0))
) {
revert NotApprovedBotException(); // U:[FA-19]
}
_multicallFullCollateralCheck(creditAccount, calls, botPermissions); // U:[FA-19, 20]
}
/// @notice Sets `bot`'s permissions to manage `creditAccount`
/// @param creditAccount Account to set permissions for
/// @param bot Bot to set permissions for
/// @param permissions A bit mask encoding bot permissions
/// @dev Reverts if `creditAccount` is not opened in connected credit manager by caller
/// @dev Reverts if `permissions` has unexpected bits enabled
/// @dev Reverts if account has more active bots than allowed after changing permissions
/// @dev Changes account's `BOT_PERMISSIONS_SET_FLAG` in the credit manager if needed
function setBotPermissions(address creditAccount, address bot, uint192 permissions)
external
override
creditAccountOwnerOnly(creditAccount) // U:[FA-5]
nonReentrant // U:[FA-4]
{
if (permissions & ~ALL_PERMISSIONS != 0) revert UnexpectedPermissionsException(); // U:[FA-41]
uint256 remainingBots = IBotListV3(botList).setBotPermissions({
bot: bot,
creditManager: creditManager,
creditAccount: creditAccount,
permissions: permissions
}); // U:[FA-41]
if (remainingBots == 0) {
_setFlagFor({creditAccount: creditAccount, flag: BOT_PERMISSIONS_SET_FLAG, value: false}); // U:[FA-41]
} else if (_flagsOf(creditAccount) & BOT_PERMISSIONS_SET_FLAG == 0) {
_setFlagFor({creditAccount: creditAccount, flag: BOT_PERMISSIONS_SET_FLAG, value: true}); // U:[FA-41]
}
}
// --------- //
// MULTICALL //
// --------- //
/// @dev Batches price feed updates, multicall and collateral check into a single function
function _multicallFullCollateralCheck(address creditAccount, MultiCall[] calldata calls, uint256 flags) internal {
uint256 forbiddenTokensMask = forbiddenTokenMask;
uint256 enabledTokensMaskBefore = _enabledTokensMaskOf(creditAccount); // U:[FA-18]
BalanceWithMask[] memory forbiddenBalances = BalancesLogic.storeBalances({
creditAccount: creditAccount,
tokensMask: forbiddenTokensMask & enabledTokensMaskBefore,
getTokenByMaskFn: _getTokenByMask
});
uint256 skipCalls = _applyOnDemandPriceUpdates(calls);
FullCheckParams memory fullCheckParams = _multicall(
creditAccount,
calls,
enabledTokensMaskBefore,
forbiddenBalances.length != 0 ? flags.enable(FORBIDDEN_TOKENS_BEFORE_CALLS) : flags,
skipCalls
);
_fullCollateralCheck({
creditAccount: creditAccount,
enabledTokensMaskBefore: enabledTokensMaskBefore,
fullCheckParams: fullCheckParams,
forbiddenBalances: forbiddenBalances,
forbiddenTokensMask: forbiddenTokensMask
}); // U:[FA-18]
}
/// @dev Multicall implementation
/// @param creditAccount Account to perform actions with
/// @param calls Array of `(target, callData)` tuples representing a sequence of calls to perform
/// - if `target` is this contract's address, `callData` must be an ABI-encoded calldata of a method
/// from `ICreditFacadeV3Multicall`, which is dispatched and handled appropriately
/// - otherwise, `target` must be an allowed adapter, which is called with `callData`, and is expected to
/// return two ABI-encoded `uint256` masks of tokens that should be enabled/disabled after the call
/// @param enabledTokensMask Bitmask of account's enabled collateral tokens before the multicall
/// @param flags Permissions and flags that dictate what methods can be called
/// @param skip The number of calls that can be skipped (see `_applyOnDemandPriceUpdates`)
/// @return fullCheckParams Collateral check parameters, see `FullCheckParams` for details
function _multicall(
address creditAccount,
MultiCall[] calldata calls,
uint256 enabledTokensMask,
uint256 flags,
uint256 skip
) internal returns (FullCheckParams memory fullCheckParams) {
emit StartMultiCall({creditAccount: creditAccount, caller: msg.sender}); // U:[FA-18]
uint256 quotedTokensMaskInverted;
Balance[] memory expectedBalances;
fullCheckParams.minHealthFactor = PERCENTAGE_FACTOR;
unchecked {
uint256 len = calls.length;
for (uint256 i = skip; i < len; ++i) {
MultiCall calldata mcall = calls[i];
// credit facade calls
if (mcall.target == address(this)) {
bytes4 method = bytes4(mcall.callData);
// storeExpectedBalances
if (method == ICreditFacadeV3Multicall.storeExpectedBalances.selector) {
if (expectedBalances.length != 0) revert ExpectedBalancesAlreadySetException(); // U:[FA-23]
BalanceDelta[] memory balanceDeltas = abi.decode(mcall.callData[4:], (BalanceDelta[])); // U:[FA-23]
expectedBalances = BalancesLogic.storeBalances(creditAccount, balanceDeltas); // U:[FA-23]
}
// compareBalances
else if (method == ICreditFacadeV3Multicall.compareBalances.selector) {
if (expectedBalances.length == 0) revert ExpectedBalancesNotSetException(); // U:[FA-23]
if (!BalancesLogic.compareBalances(creditAccount, expectedBalances, Comparison.GREATER)) {
revert BalanceLessThanExpectedException(); // U:[FA-23]
}
expectedBalances = new Balance[](0); // U:[FA-23]
}
// addCollateral
else if (method == ICreditFacadeV3Multicall.addCollateral.selector) {
_revertIfNoPermission(flags, ADD_COLLATERAL_PERMISSION); // U:[FA-21]
quotedTokensMaskInverted = _quotedTokensMaskInvertedLoE(quotedTokensMaskInverted);
enabledTokensMask = enabledTokensMask.enable({
bitsToEnable: _addCollateral(creditAccount, mcall.callData[4:]),
invertedSkipMask: quotedTokensMaskInverted
}); // U:[FA-26]
}
// addCollateralWithPermit
else if (method == ICreditFacadeV3Multicall.addCollateralWithPermit.selector) {
_revertIfNoPermission(flags, ADD_COLLATERAL_PERMISSION); // U:[FA-21]
quotedTokensMaskInverted = _quotedTokensMaskInvertedLoE(quotedTokensMaskInverted);
enabledTokensMask = enabledTokensMask.enable({
bitsToEnable: _addCollateralWithPermit(creditAccount, mcall.callData[4:]),
invertedSkipMask: quotedTokensMaskInverted
}); // U:[FA-26B]
}
// updateQuota
else if (method == ICreditFacadeV3Multicall.updateQuota.selector) {
_revertIfNoPermission(flags, UPDATE_QUOTA_PERMISSION); // U:[FA-21]
(uint256 tokensToEnable, uint256 tokensToDisable) =
_updateQuota(creditAccount, mcall.callData[4:], flags & FORBIDDEN_TOKENS_BEFORE_CALLS != 0); // U:[FA-34]
enabledTokensMask = enabledTokensMask.enableDisable(tokensToEnable, tokensToDisable); // U:[FA-34]
}
// withdrawCollateral
else if (method == ICreditFacadeV3Multicall.withdrawCollateral.selector) {
_revertIfNoPermission(flags, WITHDRAW_COLLATERAL_PERMISSION); // U:[FA-21]
fullCheckParams.revertOnForbiddenTokens = true; // U:[FA-30]
fullCheckParams.useSafePrices = true;
uint256 tokensToDisable = _withdrawCollateral(creditAccount, mcall.callData[4:]); // U:[FA-34]
quotedTokensMaskInverted = _quotedTokensMaskInvertedLoE(quotedTokensMaskInverted);
enabledTokensMask = enabledTokensMask.disable({
bitsToDisable: tokensToDisable,
invertedSkipMask: quotedTokensMaskInverted
}); // U:[FA-35]
}
// increaseDebt
else if (method == ICreditFacadeV3Multicall.increaseDebt.selector) {
_revertIfNoPermission(flags, INCREASE_DEBT_PERMISSION); // U:[FA-21]
fullCheckParams.revertOnForbiddenTokens = true; // U:[FA-30]
(uint256 tokensToEnable,) = _manageDebt(
creditAccount, mcall.callData[4:], enabledTokensMask, ManageDebtAction.INCREASE_DEBT
); // U:[FA-27]
enabledTokensMask = enabledTokensMask.enable(tokensToEnable); // U:[FA-27]
}
// decreaseDebt
else if (method == ICreditFacadeV3Multicall.decreaseDebt.selector) {
_revertIfNoPermission(flags, DECREASE_DEBT_PERMISSION); // U:[FA-21]
(, uint256 tokensToDisable) = _manageDebt(
creditAccount, mcall.callData[4:], enabledTokensMask, ManageDebtAction.DECREASE_DEBT
); // U:[FA-31]
enabledTokensMask = enabledTokensMask.disable(tokensToDisable); // U:[FA-31]
}
// setFullCheckParams
else if (method == ICreditFacadeV3Multicall.setFullCheckParams.selector) {
(fullCheckParams.collateralHints, fullCheckParams.minHealthFactor) =
abi.decode(mcall.callData[4:], (uint256[], uint16)); // U:[FA-24]
if (fullCheckParams.minHealthFactor < PERCENTAGE_FACTOR) {
revert CustomHealthFactorTooLowException(); // U:[FA-24]
}
uint256 hintsLen = fullCheckParams.collateralHints.length;
for (uint256 j; j < hintsLen; ++j) {
uint256 mask = fullCheckParams.collateralHints[j];
if (mask == 0 || mask & mask - 1 != 0) revert InvalidCollateralHintException(); // U:[FA-24]
}
}
// enableToken
else if (method == ICreditFacadeV3Multicall.enableToken.selector) {
_revertIfNoPermission(flags, ENABLE_TOKEN_PERMISSION); // U:[FA-21]
address token = abi.decode(mcall.callData[4:], (address)); // U:[FA-33]
quotedTokensMaskInverted = _quotedTokensMaskInvertedLoE(quotedTokensMaskInverted);
enabledTokensMask = enabledTokensMask.enable({
bitsToEnable: _getTokenMaskOrRevert(token),
invertedSkipMask: quotedTokensMaskInverted
}); // U:[FA-33]
}
// disableToken
else if (method == ICreditFacadeV3Multicall.disableToken.selector) {
_revertIfNoPermission(flags, DISABLE_TOKEN_PERMISSION); // U:[FA-21]
address token = abi.decode(mcall.callData[4:], (address)); // U:[FA-33]
quotedTokensMaskInverted = _quotedTokensMaskInvertedLoE(quotedTokensMaskInverted);
enabledTokensMask = enabledTokensMask.disable({
bitsToDisable: _getTokenMaskOrRevert(token),
invertedSkipMask: quotedTokensMaskInverted
}); // U:[FA-33]
}
// revokeAdapterAllowances
else if (method == ICreditFacadeV3Multicall.revokeAdapterAllowances.selector) {
_revertIfNoPermission(flags, REVOKE_ALLOWANCES_PERMISSION); // U:[FA-21]
_revokeAdapterAllowances(creditAccount, mcall.callData[4:]); // U:[FA-36]
}
// unknown method
else {
revert UnknownMethodException(); // U:[FA-22]
}
}
// adapter calls
else {
_revertIfNoPermission(flags, EXTERNAL_CALLS_PERMISSION); // U:[FA-21]
bytes memory result;
{
address targetContract = ICreditManagerV3(creditManager).adapterToContract(mcall.target);
if (targetContract == address(0)) {
revert TargetContractNotAllowedException();
}
if (flags & EXTERNAL_CONTRACT_WAS_CALLED == 0) {
flags = flags.enable(EXTERNAL_CONTRACT_WAS_CALLED);
_setActiveCreditAccount(creditAccount); // U:[FA-38]
}
result = mcall.target.functionCall(mcall.callData); // U:[FA-38]
emit Execute({creditAccount: creditAccount, targetContract: targetContract});
}
(uint256 tokensToEnable, uint256 tokensToDisable) = abi.decode(result, (uint256, uint256)); // U:[FA-38]
quotedTokensMaskInverted = _quotedTokensMaskInvertedLoE(quotedTokensMaskInverted);
enabledTokensMask = enabledTokensMask.enableDisable({
bitsToEnable: tokensToEnable,
bitsToDisable: tokensToDisable,
invertedSkipMask: quotedTokensMaskInverted
}); // U:[FA-38]
}
}
}
if (expectedBalances.length != 0) {
if (!BalancesLogic.compareBalances(creditAccount, expectedBalances, Comparison.GREATER)) {
revert BalanceLessThanExpectedException(); // U:[FA-23]
}
}
if (enabledTokensMask & forbiddenTokenMask != 0) {
fullCheckParams.useSafePrices = true;
}
if (flags & EXTERNAL_CONTRACT_WAS_CALLED != 0) {
_unsetActiveCreditAccount(); // U:[FA-38]
}
fullCheckParams.enabledTokensMaskAfter = enabledTokensMask; // U:[FA-38]
emit FinishMultiCall(); // U:[FA-18]
}
/// @dev Applies on-demand price feed updates placed at the beginning of the multicall (if there are any)
/// @return skipCalls Number of update calls made that can be skiped later in the `_multicall`
function _applyOnDemandPriceUpdates(MultiCall[] calldata calls) internal returns (uint256 skipCalls) {
address priceOracle;
unchecked {
uint256 len = calls.length;
for (uint256 i; i < len; ++i) {
MultiCall calldata mcall = calls[i];
if (
mcall.target == address(this)
&& bytes4(mcall.callData) == ICreditFacadeV3Multicall.onDemandPriceUpdate.selector
) {
(address token, bool reserve, bytes memory data) =
abi.decode(mcall.callData[4:], (address, bool, bytes)); // U:[FA-25]
priceOracle = _priceOracleLoE(priceOracle); // U:[FA-25]
address priceFeed = IPriceOracleV3(priceOracle).priceFeedsRaw(token, reserve); // U:[FA-25]
if (priceFeed == address(0)) {
revert PriceFeedDoesNotExistException(); // U:[FA-25]
}
IUpdatablePriceFeed(priceFeed).updatePrice(data); // U:[FA-25]
} else {
return i;
}
}
return len;
}
}
/// @dev Performs collateral check to ensure that
/// - account is sufficiently collateralized
/// - account has no forbidden tokens after risky operations
/// - no forbidden tokens have been enabled during the multicall
/// - no enabled forbidden token balance has increased during the multicall
function _fullCollateralCheck(
address creditAccount,
uint256 enabledTokensMaskBefore,
FullCheckParams memory fullCheckParams,
BalanceWithMask[] memory forbiddenBalances,
uint256 forbiddenTokensMask
) internal {
uint256 enabledTokensMask = ICreditManagerV3(creditManager).fullCollateralCheck(
creditAccount,
fullCheckParams.enabledTokensMaskAfter,
fullCheckParams.collateralHints,
fullCheckParams.minHealthFactor,
fullCheckParams.useSafePrices
); // U:[FA-45]
uint256 enabledForbiddenTokensMask = enabledTokensMask & forbiddenTokensMask;
if (enabledForbiddenTokensMask != 0) {
if (fullCheckParams.revertOnForbiddenTokens) revert ForbiddenTokensException(); // U:[FA-45]
uint256 enabledForbiddenTokensMaskBefore = enabledTokensMaskBefore & forbiddenTokensMask;
if (enabledForbiddenTokensMask & ~enabledForbiddenTokensMaskBefore != 0) {
revert ForbiddenTokenEnabledException(); // U:[FA-45]
}
bool success = BalancesLogic.compareBalances({
creditAccount: creditAccount,
tokensMask: enabledForbiddenTokensMask,
balances: forbiddenBalances,
comparison: Comparison.LESS
});
if (!success) revert ForbiddenTokenBalanceIncreasedException(); // U:[FA-45]
}
}
/// @dev `ICreditFacadeV3Multicall.addCollateral` implementation
function _addCollateral(address creditAccount, bytes calldata callData) internal returns (uint256 tokensToEnable) {
(address token, uint256 amount) = abi.decode(callData, (address, uint256)); // U:[FA-26]
tokensToEnable = _addCollateral({payer: msg.sender, creditAccount: creditAccount, token: token, amount: amount}); // U:[FA-26]
emit AddCollateral(creditAccount, token, amount); // U:[FA-26]
}
/// @dev `ICreditFacadeV3Multicall.addCollateralWithPermit` implementation
function _addCollateralWithPermit(address creditAccount, bytes calldata callData)
internal
returns (uint256 tokensToEnable)
{
(address token, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) =
abi.decode(callData, (address, uint256, uint256, uint8, bytes32, bytes32)); // U:[FA-26B]
// `token` is only validated later in `addCollateral`, but to benefit off of it the attacker would have to make
// it recognizable as collateral in the credit manager, which requires gaining configurator access rights
try IERC20Permit(token).permit(msg.sender, creditManager, amount, deadline, v, r, s) {} catch {} // U:[FA-26B]
tokensToEnable = _addCollateral({payer: msg.sender, creditAccount: creditAccount, token: token, amount: amount}); // U:[FA-26B]
emit AddCollateral(creditAccount, token, amount); // U:[FA-26B]
}
/// @dev `ICreditFacadeV3Multicall.{increase|decrease}Debt` implementation
function _manageDebt(
address creditAccount,
bytes calldata callData,
uint256 enabledTokensMask,
ManageDebtAction action
) internal returns (uint256 tokensToEnable, uint256 tokensToDisable) {
uint256 amount = abi.decode(callData, (uint256)); // U:[FA-27,31]
if (action == ManageDebtAction.INCREASE_DEBT) {
_revertIfOutOfBorrowingLimit(amount); // U:[FA-28]
}
uint256 newDebt;
(newDebt, tokensToEnable, tokensToDisable) =
ICreditManagerV3(creditManager).manageDebt(creditAccount, amount, enabledTokensMask, action); // U:[FA-27,31]
_revertIfOutOfDebtLimits(newDebt); // U:[FA-28, 32, 33, 33A]
if (action == ManageDebtAction.INCREASE_DEBT) {
emit IncreaseDebt({creditAccount: creditAccount, amount: amount}); // U:[FA-27]
} else {
emit DecreaseDebt({creditAccount: creditAccount, amount: amount}); // U:[FA-31]
}
}
/// @dev `ICreditFacadeV3Multicall.updateQuota` implementation
function _updateQuota(address creditAccount, bytes calldata callData, bool hasForbiddenTokens)
internal
returns (uint256 tokensToEnable, uint256 tokensToDisable)
{
(address token, int96 quotaChange, uint96 minQuota) = abi.decode(callData, (address, int96, uint96)); // U:[FA-34]
// Ensures that user is not trying to increase quota for a forbidden token. This happens implicitly when user
// has no enabled forbidden tokens because quota increase would try to enable the token, which is prohibited.
// Thus some gas is saved in this case by not querying token's mask.
if (hasForbiddenTokens && quotaChange > 0) {
if (_getTokenMaskOrRevert(token) & forbiddenTokenMask != 0) {
revert ForbiddenTokensException();
}
}
(tokensToEnable, tokensToDisable) = ICreditManagerV3(creditManager).updateQuota({
creditAccount: creditAccount,
token: token,
quotaChange: quotaChange != type(int96).min
? quotaChange / int96(uint96(PERCENTAGE_FACTOR)) * int96(uint96(PERCENTAGE_FACTOR))
: quotaChange,
minQuota: minQuota,
maxQuota: uint96(Math.min(type(uint96).max, maxQuotaMultiplier * debtLimits.maxDebt))
}); // U:[FA-34]
}
/// @dev `ICreditFacadeV3Multicall.withdrawCollateral` implementation
function _withdrawCollateral(address creditAccount, bytes calldata callData)
internal
returns (uint256 tokensToDisable)
{
(address token, uint256 amount, address to) = abi.decode(callData, (address, uint256, address)); // U:[FA-35]
if (amount == type(uint256).max) {
amount = IERC20(token).balanceOf(creditAccount);
if (amount <= 1) return 0;
unchecked {
--amount;
}
}
tokensToDisable = ICreditManagerV3(creditManager).withdrawCollateral(creditAccount, token, amount, to); // U:[FA-35]
emit WithdrawCollateral(creditAccount, token, amount, to); // U:[FA-35]
}
/// @dev `ICreditFacadeV3Multicall.revokeAdapterAllowances` implementation
function _revokeAdapterAllowances(address creditAccount, bytes calldata callData) internal {
RevocationPair[] memory revocations = abi.decode(callData, (RevocationPair[])); // U:[FA-36]
ICreditManagerV3(creditManager).revokeAdapterAllowances(creditAccount, revocations); // U:[FA-36]
}
// ------------- //
// CONFIGURATION //
// ------------- //
/// @notice Sets the credit facade expiration timestamp
/// @param newExpirationDate New expiration timestamp
/// @dev Reverts if caller is not credit configurator
/// @dev Reverts if credit facade is not expirable
function setExpirationDate(uint40 newExpirationDate)
external
override
creditConfiguratorOnly // U:[FA-6]
{
if (!expirable) {
revert NotAllowedWhenNotExpirableException(); // U:[FA-48]
}
expirationDate = newExpirationDate; // U:[FA-48]
}
/// @notice Sets debt limits per credit account
/// @param newMinDebt New minimum debt amount per credit account
/// @param newMaxDebt New maximum debt amount per credit account
/// @param newMaxDebtPerBlockMultiplier New max debt per block multiplier, `type(uint8).max` to disable the check
/// @dev Reverts if caller is not credit configurator
/// @dev Reverts if `maxDebt * maxDebtPerBlockMultiplier` doesn't fit into `uint128`
function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt, uint8 newMaxDebtPerBlockMultiplier)
external
override
creditConfiguratorOnly // U:[FA-6]
{
if ((uint256(newMaxDebtPerBlockMultiplier) * newMaxDebt) >= type(uint128).max) {
revert IncorrectParameterException(); // U:[FA-49]
}
debtLimits.minDebt = newMinDebt; // U:[FA-49]
debtLimits.maxDebt = newMaxDebt; // U:[FA-49]
maxDebtPerBlockMultiplier = newMaxDebtPerBlockMultiplier; // U:[FA-49]
}
/// @notice Sets the new bot list
/// @param newBotList New bot list address
/// @dev Reverts if caller is not credit configurator
function setBotList(address newBotList)
external
override
creditConfiguratorOnly // U:[FA-6]
{
botList = newBotList; // U:[FA-50]
}
/// @notice Sets the new max cumulative loss
/// @param newMaxCumulativeLoss New max cumulative loss
/// @param resetCumulativeLoss Whether to reset the current cumulative loss to zero
/// @dev Reverts if caller is not credit configurator
function setCumulativeLossParams(uint128 newMaxCumulativeLoss, bool resetCumulativeLoss)
external
override
creditConfiguratorOnly // U:[FA-6]
{
lossParams.maxCumulativeLoss = newMaxCumulativeLoss; // U:[FA-51]
if (resetCumulativeLoss) {
lossParams.currentCumulativeLoss = 0; // U:[FA-51]
}
}
/// @notice Changes token's forbidden status
/// @param token Token to change the status for
/// @param allowance Status to set
/// @dev Reverts if caller is not credit configurator
function setTokenAllowance(address token, AllowanceAction allowance)
external
override
creditConfiguratorOnly // U:[FA-6]
{
uint256 tokenMask = _getTokenMaskOrRevert(token); // U:[FA-52]
forbiddenTokenMask = (allowance == AllowanceAction.ALLOW)
? forbiddenTokenMask.disable(tokenMask)
: forbiddenTokenMask.enable(tokenMask); // U:[FA-52]
}
/// @notice Changes account's status as emergency liquidator
/// @param liquidator Account to change the status for
/// @param allowance Status to set
/// @dev Reverts if caller is not credit configurator
function setEmergencyLiquidator(address liquidator, AllowanceAction allowance)
external
override
creditConfiguratorOnly // U:[FA-6]
{
canLiquidateWhilePaused[liquidator] = allowance == AllowanceAction.ALLOW; // U:[FA-53]
}
// --------- //
// INTERNALS //
// --------- //
/// @dev Ensures that amount borrowed by credit manager in the current block does not exceed the limit
/// @dev Skipped when `maxDebtPerBlockMultiplier == type(uint8).max`
function _revertIfOutOfBorrowingLimit(uint256 amount) internal {
uint8 _maxDebtPerBlockMultiplier = maxDebtPerBlockMultiplier; // U:[FA-43]
if (_maxDebtPerBlockMultiplier == type(uint8).max) return; // U:[FA-43]
uint256 newDebtInCurrentBlock;
if (lastBlockBorrowed == block.number) {
newDebtInCurrentBlock = amount + totalBorrowedInBlock; // U:[FA-43]
} else {
newDebtInCurrentBlock = amount;
lastBlockBorrowed = uint64(block.number); // U:[FA-43]
}
if (newDebtInCurrentBlock > uint256(_maxDebtPerBlockMultiplier) * debtLimits.maxDebt) {
revert BorrowedBlockLimitException(); // U:[FA-43]
}
// the conversion is safe because of the check in `setDebtLimits`
totalBorrowedInBlock = uint128(newDebtInCurrentBlock); // U:[FA-43]
}
/// @dev Ensures that account's debt principal is within allowed range or is zero
function _revertIfOutOfDebtLimits(uint256 debt) internal view {
uint256 minDebt;
uint256 maxDebt;
// minDebt = debtLimits.minDebt;
// maxDebt = debtLimits.maxDebt;
assembly {
let data := sload(debtLimits.slot)
maxDebt := shr(128, data)
minDebt := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
if (debt != 0 && ((debt < minDebt) || (debt > maxDebt))) {
revert BorrowAmountOutOfLimitsException(); // U:[FA-44]
}
}
/// @dev Ensures that `flags` has the `permission` bit enabled
function _revertIfNoPermission(uint256 flags, uint256 permission) internal pure {
if (flags & permission == 0) {
revert NoPermissionException(permission); // U:[FA-39]
}
}
/// @dev Load-on-empty function to read inverted quoted tokens mask at most once if it's needed,
/// returns its argument if it's not empty or inverted `quotedTokensMask` from credit manager otherwise
/// @dev Non-empty inverted quoted tokens mask always has it's LSB set to 1 since underlying can't be quoted
function _quotedTokensMaskInvertedLoE(uint256 quotedTokensMaskInvertedOrEmpty) internal view returns (uint256) {
return quotedTokensMaskInvertedOrEmpty == 0
? ~ICreditManagerV3(creditManager).quotedTokensMask()
: quotedTokensMaskInvertedOrEmpty;
}
/// @dev Load-on-empty function to read price oracle at most once if it's needed,
/// returns its argument if it's not empty or `priceOracle` from credit manager otherwise
/// @dev Non-empty price oracle always has non-zero address
function _priceOracleLoE(address priceOracleOrEmpty) internal view returns (address) {
return priceOracleOrEmpty == address(0) ? ICreditManagerV3(creditManager).priceOracle() : priceOracleOrEmpty;
}
/// @dev Wraps any ETH sent in the function call and sends it back to `msg.sender`
function _wrapETH() internal {
if (msg.value != 0) {
IWETH(weth).deposit{value: msg.value}(); // U:[FA-7]
IERC20(weth).safeTransfer(msg.sender, msg.value); // U:[FA-7]
}
}
/// @dev Whether credit facade has expired (`false` if it's not expirable or expiration timestamp is not set)
function _isExpired() internal view returns (bool) {
if (!expirable) return false; // U:[FA-46]
uint40 _expirationDate = expirationDate;
return _expirationDate != 0 && block.timestamp >= _expirationDate; // U:[FA-46]
}
/// @dev Internal wrapper for `creditManager.getBorrowerOrRevert` call to reduce contract size
function _getBorrowerOrRevert(address creditAccount) internal view returns (address) {
return ICreditManagerV3(creditManager).getBorrowerOrRevert({creditAccount: creditAccount});
}
/// @dev Internal wrapper for `creditManager.getTokenMaskOrRevert` call to reduce contract size
function _getTokenMaskOrRevert(address token) internal view returns (uint256) {
return ICreditManagerV3(creditManager).getTokenMaskOrRevert(token);
}
/// @dev Internal wrapper for `creditManager.getTokenByMask` call to reduce contract size
function _getTokenByMask(uint256 mask) internal view returns (address) {
return ICreditManagerV3(creditManager).getTokenByMask(mask);
}
/// @dev Internal wrapper for `creditManager.flagsOf` call to reduce contract size
function _flagsOf(address creditAccount) internal view returns (uint16) {
return ICreditManagerV3(creditManager).flagsOf(creditAccount);
}
/// @dev Internal wrapper for `creditManager.setFlagFor` call to reduce contract size
function _setFlagFor(address creditAccount, uint16 flag, bool value) internal {
ICreditManagerV3(creditManager).setFlagFor(creditAccount, flag, value);
}
/// @dev Internal wrapper for `creditManager.setActiveCreditAccount` call to reduce contract size
function _setActiveCreditAccount(address creditAccount) internal {
ICreditManagerV3(creditManager).setActiveCreditAccount(creditAccount);
}
/// @dev Same as above but unsets active credit account
function _unsetActiveCreditAccount() internal {
_setActiveCreditAccount(INACTIVE_CREDIT_ACCOUNT_ADDRESS);
}
/// @dev Internal wrapper for `creditManager.addCollateral` call to reduce contract size
function _addCollateral(address payer, address creditAccount, address token, uint256 amount)
internal
returns (uint256 tokenMask)
{
tokenMask = ICreditManagerV3(creditManager).addCollateral({
payer: payer,
creditAccount: creditAccount,
token: token,
amount: amount
});
}
/// @dev Internal wrapper for `creditManager.enabledTokensMaskOf` call to reduce contract size
function _enabledTokensMaskOf(address creditAccount) internal view returns (uint256) {
return ICreditManagerV3(creditManager).enabledTokensMaskOf(creditAccount);
}
/// @dev Reverts if `msg.sender` is not credit configurator
function _checkCreditConfigurator() internal view {
if (msg.sender != ICreditManagerV3(creditManager).creditConfigurator()) {
revert CallerNotConfiguratorException();
}
}
/// @dev Reverts if `msg.sender` is not `creditAccount` owner
function _checkCreditAccountOwner(address creditAccount) internal view {
if (msg.sender != _getBorrowerOrRevert(creditAccount)) {
revert CallerNotCreditAccountOwnerException();
}
}
/// @dev Reverts if credit facade is expired
function _checkExpired() internal view {
if (_isExpired()) {
revert NotAllowedAfterExpirationException();
}
}
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;
import { IVersion } from "./IVersion.sol";
interface IACLExceptions {
/// @dev Thrown when attempting to delete an address from a set that is not a pausable admin
error AddressNotPausableAdminException(address addr);
/// @dev Thrown when attempting to delete an address from a set that is not a unpausable admin
error AddressNotUnpausableAdminException(address addr);
}
interface IACLEvents {
/// @dev Emits when a new admin is added that can pause contracts
event PausableAdminAdded(address indexed newAdmin);
/// @dev Emits when a Pausable admin is removed
event PausableAdminRemoved(address indexed admin);
/// @dev Emits when a new admin is added that can unpause contracts
event UnpausableAdminAdded(address indexed newAdmin);
/// @dev Emits when an Unpausable admin is removed
event UnpausableAdminRemoved(address indexed admin);
}
/// @title ACL interface
interface IACL is IACLEvents, IACLExceptions, IVersion {
/// @dev Returns true if the address is a pausable admin and false if not
/// @param addr Address to check
function isPausableAdmin(address addr) external view returns (bool);
/// @dev Returns true if the address is unpausable admin and false if not
/// @param addr Address to check
function isUnpausableAdmin(address addr) external view returns (bool);
/// @dev Returns true if an address has configurator rights
/// @param account Address to check
function isConfigurator(address account) external view returns (bool);
/// @dev Returns address of configurator
function owner() external view returns (address);
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
uint256 constant NO_VERSION_CONTROL = 0;
bytes32 constant AP_CONTRACTS_REGISTER = "CONTRACTS_REGISTER";
bytes32 constant AP_ACL = "ACL";
bytes32 constant AP_PRICE_ORACLE = "PRICE_ORACLE";
bytes32 constant AP_ACCOUNT_FACTORY = "ACCOUNT_FACTORY";
bytes32 constant AP_DATA_COMPRESSOR = "DATA_COMPRESSOR";
bytes32 constant AP_TREASURY = "TREASURY";
bytes32 constant AP_GEAR_TOKEN = "GEAR_TOKEN";
bytes32 constant AP_WETH_TOKEN = "WETH_TOKEN";
bytes32 constant AP_WETH_GATEWAY = "WETH_GATEWAY";
bytes32 constant AP_ROUTER = "ROUTER";
bytes32 constant AP_BOT_LIST = "BOT_LIST";
bytes32 constant AP_GEAR_STAKING = "GEAR_STAKING";
bytes32 constant AP_ZAPPER_REGISTER = "ZAPPER_REGISTER";
interface IAddressProviderV3Events {
/// @notice Emitted when an address is set for a contract key
event SetAddress(bytes32 indexed key, address indexed value, uint256 indexed version);
}
/// @title Address provider V3 interface
interface IAddressProviderV3 is IAddressProviderV3Events, IVersion {
function addresses(bytes32 key, uint256 _version) external view returns (address);
function getAddressOrRevert(bytes32 key, uint256 _version) external view returns (address result);
function setAddress(bytes32 key, address value, bool saveVersion) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
/// @notice Bot info
/// @param forbidden Whether bot is forbidden
/// @param specialPermissions Mapping credit manager => bot's special permissions
/// @param permissions Mapping credit manager => credit account => bot's permissions
struct BotInfo {
bool forbidden;
mapping(address => uint192) specialPermissions;
mapping(address => mapping(address => uint192)) permissions;
}
interface IBotListV3Events {
// ----------- //
// PERMISSIONS //
// ----------- //
/// @notice Emitted when new `bot`'s permissions and funding params are set for `creditAccount` in `creditManager`
event SetBotPermissions(
address indexed bot, address indexed creditManager, address indexed creditAccount, uint192 permissions
);
/// @notice Emitted when `bot`'s permissions and funding params are removed for `creditAccount` in `creditManager`
event EraseBot(address indexed bot, address indexed creditManager, address indexed creditAccount);
// ------------- //
// CONFIGURATION //
// ------------- //
/// @notice Emitted when `bot`'s forbidden status is set
event SetBotForbiddenStatus(address indexed bot, bool forbidden);
/// @notice Emitted when `bot`'s special permissions in `creditManager` are set
event SetBotSpecialPermissions(address indexed bot, address indexed creditManager, uint192 permissions);
/// @notice Emitted when `creditManager`'s approved status is set
event SetCreditManagerApprovedStatus(address indexed creditManager, bool approved);
}
/// @title Bot list V3 interface
interface IBotListV3 is IBotListV3Events, IVersion {
// ----------- //
// PERMISSIONS //
// ----------- //
function botPermissions(address bot, address creditManager, address creditAccount)
external
view
returns (uint192);
function activeBots(address creditManager, address creditAccount) external view returns (address[] memory);
function getBotStatus(address bot, address creditManager, address creditAccount)
external
view
returns (uint192 permissions, bool forbidden, bool hasSpecialPermissions);
function setBotPermissions(address bot, address creditManager, address creditAccount, uint192 permissions)
external
returns (uint256 activeBotsRemaining);
function eraseAllBotPermissions(address creditManager, address creditAccount) external;
// ------------- //
// CONFIGURATION //
// ------------- //
function botForbiddenStatus(address bot) external view returns (bool);
function botSpecialPermissions(address bot, address creditManager) external view returns (uint192);
function approvedCreditManager(address creditManager) external view returns (bool);
function setBotForbiddenStatus(address bot, bool forbidden) external;
function setBotSpecialPermissions(address bot, address creditManager, uint192 permissions) external;
function setCreditManagerApprovedStatus(address creditManager, bool approved) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
enum AllowanceAction {
FORBID,
ALLOW
}
/// @notice Struct with credit manager configuration parameters
/// @param minDebt Minimum debt amount per account
/// @param maxDebt Maximum debt amount per account
/// @param degenNFT Whether to apply Degen NFT whitelist logic
/// @param expirable Whether facade must be expirable
/// @param name Credit manager name
struct CreditManagerOpts {
uint128 minDebt;
uint128 maxDebt;
address degenNFT;
bool expirable;
string name;
}
interface ICreditConfiguratorV3Events {
// ------ //
// TOKENS //
// ------ //
/// @notice Emitted when a token is made recognizable as collateral in the credit manager
event AddCollateralToken(address indexed token);
/// @notice Emitted when a new collateral token liquidation threshold is set
event SetTokenLiquidationThreshold(address indexed token, uint16 liquidationThreshold);
/// @notice Emitted when a collateral token liquidation threshold ramping is scheduled
event ScheduleTokenLiquidationThresholdRamp(
address indexed token,
uint16 liquidationThresholdInitial,
uint16 liquidationThresholdFinal,
uint40 timestampRampStart,
uint40 timestampRampEnd
);
/// @notice Emitted when a collateral token is forbidden
event ForbidToken(address indexed token);
/// @notice Emitted when a previously forbidden collateral token is allowed
event AllowToken(address indexed token);
/// @notice Emitted when a token is made quoted
event QuoteToken(address indexed token);
// -------- //
// ADAPTERS //
// -------- //
/// @notice Emitted when a new adapter and its target contract are allowed in the credit manager
event AllowAdapter(address indexed targetContract, address indexed adapter);
/// @notice Emitted when adapter and its target contract are forbidden in the credit manager
event ForbidAdapter(address indexed targetContract, address indexed adapter);
// -------------- //
// CREDIT MANAGER //
// -------------- //
/// @notice Emitted when a new maximum number of enabled tokens is set in the credit manager
event SetMaxEnabledTokens(uint8 maxEnabledTokens);
/// @notice Emitted when new fee parameters are set in the credit manager
event UpdateFees(
uint16 feeInterest,
uint16 feeLiquidation,
uint16 liquidationPremium,
uint16 feeLiquidationExpired,
uint16 liquidationPremiumExpired
);
// -------- //
// UPGRADES //
// -------- //
/// @notice Emitted when a new price oracle is set in the credit manager
event SetPriceOracle(address indexed priceOracle);
/// @notice Emitted when a new bot list is set in the credit facade
event SetBotList(address indexed botList);
/// @notice Emitted when a new facade is connected to the credit manager
event SetCreditFacade(address indexed creditFacade);
/// @notice Emitted when credit manager's configurator contract is upgraded
event CreditConfiguratorUpgraded(address indexed creditConfigurator);
// ------------- //
// CREDIT FACADE //
// ------------- //
/// @notice Emitted when new debt principal limits are set
event SetBorrowingLimits(uint256 minDebt, uint256 maxDebt);
/// @notice Emitted when a new max debt per block multiplier is set
event SetMaxDebtPerBlockMultiplier(uint8 maxDebtPerBlockMultiplier);
/// @notice Emitted when a new max cumulative loss is set
event SetMaxCumulativeLoss(uint128 maxCumulativeLoss);
/// @notice Emitted when cumulative loss is reset to zero in the credit facade
event ResetCumulativeLoss();
/// @notice Emitted when a new expiration timestamp is set in the credit facade
event SetExpirationDate(uint40 expirationDate);
/// @notice Emitted when an address is added to the list of emergency liquidators
event AddEmergencyLiquidator(address indexed liquidator);
/// @notice Emitted when an address is removed from the list of emergency liquidators
event RemoveEmergencyLiquidator(address indexed liquidator);
}
/// @title Credit configurator V3 interface
interface ICreditConfiguratorV3 is IVersion, ICreditConfiguratorV3Events {
function addressProvider() external view returns (address);
function creditManager() external view returns (address);
function creditFacade() external view returns (address);
function underlying() external view returns (address);
// ------ //
// TOKENS //
// ------ //
function addCollateralToken(address token, uint16 liquidationThreshold) external;
function setLiquidationThreshold(address token, uint16 liquidationThreshold) external;
function rampLiquidationThreshold(
address token,
uint16 liquidationThresholdFinal,
uint40 rampStart,
uint24 rampDuration
) external;
function forbidToken(address token) external;
function allowToken(address token) external;
function makeTokenQuoted(address token) external;
// -------- //
// ADAPTERS //
// -------- //
function allowedAdapters() external view returns (address[] memory);
function allowAdapter(address adapter) external;
function forbidAdapter(address adapter) external;
// -------------- //
// CREDIT MANAGER //
// -------------- //
function setFees(
uint16 feeInterest,
uint16 feeLiquidation,
uint16 liquidationPremium,
uint16 feeLiquidationExpired,
uint16 liquidationPremiumExpired
) external;
function setMaxEnabledTokens(uint8 newMaxEnabledTokens) external;
// -------- //
// UPGRADES //
// -------- //
function setPriceOracle(uint256 newVersion) external;
function setBotList(uint256 newVersion) external;
function setCreditFacade(address newCreditFacade, bool migrateParams) external;
function upgradeCreditConfigurator(address newCreditConfigurator) external;
// ------------- //
// CREDIT FACADE //
// ------------- //
function setMinDebtLimit(uint128 newMinDebt) external;
function setMaxDebtLimit(uint128 newMaxDebt) external;
function setMaxDebtPerBlockMultiplier(uint8 newMaxDebtLimitPerBlockMultiplier) external;
function forbidBorrowing() external;
function setMaxCumulativeLoss(uint128 newMaxCumulativeLoss) external;
function resetCumulativeLoss() external;
function setExpirationDate(uint40 newExpirationDate) external;
function emergencyLiquidators() external view returns (address[] memory);
function addEmergencyLiquidator(address liquidator) external;
function removeEmergencyLiquidator(address liquidator) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {MultiCall} from "@gearbox-protocol/core-v2/contracts/libraries/MultiCall.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
import "./ICreditFacadeV3Multicall.sol";
import {AllowanceAction} from "../interfaces/ICreditConfiguratorV3.sol";
/// @notice Debt limits packed into a single slot
/// @param minDebt Minimum debt amount per credit account
/// @param maxDebt Maximum debt amount per credit account
struct DebtLimits {
uint128 minDebt;
uint128 maxDebt;
}
/// @notice Info on bad debt liquidation losses packed into a single slot
/// @param currentCumulativeLoss Current cumulative loss from bad debt liquidations
/// @param maxCumulativeLoss Max cumulative loss incurred before the facade gets paused
struct CumulativeLossParams {
uint128 currentCumulativeLoss;
uint128 maxCumulativeLoss;
}
/// @notice Collateral check params
/// @param collateralHints Optional array of token masks to check first to reduce the amount of computation
/// when known subset of account's collateral tokens covers all the debt
/// @param minHealthFactor Min account's health factor in bps in order not to revert
/// @param enabledTokensMaskAfter Bitmask of account's enabled collateral tokens after the multicall
/// @param revertOnForbiddenTokens Whether to revert on enabled forbidden tokens after the multicall
/// @param useSafePrices Whether to use safe pricing (min of main and reserve feeds) when evaluating collateral
struct FullCheckParams {
uint256[] collateralHints;
uint16 minHealthFactor;
uint256 enabledTokensMaskAfter;
bool revertOnForbiddenTokens;
bool useSafePrices;
}
interface ICreditFacadeV3Events {
/// @notice Emitted when a new credit account is opened
event OpenCreditAccount(
address indexed creditAccount, address indexed onBehalfOf, address indexed caller, uint256 referralCode
);
/// @notice Emitted when account is closed
event CloseCreditAccount(address indexed creditAccount, address indexed borrower);
/// @notice Emitted when account is liquidated
event LiquidateCreditAccount(
address indexed creditAccount, address indexed liquidator, address to, uint256 remainingFunds
);
/// @notice Emitted when account's debt is increased
event IncreaseDebt(address indexed creditAccount, uint256 amount);
/// @notice Emitted when account's debt is decreased
event DecreaseDebt(address indexed creditAccount, uint256 amount);
/// @notice Emitted when collateral is added to account
event AddCollateral(address indexed creditAccount, address indexed token, uint256 amount);
/// @notice Emitted when collateral is withdrawn from account
event WithdrawCollateral(address indexed creditAccount, address indexed token, uint256 amount, address to);
/// @notice Emitted when a multicall is started
event StartMultiCall(address indexed creditAccount, address indexed caller);
/// @notice Emitted when a call from account to an external contract is made during a multicall
event Execute(address indexed creditAccount, address indexed targetContract);
/// @notice Emitted when a multicall is finished
event FinishMultiCall();
}
/// @title Credit facade V3 interface
interface ICreditFacadeV3 is IVersion, ICreditFacadeV3Events {
function creditManager() external view returns (address);
function degenNFT() external view returns (address);
function weth() external view returns (address);
function botList() external view returns (address);
function maxDebtPerBlockMultiplier() external view returns (uint8);
function maxQuotaMultiplier() external view returns (uint256);
function expirable() external view returns (bool);
function expirationDate() external view returns (uint40);
function debtLimits() external view returns (uint128 minDebt, uint128 maxDebt);
function lossParams() external view returns (uint128 currentCumulativeLoss, uint128 maxCumulativeLoss);
function forbiddenTokenMask() external view returns (uint256);
function canLiquidateWhilePaused(address) external view returns (bool);
// ------------------ //
// ACCOUNT MANAGEMENT //
// ------------------ //
function openCreditAccount(address onBehalfOf, MultiCall[] calldata calls, uint256 referralCode)
external
payable
returns (address creditAccount);
function closeCreditAccount(address creditAccount, MultiCall[] calldata calls) external payable;
function liquidateCreditAccount(address creditAccount, address to, MultiCall[] calldata calls) external;
function multicall(address creditAccount, MultiCall[] calldata calls) external payable;
function botMulticall(address creditAccount, MultiCall[] calldata calls) external;
function setBotPermissions(address creditAccount, address bot, uint192 permissions) external;
// ------------- //
// CONFIGURATION //
// ------------- //
function setExpirationDate(uint40 newExpirationDate) external;
function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt, uint8 newMaxDebtPerBlockMultiplier) external;
function setBotList(address newBotList) external;
function setCumulativeLossParams(uint128 newMaxCumulativeLoss, bool resetCumulativeLoss) external;
function setTokenAllowance(address token, AllowanceAction allowance) external;
function setEmergencyLiquidator(address liquidator, AllowanceAction allowance) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {BalanceDelta} from "../libraries/BalancesLogic.sol";
import {RevocationPair} from "./ICreditManagerV3.sol";
// ----------- //
// PERMISSIONS //
// ----------- //
uint192 constant ADD_COLLATERAL_PERMISSION = 1;
uint192 constant INCREASE_DEBT_PERMISSION = 1 << 1;
uint192 constant DECREASE_DEBT_PERMISSION = 1 << 2;
uint192 constant ENABLE_TOKEN_PERMISSION = 1 << 3;
uint192 constant DISABLE_TOKEN_PERMISSION = 1 << 4;
uint192 constant WITHDRAW_COLLATERAL_PERMISSION = 1 << 5;
uint192 constant UPDATE_QUOTA_PERMISSION = 1 << 6;
uint192 constant REVOKE_ALLOWANCES_PERMISSION = 1 << 7;
uint192 constant EXTERNAL_CALLS_PERMISSION = 1 << 16;
uint256 constant ALL_CREDIT_FACADE_CALLS_PERMISSION = ADD_COLLATERAL_PERMISSION | WITHDRAW_COLLATERAL_PERMISSION
| INCREASE_DEBT_PERMISSION | DECREASE_DEBT_PERMISSION | ENABLE_TOKEN_PERMISSION | DISABLE_TOKEN_PERMISSION
| UPDATE_QUOTA_PERMISSION | REVOKE_ALLOWANCES_PERMISSION;
uint256 constant ALL_PERMISSIONS = ALL_CREDIT_FACADE_CALLS_PERMISSION | EXTERNAL_CALLS_PERMISSION;
// ----- //
// FLAGS //
// ----- //
/// @dev Indicates that there are enabled forbidden tokens on the account before multicall
uint256 constant FORBIDDEN_TOKENS_BEFORE_CALLS = 1 << 192;
/// @dev Indicates that external calls from credit account to adapters were made during multicall,
/// set to true on the first call to the adapter
uint256 constant EXTERNAL_CONTRACT_WAS_CALLED = 1 << 193;
/// @title Credit facade V3 multicall interface
/// @dev Unless specified otherwise, all these methods are only available in `openCreditAccount`,
/// `closeCreditAccount`, `multicall`, and, with account owner's permission, `botMulticall`
interface ICreditFacadeV3Multicall {
/// @notice Updates the price for a token with on-demand updatable price feed
/// @param token Token to push the price update for
/// @param reserve Whether to update reserve price feed or main price feed
/// @param data Data to call `updatePrice` with
/// @dev Calls of this type must be placed before all other calls in the multicall not to revert
/// @dev This method is available in all kinds of multicalls
function onDemandPriceUpdate(address token, bool reserve, bytes calldata data) external;
/// @notice Stores expected token balances (current balance + delta) after operations for a slippage check.
/// Normally, a check is performed automatically at the end of the multicall, but more fine-grained
/// behavior can be achieved by placing `storeExpectedBalances` and `compareBalances` where needed.
/// @param balanceDeltas Array of (token, minBalanceDelta) pairs, deltas are allowed to be negative
/// @dev Reverts if expected balances are already set
/// @dev This method is available in all kinds of multicalls
function storeExpectedBalances(BalanceDelta[] calldata balanceDeltas) external;
/// @notice Performs a slippage check ensuring that current token balances are greater than saved expected ones
/// @dev Resets stored expected balances
/// @dev Reverts if expected balances are not stored
/// @dev This method is available in all kinds of multicalls
function compareBalances() external;
/// @notice Adds collateral to account
/// @param token Token to add
/// @param amount Amount to add
/// @dev Requires token approval from caller to the credit manager
/// @dev This method can also be called during liquidation
function addCollateral(address token, uint256 amount) external;
/// @notice Adds collateral to account using signed EIP-2612 permit message
/// @param token Token to add
/// @param amount Amount to add
/// @param deadline Permit deadline
/// @dev `v`, `r`, `s` must be a valid signature of the permit message from caller to the credit manager
/// @dev This method can also be called during liquidation
function addCollateralWithPermit(address token, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
/// @notice Increases account's debt
/// @param amount Underlying amount to borrow
/// @dev Increasing debt is prohibited when closing an account
/// @dev Increasing debt is prohibited if it was previously updated in the same block
/// @dev The resulting debt amount must be within allowed range
/// @dev Increasing debt is prohibited if there are forbidden tokens enabled as collateral on the account
/// @dev After debt increase, total amount borrowed by the credit manager in the current block must not exceed
/// the limit defined in the facade
function increaseDebt(uint256 amount) external;
/// @notice Decreases account's debt
/// @param amount Underlying amount to repay, value above account's total debt indicates full repayment
/// @dev Decreasing debt is prohibited when opening an account
/// @dev Decreasing debt is prohibited if it was previously updated in the same block
/// @dev The resulting debt amount must be within allowed range or zero
/// @dev Full repayment brings account into a special mode that skips collateral checks and thus requires
/// an account to have no potential debt sources, e.g., all quotas must be disabled
function decreaseDebt(uint256 amount) external;
/// @notice Updates account's quota for a token
/// @param token Token to update the quota for
/// @param quotaChange Desired quota change in underlying token units (`type(int96).min` to disable quota)
/// @param minQuota Minimum resulting account's quota for token required not to revert
/// @dev Enables token as collateral if quota is increased from zero, disables if decreased to zero
/// @dev Quota increase is prohibited if there are forbidden tokens enabled as collateral on the account
/// @dev Quota update is prohibited if account has zero debt
/// @dev Resulting account's quota for token must not exceed the limit defined in the facade
function updateQuota(address token, int96 quotaChange, uint96 minQuota) external;
/// @notice Withdraws collateral from account
/// @param token Token to withdraw
/// @param amount Amount to withdraw, `type(uint256).max` to withdraw all balance
/// @param to Token recipient
/// @dev This method can also be called during liquidation
/// @dev Withdrawals are prohibited in multicalls if there are forbidden tokens enabled as collateral on the account
/// @dev Withdrawals activate safe pricing (min of main and reserve feeds) in collateral check
function withdrawCollateral(address token, uint256 amount, address to) external;
/// @notice Sets advanced collateral check parameters
/// @param collateralHints Optional array of token masks to check first to reduce the amount of computation
/// when known subset of account's collateral tokens covers all the debt
/// @param minHealthFactor Min account's health factor in bps in order not to revert, must be at least 10000
function setFullCheckParams(uint256[] calldata collateralHints, uint16 minHealthFactor) external;
/// @notice Enables token as account's collateral, which makes it count towards account's total value
/// @param token Token to enable as collateral
/// @dev Enabling forbidden tokens is prohibited
/// @dev Quoted tokens can only be enabled via `updateQuota`, this method is no-op for them
function enableToken(address token) external;
/// @notice Disables token as account's collateral
/// @param token Token to disable as collateral
/// @dev Quoted tokens can only be disabled via `updateQuota`, this method is no-op for them
function disableToken(address token) external;
/// @notice Revokes account's allowances for specified spender/token pairs
/// @param revocations Array of spender/token pairs
/// @dev Exists primarily to allow users to revoke allowances on accounts from old account factory on mainnet
function revokeAdapterAllowances(RevocationPair[] calldata revocations) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
uint8 constant BOT_PERMISSIONS_SET_FLAG = 1;
uint8 constant DEFAULT_MAX_ENABLED_TOKENS = 4;
address constant INACTIVE_CREDIT_ACCOUNT_ADDRESS = address(1);
/// @notice Debt management type
/// - `INCREASE_DEBT` borrows additional funds from the pool, updates account's debt and cumulative interest index
/// - `DECREASE_DEBT` repays debt components (quota interest and fees -> base interest and fees -> debt principal)
/// and updates all corresponding state varibles (base interest index, quota interest and fees, debt).
/// When repaying all the debt, ensures that account has no enabled quotas.
enum ManageDebtAction {
INCREASE_DEBT,
DECREASE_DEBT
}
/// @notice Collateral/debt calculation mode
/// - `GENERIC_PARAMS` returns generic data like account debt and cumulative indexes
/// - `DEBT_ONLY` is same as `GENERIC_PARAMS` but includes more detailed debt info, like accrued base/quota
/// interest and fees
/// - `FULL_COLLATERAL_CHECK_LAZY` checks whether account is sufficiently collateralized in a lazy fashion,
/// i.e. it stops iterating over collateral tokens once TWV reaches the desired target.
/// Since it may return underestimated TWV, it's only available for internal use.
/// - `DEBT_COLLATERAL` is same as `DEBT_ONLY` but also returns total value and total LT-weighted value of
/// account's tokens, this mode is used during account liquidation
/// - `DEBT_COLLATERAL_SAFE_PRICES` is same as `DEBT_COLLATERAL` but uses safe prices from price oracle
enum CollateralCalcTask {
GENERIC_PARAMS,
DEBT_ONLY,
FULL_COLLATERAL_CHECK_LAZY,
DEBT_COLLATERAL,
DEBT_COLLATERAL_SAFE_PRICES
}
struct CreditAccountInfo {
uint256 debt;
uint256 cumulativeIndexLastUpdate;
uint128 cumulativeQuotaInterest;
uint128 quotaFees;
uint256 enabledTokensMask;
uint16 flags;
uint64 lastDebtUpdate;
address borrower;
}
struct CollateralDebtData {
uint256 debt;
uint256 cumulativeIndexNow;
uint256 cumulativeIndexLastUpdate;
uint128 cumulativeQuotaInterest;
uint256 accruedInterest;
uint256 accruedFees;
uint256 totalDebtUSD;
uint256 totalValue;
uint256 totalValueUSD;
uint256 twvUSD;
uint256 enabledTokensMask;
uint256 quotedTokensMask;
address[] quotedTokens;
address _poolQuotaKeeper;
}
struct CollateralTokenData {
address token;
uint16 ltInitial;
uint16 ltFinal;
uint40 timestampRampStart;
uint24 rampDuration;
}
struct RevocationPair {
address spender;
address token;
}
interface ICreditManagerV3Events {
/// @notice Emitted when new credit configurator is set
event SetCreditConfigurator(address indexed newConfigurator);
}
/// @title Credit manager V3 interface
interface ICreditManagerV3 is IVersion, ICreditManagerV3Events {
function pool() external view returns (address);
function underlying() external view returns (address);
function creditFacade() external view returns (address);
function creditConfigurator() external view returns (address);
function addressProvider() external view returns (address);
function accountFactory() external view returns (address);
function name() external view returns (string memory);
// ------------------ //
// ACCOUNT MANAGEMENT //
// ------------------ //
function openCreditAccount(address onBehalfOf) external returns (address);
function closeCreditAccount(address creditAccount) external;
function liquidateCreditAccount(
address creditAccount,
CollateralDebtData calldata collateralDebtData,
address to,
bool isExpired
) external returns (uint256 remainingFunds, uint256 loss);
function manageDebt(address creditAccount, uint256 amount, uint256 enabledTokensMask, ManageDebtAction action)
external
returns (uint256 newDebt, uint256 tokensToEnable, uint256 tokensToDisable);
function addCollateral(address payer, address creditAccount, address token, uint256 amount)
external
returns (uint256 tokensToEnable);
function withdrawCollateral(address creditAccount, address token, uint256 amount, address to)
external
returns (uint256 tokensToDisable);
function externalCall(address creditAccount, address target, bytes calldata callData)
external
returns (bytes memory result);
function approveToken(address creditAccount, address token, address spender, uint256 amount) external;
function revokeAdapterAllowances(address creditAccount, RevocationPair[] calldata revocations) external;
// -------- //
// ADAPTERS //
// -------- //
function adapterToContract(address adapter) external view returns (address targetContract);
function contractToAdapter(address targetContract) external view returns (address adapter);
function execute(bytes calldata data) external returns (bytes memory result);
function approveCreditAccount(address token, uint256 amount) external;
function setActiveCreditAccount(address creditAccount) external;
function getActiveCreditAccountOrRevert() external view returns (address creditAccount);
// ----------------- //
// COLLATERAL CHECKS //
// ----------------- //
function priceOracle() external view returns (address);
function fullCollateralCheck(
address creditAccount,
uint256 enabledTokensMask,
uint256[] calldata collateralHints,
uint16 minHealthFactor,
bool useSafePrices
) external returns (uint256 enabledTokensMaskAfter);
function isLiquidatable(address creditAccount, uint16 minHealthFactor) external view returns (bool);
function calcDebtAndCollateral(address creditAccount, CollateralCalcTask task)
external
view
returns (CollateralDebtData memory cdd);
// ------ //
// QUOTAS //
// ------ //
function poolQuotaKeeper() external view returns (address);
function quotedTokensMask() external view returns (uint256);
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota, uint96 maxQuota)
external
returns (uint256 tokensToEnable, uint256 tokensToDisable);
// --------------------- //
// CREDIT MANAGER PARAMS //
// --------------------- //
function maxEnabledTokens() external view returns (uint8);
function fees()
external
view
returns (
uint16 feeInterest,
uint16 feeLiquidation,
uint16 liquidationDiscount,
uint16 feeLiquidationExpired,
uint16 liquidationDiscountExpired
);
function collateralTokensCount() external view returns (uint8);
function getTokenMaskOrRevert(address token) external view returns (uint256 tokenMask);
function getTokenByMask(uint256 tokenMask) external view returns (address token);
function liquidationThresholds(address token) external view returns (uint16 lt);
function ltParams(address token)
external
view
returns (uint16 ltInitial, uint16 ltFinal, uint40 timestampRampStart, uint24 rampDuration);
function collateralTokenByMask(uint256 tokenMask)
external
view
returns (address token, uint16 liquidationThreshold);
// ------------ //
// ACCOUNT INFO //
// ------------ //
function creditAccountInfo(address creditAccount)
external
view
returns (
uint256 debt,
uint256 cumulativeIndexLastUpdate,
uint128 cumulativeQuotaInterest,
uint128 quotaFees,
uint256 enabledTokensMask,
uint16 flags,
uint64 lastDebtUpdate,
address borrower
);
function getBorrowerOrRevert(address creditAccount) external view returns (address borrower);
function flagsOf(address creditAccount) external view returns (uint16);
function setFlagFor(address creditAccount, uint16 flag, bool value) external;
function enabledTokensMaskOf(address creditAccount) external view returns (uint256);
function creditAccounts() external view returns (address[] memory);
function creditAccounts(uint256 offset, uint256 limit) external view returns (address[] memory);
function creditAccountsLen() external view returns (uint256);
// ------------- //
// CONFIGURATION //
// ------------- //
function addToken(address token) external;
function setCollateralTokenData(
address token,
uint16 ltInitial,
uint16 ltFinal,
uint40 timestampRampStart,
uint24 rampDuration
) external;
function setFees(
uint16 feeInterest,
uint16 feeLiquidation,
uint16 liquidationDiscount,
uint16 feeLiquidationExpired,
uint16 liquidationDiscountExpired
) external;
function setQuotedMask(uint256 quotedTokensMask) external;
function setMaxEnabledTokens(uint8 maxEnabledTokens) external;
function setContractAllowance(address adapter, address targetContract) external;
function setCreditFacade(address creditFacade) external;
function setPriceOracle(address priceOracle) external;
function setCreditConfigurator(address creditConfigurator) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IDaiLikePermit {
function permit(
address holder,
address spender,
uint256 nonce,
uint256 expiry,
bool allowed,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;
import { IVersion } from "./IVersion.sol";
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
interface IDegenNFTV2Exceptions {
/// @dev Thrown if an access-restricted function was called by non-CreditFacade
error CreditFacadeOrConfiguratorOnlyException();
/// @dev Thrown if an access-restricted function was called by non-minter
error MinterOnlyException();
/// @dev Thrown if trying to add a burner address that is not a correct Credit Facade
error InvalidCreditFacadeException();
/// @dev Thrown if the account's balance is not sufficient for an action (usually a burn)
error InsufficientBalanceException();
}
interface IDegenNFTV2Events {
/// @dev Minted when new minter set
event NewMinterSet(address indexed);
/// @dev Minted each time when new credit facade added
event NewCreditFacadeAdded(address indexed);
/// @dev Minted each time when new credit facade added
event NewCreditFacadeRemoved(address indexed);
}
interface IDegenNFTV2 is
IDegenNFTV2Exceptions,
IDegenNFTV2Events,
IVersion,
IERC721Metadata
{
/// @dev address of the current minter
function minter() external view returns (address);
/// @dev Stores the total number of tokens on holder accounts
function totalSupply() external view returns (uint256);
/// @dev Stores the base URI for NFT metadata
function baseURI() external view returns (string memory);
/// @dev Mints a specified amount of tokens to the address
/// @param to Address the tokens are minted to
/// @param amount The number of tokens to mint
function mint(address to, uint256 amount) external;
/// @dev Burns a number of tokens from a specified address
/// @param from The address a token will be burnt from
/// @param amount The number of tokens to burn
function burn(address from, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.0;
import "../token/ERC20/IERC20.sol";
import "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*
* _Available since v4.7._
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
// ------- //
// GENERAL //
// ------- //
/// @notice Thrown on attempting to set an important address to zero address
error ZeroAddressException();
/// @notice Thrown when attempting to pass a zero amount to a funding-related operation
error AmountCantBeZeroException();
/// @notice Thrown on incorrect input parameter
error IncorrectParameterException();
/// @notice Thrown when balance is insufficient to perform an operation
error InsufficientBalanceException();
/// @notice Thrown if parameter is out of range
error ValueOutOfRangeException();
/// @notice Thrown when trying to send ETH to a contract that is not allowed to receive ETH directly
error ReceiveIsNotAllowedException();
/// @notice Thrown on attempting to set an EOA as an important contract in the system
error AddressIsNotContractException(address);
/// @notice Thrown on attempting to receive a token that is not a collateral token or was forbidden
error TokenNotAllowedException();
/// @notice Thrown on attempting to add a token that is already in a collateral list
error TokenAlreadyAddedException();
/// @notice Thrown when attempting to use quota-related logic for a token that is not quoted in quota keeper
error TokenIsNotQuotedException();
/// @notice Thrown on attempting to interact with an address that is not a valid target contract
error TargetContractNotAllowedException();
/// @notice Thrown if function is not implemented
error NotImplementedException();
// ------------------ //
// CONTRACTS REGISTER //
// ------------------ //
/// @notice Thrown when an address is expected to be a registered credit manager, but is not
error RegisteredCreditManagerOnlyException();
/// @notice Thrown when an address is expected to be a registered pool, but is not
error RegisteredPoolOnlyException();
// ---------------- //
// ADDRESS PROVIDER //
// ---------------- //
/// @notice Reverts if address key isn't found in address provider
error AddressNotFoundException();
// ----------------- //
// POOL, PQK, GAUGES //
// ----------------- //
/// @notice Thrown by pool-adjacent contracts when a credit manager being connected has a wrong pool address
error IncompatibleCreditManagerException();
/// @notice Thrown when attempting to set an incompatible successor staking contract
error IncompatibleSuccessorException();
/// @notice Thrown when attempting to vote in a non-approved contract
error VotingContractNotAllowedException();
/// @notice Thrown when attempting to unvote more votes than there are
error InsufficientVotesException();
/// @notice Thrown when attempting to borrow more than the second point on a two-point curve
error BorrowingMoreThanU2ForbiddenException();
/// @notice Thrown when a credit manager attempts to borrow more than its limit in the current block, or in general
error CreditManagerCantBorrowException();
/// @notice Thrown when attempting to connect a quota keeper to an incompatible pool
error IncompatiblePoolQuotaKeeperException();
/// @notice Thrown when the quota is outside of min/max bounds
error QuotaIsOutOfBoundsException();
// -------------- //
// CREDIT MANAGER //
// -------------- //
/// @notice Thrown on failing a full collateral check after multicall
error NotEnoughCollateralException();
/// @notice Thrown if an attempt to approve a collateral token to adapter's target contract fails
error AllowanceFailedException();
/// @notice Thrown on attempting to perform an action for a credit account that does not exist
error CreditAccountDoesNotExistException();
/// @notice Thrown on configurator attempting to add more than 255 collateral tokens
error TooManyTokensException();
/// @notice Thrown if more than the maximum number of tokens were enabled on a credit account
error TooManyEnabledTokensException();
/// @notice Thrown when attempting to execute a protocol interaction without active credit account set
error ActiveCreditAccountNotSetException();
/// @notice Thrown when trying to update credit account's debt more than once in the same block
error DebtUpdatedTwiceInOneBlockException();
/// @notice Thrown when trying to repay all debt while having active quotas
error DebtToZeroWithActiveQuotasException();
/// @notice Thrown when a zero-debt account attempts to update quota
error UpdateQuotaOnZeroDebtAccountException();
/// @notice Thrown when attempting to close an account with non-zero debt
error CloseAccountWithNonZeroDebtException();
/// @notice Thrown when value of funds remaining on the account after liquidation is insufficient
error InsufficientRemainingFundsException();
/// @notice Thrown when Credit Facade tries to write over a non-zero active Credit Account
error ActiveCreditAccountOverridenException();
// ------------------- //
// CREDIT CONFIGURATOR //
// ------------------- //
/// @notice Thrown on attempting to use a non-ERC20 contract or an EOA as a token
error IncorrectTokenContractException();
/// @notice Thrown if the newly set LT if zero or greater than the underlying's LT
error IncorrectLiquidationThresholdException();
/// @notice Thrown if borrowing limits are incorrect: minLimit > maxLimit or maxLimit > blockLimit
error IncorrectLimitsException();
/// @notice Thrown if the new expiration date is less than the current expiration date or current timestamp
error IncorrectExpirationDateException();
/// @notice Thrown if a contract returns a wrong credit manager or reverts when trying to retrieve it
error IncompatibleContractException();
/// @notice Thrown if attempting to forbid an adapter that is not registered in the credit manager
error AdapterIsNotRegisteredException();
// ------------- //
// CREDIT FACADE //
// ------------- //
/// @notice Thrown when attempting to perform an action that is forbidden in whitelisted mode
error ForbiddenInWhitelistedModeException();
/// @notice Thrown if credit facade is not expirable, and attempted aciton requires expirability
error NotAllowedWhenNotExpirableException();
/// @notice Thrown if a selector that doesn't match any allowed function is passed to the credit facade in a multicall
error UnknownMethodException();
/// @notice Thrown when trying to close an account with enabled tokens
error CloseAccountWithEnabledTokensException();
/// @notice Thrown if a liquidator tries to liquidate an account with a health factor above 1
error CreditAccountNotLiquidatableException();
/// @notice Thrown if too much new debt was taken within a single block
error BorrowedBlockLimitException();
/// @notice Thrown if the new debt principal for a credit account falls outside of borrowing limits
error BorrowAmountOutOfLimitsException();
/// @notice Thrown if a user attempts to open an account via an expired credit facade
error NotAllowedAfterExpirationException();
/// @notice Thrown if expected balances are attempted to be set twice without performing a slippage check
error ExpectedBalancesAlreadySetException();
/// @notice Thrown if attempting to perform a slippage check when excepted balances are not set
error ExpectedBalancesNotSetException();
/// @notice Thrown if balance of at least one token is less than expected during a slippage check
error BalanceLessThanExpectedException();
/// @notice Thrown when trying to perform an action that is forbidden when credit account has enabled forbidden tokens
error ForbiddenTokensException();
/// @notice Thrown when new forbidden tokens are enabled during the multicall
error ForbiddenTokenEnabledException();
/// @notice Thrown when enabled forbidden token balance is increased during the multicall
error ForbiddenTokenBalanceIncreasedException();
/// @notice Thrown when the remaining token balance is increased during the liquidation
error RemainingTokenBalanceIncreasedException();
/// @notice Thrown if `botMulticall` is called by an address that is not approved by account owner or is forbidden
error NotApprovedBotException();
/// @notice Thrown when attempting to perform a multicall action with no permission for it
error NoPermissionException(uint256 permission);
/// @notice Thrown when attempting to give a bot unexpected permissions
error UnexpectedPermissionsException();
/// @notice Thrown when a custom HF parameter lower than 10000 is passed into the full collateral check
error CustomHealthFactorTooLowException();
/// @notice Thrown when submitted collateral hint is not a valid token mask
error InvalidCollateralHintException();
// ------ //
// ACCESS //
// ------ //
/// @notice Thrown on attempting to call an access restricted function not as credit account owner
error CallerNotCreditAccountOwnerException();
/// @notice Thrown on attempting to call an access restricted function not as configurator
error CallerNotConfiguratorException();
/// @notice Thrown on attempting to call an access-restructed function not as account factory
error CallerNotAccountFactoryException();
/// @notice Thrown on attempting to call an access restricted function not as credit manager
error CallerNotCreditManagerException();
/// @notice Thrown on attempting to call an access restricted function not as credit facade
error CallerNotCreditFacadeException();
/// @notice Thrown on attempting to call an access restricted function not as controller or configurator
error CallerNotControllerException();
/// @notice Thrown on attempting to pause a contract without pausable admin rights
error CallerNotPausableAdminException();
/// @notice Thrown on attempting to unpause a contract without unpausable admin rights
error CallerNotUnpausableAdminException();
/// @notice Thrown on attempting to call an access restricted function not as gauge
error CallerNotGaugeException();
/// @notice Thrown on attempting to call an access restricted function not as quota keeper
error CallerNotPoolQuotaKeeperException();
/// @notice Thrown on attempting to call an access restricted function not as voter
error CallerNotVoterException();
/// @notice Thrown on attempting to call an access restricted function not as allowed adapter
error CallerNotAdapterException();
/// @notice Thrown on attempting to call an access restricted function not as migrator
error CallerNotMigratorException();
/// @notice Thrown when an address that is not the designated executor attempts to execute a transaction
error CallerNotExecutorException();
/// @notice Thrown on attempting to call an access restricted function not as veto admin
error CallerNotVetoAdminException();
// ------------------- //
// CONTROLLER TIMELOCK //
// ------------------- //
/// @notice Thrown when the new parameter values do not satisfy required conditions
error ParameterChecksFailedException();
/// @notice Thrown when attempting to execute a non-queued transaction
error TxNotQueuedException();
/// @notice Thrown when attempting to execute a transaction that is either immature or stale
error TxExecutedOutsideTimeWindowException();
/// @notice Thrown when execution of a transaction fails
error TxExecutionRevertedException();
/// @notice Thrown when the value of a parameter on execution is different from the value on queue
error ParameterChangedAfterQueuedTxException();
// -------- //
// BOT LIST //
// -------- //
/// @notice Thrown when attempting to set non-zero permissions for a forbidden or special bot
error InvalidBotException();
// --------------- //
// ACCOUNT FACTORY //
// --------------- //
/// @notice Thrown when trying to deploy second master credit account for a credit manager
error MasterCreditAccountAlreadyDeployedException();
/// @notice Thrown when trying to rescue funds from a credit account that is currently in use
error CreditAccountIsInUseException();
// ------------ //
// PRICE ORACLE //
// ------------ //
/// @notice Thrown on attempting to set a token price feed to an address that is not a correct price feed
error IncorrectPriceFeedException();
/// @notice Thrown on attempting to interact with a price feed for a token not added to the price oracle
error PriceFeedDoesNotExistException();
/// @notice Thrown when price feed returns incorrect price for a token
error IncorrectPriceException();
/// @notice Thrown when token's price feed becomes stale
error StalePriceException();
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IPermit2 {
struct PermitDetails {
// ERC20 token address
address token;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice The permit message signed for a single token allownce
struct PermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice Packed allowance
struct PackedAllowance {
// amount allowed
uint160 amount;
// permission expiry
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
function transferFrom(address user, address spender, uint160 amount, address token) external;
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
function allowance(address user, address token, address spender) external view returns (PackedAllowance memory);
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
pragma abicoder v1;
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";
interface IPoolV3Events {
/// @notice Emitted when depositing liquidity with referral code
event Refer(address indexed onBehalfOf, uint256 indexed referralCode, uint256 amount);
/// @notice Emitted when credit account borrows funds from the pool
event Borrow(address indexed creditManager, address indexed creditAccount, uint256 amount);
/// @notice Emitted when credit account's debt is repaid to the pool
event Repay(address indexed creditManager, uint256 borrowedAmount, uint256 profit, uint256 loss);
/// @notice Emitted when incurred loss can't be fully covered by burning treasury's shares
event IncurUncoveredLoss(address indexed creditManager, uint256 loss);
/// @notice Emitted when new interest rate model contract is set
event SetInterestRateModel(address indexed newInterestRateModel);
/// @notice Emitted when new pool quota keeper contract is set
event SetPoolQuotaKeeper(address indexed newPoolQuotaKeeper);
/// @notice Emitted when new total debt limit is set
event SetTotalDebtLimit(uint256 limit);
/// @notice Emitted when new credit manager is connected to the pool
event AddCreditManager(address indexed creditManager);
/// @notice Emitted when new debt limit is set for a credit manager
event SetCreditManagerDebtLimit(address indexed creditManager, uint256 newLimit);
/// @notice Emitted when new withdrawal fee is set
event SetWithdrawFee(uint256 fee);
}
/// @title Pool V3 interface
interface IPoolV3 is IVersion, IPoolV3Events, IERC4626, IERC20Permit {
function addressProvider() external view returns (address);
function underlyingToken() external view returns (address);
function treasury() external view returns (address);
function withdrawFee() external view returns (uint16);
function creditManagers() external view returns (address[] memory);
function availableLiquidity() external view returns (uint256);
function expectedLiquidity() external view returns (uint256);
function expectedLiquidityLU() external view returns (uint256);
// ---------------- //
// ERC-4626 LENDING //
// ---------------- //
function depositWithReferral(uint256 assets, address receiver, uint256 referralCode)
external
returns (uint256 shares);
function mintWithReferral(uint256 shares, address receiver, uint256 referralCode)
external
returns (uint256 assets);
// --------- //
// BORROWING //
// --------- //
function totalBorrowed() external view returns (uint256);
function totalDebtLimit() external view returns (uint256);
function creditManagerBorrowed(address creditManager) external view returns (uint256);
function creditManagerDebtLimit(address creditManager) external view returns (uint256);
function creditManagerBorrowable(address creditManager) external view returns (uint256 borrowable);
function lendCreditAccount(uint256 borrowedAmount, address creditAccount) external;
function repayCreditAccount(uint256 repaidAmount, uint256 profit, uint256 loss) external;
// ------------- //
// INTEREST RATE //
// ------------- //
function interestRateModel() external view returns (address);
function baseInterestRate() external view returns (uint256);
function supplyRate() external view returns (uint256);
function baseInterestIndex() external view returns (uint256);
function baseInterestIndexLU() external view returns (uint256);
function lastBaseInterestUpdate() external view returns (uint40);
// ------ //
// QUOTAS //
// ------ //
function poolQuotaKeeper() external view returns (address);
function quotaRevenue() external view returns (uint256);
function lastQuotaRevenueUpdate() external view returns (uint40);
function updateQuotaRevenue(int256 quotaRevenueDelta) external;
function setQuotaRevenue(uint256 newQuotaRevenue) external;
// ------------- //
// CONFIGURATION //
// ------------- //
function setInterestRateModel(address newInterestRateModel) external;
function setPoolQuotaKeeper(address newPoolQuotaKeeper) external;
function setTotalDebtLimit(uint256 newLimit) external;
function setCreditManagerDebtLimit(address creditManager, uint256 newLimit) external;
function setWithdrawFee(uint256 newWithdrawFee) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.0;
import { PriceFeedType } from "@gearbox-protocol/sdk-gov/contracts/PriceFeedType.sol";
/// @title Price feed interface
interface IPriceFeed {
function priceFeedType() external view returns (PriceFeedType);
function version() external view returns (uint256);
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function skipPriceCheck() external view returns (bool);
function latestRoundData()
external
view
returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80);
}
/// @title Updatable price feed interface
interface IUpdatablePriceFeed is IPriceFeed {
function updatable() external view returns (bool);
function updatePrice(bytes calldata data) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;
import { IVersion } from "./IVersion.sol";
/// @title Price oracle base interface
/// @notice Functions shared accross newer and older versions
interface IPriceOracleBase is IVersion {
function getPrice(address token) external view returns (uint256);
function convertToUSD(
uint256 amount,
address token
) external view returns (uint256);
function convertFromUSD(
uint256 amount,
address token
) external view returns (uint256);
function convert(
uint256 amount,
address tokenFrom,
address tokenTo
) external view returns (uint256);
function priceFeeds(
address token
) external view returns (address priceFeed);
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {IPriceOracleBase} from "@gearbox-protocol/core-v2/contracts/interfaces/IPriceOracleBase.sol";
struct PriceFeedParams {
address priceFeed;
uint32 stalenessPeriod;
bool skipCheck;
uint8 decimals;
bool useReserve;
bool trusted;
}
interface IPriceOracleV3Events {
/// @notice Emitted when new price feed is set for token
event SetPriceFeed(
address indexed token, address indexed priceFeed, uint32 stalenessPeriod, bool skipCheck, bool trusted
);
/// @notice Emitted when new reserve price feed is set for token
event SetReservePriceFeed(address indexed token, address indexed priceFeed, uint32 stalenessPeriod, bool skipCheck);
/// @notice Emitted when new reserve price feed status is set for a token
event SetReservePriceFeedStatus(address indexed token, bool active);
}
/// @title Price oracle V3 interface
interface IPriceOracleV3 is IPriceOracleBase, IPriceOracleV3Events {
function getPriceSafe(address token) external view returns (uint256);
function getPriceRaw(address token, bool reserve) external view returns (uint256);
function priceFeedsRaw(address token, bool reserve) external view returns (address);
function priceFeedParams(address token)
external
view
returns (address priceFeed, uint32 stalenessPeriod, bool skipCheck, uint8 decimals, bool trusted);
function safeConvertToUSD(uint256 amount, address token) external view returns (uint256);
// ------------- //
// CONFIGURATION //
// ------------- //
function setPriceFeed(address token, address priceFeed, uint32 stalenessPeriod, bool trusted) external;
function setReservePriceFeed(address token, address priceFeed, uint32 stalenessPeriod) external;
function setReservePriceFeedStatus(address token, bool active) external;
}
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;
/// @title Version interface
/// @notice Defines contract version
interface IVersion {
/// @notice Contract version
function version() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.4;
interface IWETH {
/// @dev Deposits native ETH into the contract and mints WETH
function deposit() external payable;
/// @dev Transfers WETH to another account
function transfer(address to, uint256 value) external returns (bool);
/// @dev Burns WETH from msg.sender and send back native ETH
function withdraw(uint256) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
struct MultiCall {
address target;
bytes callData;
}
library MultiCallOps {
function copyMulticall(MultiCall memory call)
internal
pure
returns (MultiCall memory)
{
return MultiCall({ target: call.target, callData: call.callData });
}
function trim(MultiCall[] memory calls)
internal
pure
returns (MultiCall[] memory trimmed)
{
uint256 len = calls.length;
if (len == 0) return calls;
uint256 foundLen;
while (calls[foundLen].target != address(0)) {
unchecked {
++foundLen;
if (foundLen == len) return calls;
}
}
if (foundLen > 0) return copy(calls, foundLen);
}
function copy(MultiCall[] memory calls, uint256 len)
internal
pure
returns (MultiCall[] memory res)
{
res = new MultiCall[](len);
for (uint256 i; i < len; ) {
res[i] = copyMulticall(calls[i]);
unchecked {
++i;
}
}
}
function clone(MultiCall[] memory calls)
internal
pure
returns (MultiCall[] memory res)
{
return copy(calls, calls.length);
}
function append(MultiCall[] memory calls, MultiCall memory newCall)
internal
pure
returns (MultiCall[] memory res)
{
uint256 len = calls.length;
res = new MultiCall[](len + 1);
for (uint256 i; i < len; ) {
res[i] = copyMulticall(calls[i]);
unchecked {
++i;
}
}
res[len] = copyMulticall(newCall);
}
function prepend(MultiCall[] memory calls, MultiCall memory newCall)
internal
pure
returns (MultiCall[] memory res)
{
uint256 len = calls.length;
res = new MultiCall[](len + 1);
res[0] = copyMulticall(newCall);
for (uint256 i = 1; i < len + 1; ) {
res[i] = copyMulticall(calls[i]);
unchecked {
++i;
}
}
}
function concat(MultiCall[] memory calls1, MultiCall[] memory calls2)
internal
pure
returns (MultiCall[] memory res)
{
uint256 len1 = calls1.length;
uint256 lenTotal = len1 + calls2.length;
if (lenTotal == calls1.length) return clone(calls1);
if (lenTotal == calls2.length) return clone(calls2);
res = new MultiCall[](lenTotal);
for (uint256 i; i < lenTotal; ) {
res[i] = (i < len1)
? copyMulticall(calls1[i])
: copyMulticall(calls2[i - len1]);
unchecked {
++i;
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^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.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: UNLICENSED
// Gearbox. Generalized leverage protocol that allows to take leverage and then use it across other DeFi protocols and platforms in a composable way.
// (c) Gearbox Foundation, 2023
pragma solidity ^0.8.17;
enum PriceFeedType {
CHAINLINK_ORACLE,
YEARN_ORACLE,
CURVE_2LP_ORACLE,
CURVE_3LP_ORACLE,
CURVE_4LP_ORACLE,
ZERO_ORACLE,
WSTETH_ORACLE,
BOUNDED_ORACLE,
COMPOSITE_ORACLE,
WRAPPED_AAVE_V2_ORACLE,
COMPOUND_V2_ORACLE,
BALANCER_STABLE_LP_ORACLE,
BALANCER_WEIGHTED_LP_ORACLE,
CURVE_CRYPTO_ORACLE,
THE_SAME_AS,
REDSTONE_ORACLE,
ERC4626_VAULT_ORACLE,
NETWORK_DEPENDENT,
CURVE_USD_ORACLE,
PYTH_ORACLE
}
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
uint8 constant NOT_ENTERED = 1;
uint8 constant ENTERED = 2;
/// @title Reentrancy guard trait
/// @notice Same as OpenZeppelin's `ReentrancyGuard` but only uses 1 byte of storage instead of 32
abstract contract ReentrancyGuardTrait {
uint8 internal _reentrancyStatus = 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.
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
_ensureNotEntered();
// Any calls to nonReentrant after this point will fail
_reentrancyStatus = ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_reentrancyStatus = NOT_ENTERED;
}
/// @dev Reverts if the contract is currently entered
/// @dev Used to cut contract size on modifiers
function _ensureNotEntered() internal view {
require(_reentrancyStatus != ENTERED, "ReentrancyGuard: reentrant call");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Revert reason forwarder.
library RevertReasonForwarder {
/// @dev Forwards latest externall call revert.
function reRevert() internal pure {
// bubble up revert reason from latest external call
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^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.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toUint248(uint256 value) internal pure returns (uint248) {
require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toUint240(uint256 value) internal pure returns (uint240) {
require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toUint232(uint256 value) internal pure returns (uint232) {
require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
return uint232(value);
}
/**
* @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
*
* _Available since v4.2._
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toUint216(uint256 value) internal pure returns (uint216) {
require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toUint208(uint256 value) internal pure returns (uint208) {
require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toUint200(uint256 value) internal pure returns (uint200) {
require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toUint192(uint256 value) internal pure returns (uint192) {
require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toUint184(uint256 value) internal pure returns (uint184) {
require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toUint176(uint256 value) internal pure returns (uint176) {
require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toUint168(uint256 value) internal pure returns (uint168) {
require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toUint160(uint256 value) internal pure returns (uint160) {
require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toUint152(uint256 value) internal pure returns (uint152) {
require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toUint144(uint256 value) internal pure returns (uint144) {
require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toUint136(uint256 value) internal pure returns (uint136) {
require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
return uint136(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
*
* _Available since v2.5._
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toUint120(uint256 value) internal pure returns (uint120) {
require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toUint112(uint256 value) internal pure returns (uint112) {
require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toUint104(uint256 value) internal pure returns (uint104) {
require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
return uint104(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
*
* _Available since v4.2._
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toUint88(uint256 value) internal pure returns (uint88) {
require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toUint80(uint256 value) internal pure returns (uint80) {
require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toUint72(uint256 value) internal pure returns (uint72) {
require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
return uint72(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
*
* _Available since v2.5._
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toUint56(uint256 value) internal pure returns (uint56) {
require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toUint48(uint256 value) internal pure returns (uint48) {
require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toUint40(uint256 value) internal pure returns (uint40) {
require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
return uint40(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
*
* _Available since v2.5._
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toUint24(uint256 value) internal pure returns (uint24) {
require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
return uint24(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
*
* _Available since v2.5._
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(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
*
* _Available since v2.5._
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*
* _Available since v3.0._
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*
* _Available since v4.7._
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*
* _Available since v4.7._
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*
* _Available since v4.7._
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*
* _Available since v4.7._
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*
* _Available since v4.7._
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*
* _Available since v4.7._
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*
* _Available since v4.7._
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*
* _Available since v4.7._
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*
* _Available since v4.7._
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*
* _Available since v4.7._
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*
* _Available since v4.7._
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*
* _Available since v4.7._
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*
* _Available since v4.7._
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*
* _Available since v4.7._
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*
* _Available since v4.7._
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
}
/**
* @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._
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*
* _Available since v4.7._
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*
* _Available since v4.7._
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*
* _Available since v4.7._
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*
* _Available since v4.7._
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*
* _Available since v4.7._
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*
* _Available since v4.7._
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*
* _Available since v4.7._
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
}
/**
* @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._
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*
* _Available since v4.7._
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*
* _Available since v4.7._
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*
* _Available since v4.7._
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
}
/**
* @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._
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*
* _Available since v4.7._
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
}
/**
* @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._
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
}
/**
* @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._
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*
* _Available since v3.0._
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
import "../interfaces/IDaiLikePermit.sol";
import "../interfaces/IPermit2.sol";
import "../interfaces/IWETH.sol";
import "../libraries/RevertReasonForwarder.sol";
/// @title Implements efficient safe methods for ERC20 interface.
library SafeERC20 {
error SafeTransferFailed();
error SafeTransferFromFailed();
error ForceApproveFailed();
error SafeIncreaseAllowanceFailed();
error SafeDecreaseAllowanceFailed();
error SafePermitBadLength();
error Permit2TransferAmountTooHigh();
address private constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
bytes4 private constant _PERMIT_LENGTH_ERROR = 0x68275857; // SafePermitBadLength.selector
uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
function safeBalanceOf(
IERC20 token,
address account
) internal view returns(uint256 tokenBalance) {
bytes4 selector = IERC20.balanceOf.selector;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(0x00, selector)
mstore(0x04, account)
let success := staticcall(gas(), token, 0x00, 0x24, 0x00, 0x20)
tokenBalance := mload(0)
if or(iszero(success), lt(returndatasize(), 0x20)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
/// @dev Ensures method do not revert or return boolean `true`, admits call to non-smart-contract.
function safeTransferFromUniversal(
IERC20 token,
address from,
address to,
uint256 amount,
bool permit2
) internal {
if (permit2) {
safeTransferFromPermit2(token, from, to, amount);
} else {
safeTransferFrom(token, from, to, amount);
}
}
/// @dev Ensures method do not revert or return boolean `true`, admits call to non-smart-contract.
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 amount
) internal {
bytes4 selector = token.transferFrom.selector;
bool success;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let data := mload(0x40)
mstore(data, selector)
mstore(add(data, 0x04), from)
mstore(add(data, 0x24), to)
mstore(add(data, 0x44), amount)
success := call(gas(), token, 0, data, 100, 0x0, 0x20)
if success {
switch returndatasize()
case 0 {
success := gt(extcodesize(token), 0)
}
default {
success := and(gt(returndatasize(), 31), eq(mload(0), 1))
}
}
}
if (!success) revert SafeTransferFromFailed();
}
/// @dev Permit2 version of safeTransferFrom above.
function safeTransferFromPermit2(
IERC20 token,
address from,
address to,
uint256 amount
) internal {
if (amount > type(uint160).max) revert Permit2TransferAmountTooHigh();
bytes4 selector = IPermit2.transferFrom.selector;
bool success;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let data := mload(0x40)
mstore(data, selector)
mstore(add(data, 0x04), from)
mstore(add(data, 0x24), to)
mstore(add(data, 0x44), amount)
mstore(add(data, 0x64), token)
success := call(gas(), _PERMIT2, 0, data, 0x84, 0x0, 0x0)
if success {
success := gt(extcodesize(_PERMIT2), 0)
}
}
if (!success) revert SafeTransferFromFailed();
}
/// @dev Ensures method do not revert or return boolean `true`, admits call to non-smart-contract.
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
if (!_makeCall(token, token.transfer.selector, to, value)) {
revert SafeTransferFailed();
}
}
/// @dev If `approve(from, to, amount)` fails, try to `approve(from, to, 0)` before retry.
function forceApprove(
IERC20 token,
address spender,
uint256 value
) internal {
if (!_makeCall(token, token.approve.selector, spender, value)) {
if (
!_makeCall(token, token.approve.selector, spender, 0) ||
!_makeCall(token, token.approve.selector, spender, value)
) {
revert ForceApproveFailed();
}
}
}
/// @dev Allowance increase with safe math check.
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 allowance = token.allowance(address(this), spender);
if (value > type(uint256).max - allowance) revert SafeIncreaseAllowanceFailed();
forceApprove(token, spender, allowance + value);
}
/// @dev Allowance decrease with safe math check.
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 allowance = token.allowance(address(this), spender);
if (value > allowance) revert SafeDecreaseAllowanceFailed();
forceApprove(token, spender, allowance - value);
}
function safePermit(IERC20 token, bytes calldata permit) internal {
if (!tryPermit(token, msg.sender, address(this), permit)) RevertReasonForwarder.reRevert();
}
function safePermit(IERC20 token, address owner, address spender, bytes calldata permit) internal {
if (!tryPermit(token, owner, spender, permit)) RevertReasonForwarder.reRevert();
}
function tryPermit(IERC20 token, bytes calldata permit) internal returns(bool success) {
return tryPermit(token, msg.sender, address(this), permit);
}
function tryPermit(IERC20 token, address owner, address spender, bytes calldata permit) internal returns(bool success) {
bytes4 permitSelector = IERC20Permit.permit.selector;
bytes4 daiPermitSelector = IDaiLikePermit.permit.selector;
bytes4 permit2Selector = IPermit2.permit.selector;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let ptr := mload(0x40)
switch permit.length
case 100 {
mstore(ptr, permitSelector)
mstore(add(ptr, 0x04), owner)
mstore(add(ptr, 0x24), spender)
// Compact IERC20Permit.permit(uint256 value, uint32 deadline, uint256 r, uint256 vs)
{ // stack too deep
let deadline := shr(224, calldataload(add(permit.offset, 0x20)))
let vs := calldataload(add(permit.offset, 0x44))
calldatacopy(add(ptr, 0x44), permit.offset, 0x20) // value
mstore(add(ptr, 0x64), sub(deadline, 1))
mstore(add(ptr, 0x84), add(27, shr(255, vs)))
calldatacopy(add(ptr, 0xa4), add(permit.offset, 0x24), 0x20) // r
mstore(add(ptr, 0xc4), shr(1, shl(1, vs)))
}
// IERC20Permit.permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0xe4, 0, 0)
}
case 72 {
mstore(ptr, daiPermitSelector)
mstore(add(ptr, 0x04), owner)
mstore(add(ptr, 0x24), spender)
// Compact IDaiLikePermit.permit(uint32 nonce, uint32 expiry, uint256 r, uint256 vs)
{ // stack too deep
let expiry := shr(224, calldataload(add(permit.offset, 0x04)))
let vs := calldataload(add(permit.offset, 0x28))
mstore(add(ptr, 0x44), shr(224, calldataload(permit.offset)))
mstore(add(ptr, 0x64), sub(expiry, 1))
mstore(add(ptr, 0x84), true)
mstore(add(ptr, 0xa4), add(27, shr(255, vs)))
calldatacopy(add(ptr, 0xc4), add(permit.offset, 0x08), 0x20) // r
mstore(add(ptr, 0xe4), shr(1, shl(1, vs)))
}
// IDaiLikePermit.permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0x104, 0, 0)
}
case 224 {
mstore(ptr, permitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length)
// IERC20Permit.permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0xe4, 0, 0)
}
case 256 {
mstore(ptr, daiPermitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length)
// IDaiLikePermit.permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0x104, 0, 0)
}
case 96 {
// Compact IPermit2.permit(uint160 amount, uint32 expiration, uint32 nonce, uint32 sigDeadline, uint256 r, uint256 vs)
mstore(ptr, permit2Selector)
mstore(add(ptr, 0x04), owner)
mstore(add(ptr, 0x24), token)
calldatacopy(add(ptr, 0x50), permit.offset, 0x14) // amount
mstore(add(ptr, 0x64), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x14))), 1))) // expiration
mstore(add(ptr, 0x84), shr(224, calldataload(add(permit.offset, 0x18)))) // nonce
mstore(add(ptr, 0xa4), spender)
mstore(add(ptr, 0xc4), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x1c))), 1))) // sigDeadline
mstore(add(ptr, 0xe4), 0x100)
mstore(add(ptr, 0x104), 0x40)
calldatacopy(add(ptr, 0x124), add(permit.offset, 0x20), 0x20) // r
calldatacopy(add(ptr, 0x144), add(permit.offset, 0x40), 0x20) // vs
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
success := call(gas(), _PERMIT2, 0, ptr, 0x164, 0, 0)
}
case 352 {
mstore(ptr, permit2Selector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length)
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
success := call(gas(), _PERMIT2, 0, ptr, 0x164, 0, 0)
}
default {
mstore(ptr, _PERMIT_LENGTH_ERROR)
revert(ptr, 4)
}
}
}
function _makeCall(
IERC20 token,
bytes4 selector,
address to,
uint256 amount
) private returns (bool success) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let data := mload(0x40)
mstore(data, selector)
mstore(add(data, 0x04), to)
mstore(add(data, 0x24), amount)
success := call(gas(), token, 0, data, 0x44, 0x0, 0x20)
if success {
switch returndatasize()
case 0 {
success := gt(extcodesize(token), 0)
}
default {
success := and(gt(returndatasize(), 31), eq(mload(0), 1))
}
}
}
}
function safeDeposit(IWETH weth, uint256 amount) internal {
if (amount > 0) {
bytes4 selector = IWETH.deposit.selector;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(0, selector)
if iszero(call(gas(), weth, amount, 0, 4, 0, 0)) {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
}
}
function safeWithdraw(IWETH weth, uint256 amount) internal {
bytes4 selector = IWETH.withdraw.selector;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(0, selector)
mstore(4, amount)
if iszero(call(gas(), weth, 0, 0, 0x24, 0, 0)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
function safeWithdrawTo(IWETH weth, uint256 amount, address to) internal {
safeWithdraw(weth, amount);
if (to != address(this)) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
if iszero(call(_RAW_CALL_GAS_LIMIT, to, amount, 0, 0, 0, 0)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
}
}
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;
import {ZeroAddressException} from "../interfaces/IExceptions.sol";
/// @title Sanity check trait
abstract contract SanityCheckTrait {
/// @dev Ensures that passed address is non-zero
modifier nonZeroAddress(address addr) {
_revertIfZeroAddress(addr);
_;
}
/// @dev Reverts if address is zero
function _revertIfZeroAddress(address addr) private pure {
if (addr == address(0)) revert ZeroAddressException();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
// EIP-2612 is Final as of 2022-11-01. This file is deprecated.
import "./IERC20Permit.sol";
{
"compilationTarget": {
"node_modules/@gearbox-protocol/core-v3/contracts/credit/CreditFacadeV3.sol": "CreditFacadeV3"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": [
":@1inch/=node_modules/@1inch/",
":@arbitrum/=node_modules/@arbitrum/",
":@chainlink/=node_modules/@chainlink/",
":@eth-optimism/=node_modules/@eth-optimism/",
":@gearbox-protocol/=node_modules/@gearbox-protocol/",
":@openzeppelin/=node_modules/@openzeppelin/",
":@redstone-finance/=node_modules/@redstone-finance/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":eth-gas-reporter/=node_modules/eth-gas-reporter/",
":forge-std/=lib/forge-std/src/"
]
}
[{"inputs":[{"internalType":"address","name":"_creditManager","type":"address"},{"internalType":"address","name":"_degenNFT","type":"address"},{"internalType":"bool","name":"_expirable","type":"bool"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BalanceLessThanExpectedException","type":"error"},{"inputs":[],"name":"BorrowAmountOutOfLimitsException","type":"error"},{"inputs":[],"name":"BorrowedBlockLimitException","type":"error"},{"inputs":[],"name":"CallerNotConfiguratorException","type":"error"},{"inputs":[],"name":"CallerNotCreditAccountOwnerException","type":"error"},{"inputs":[],"name":"CallerNotPausableAdminException","type":"error"},{"inputs":[],"name":"CallerNotUnpausableAdminException","type":"error"},{"inputs":[],"name":"CloseAccountWithEnabledTokensException","type":"error"},{"inputs":[],"name":"CreditAccountNotLiquidatableException","type":"error"},{"inputs":[],"name":"CustomHealthFactorTooLowException","type":"error"},{"inputs":[],"name":"ExpectedBalancesAlreadySetException","type":"error"},{"inputs":[],"name":"ExpectedBalancesNotSetException","type":"error"},{"inputs":[],"name":"ForbiddenInWhitelistedModeException","type":"error"},{"inputs":[],"name":"ForbiddenTokenBalanceIncreasedException","type":"error"},{"inputs":[],"name":"ForbiddenTokenEnabledException","type":"error"},{"inputs":[],"name":"ForbiddenTokensException","type":"error"},{"inputs":[],"name":"IncorrectParameterException","type":"error"},{"inputs":[],"name":"InvalidCollateralHintException","type":"error"},{"inputs":[{"internalType":"uint256","name":"permission","type":"uint256"}],"name":"NoPermissionException","type":"error"},{"inputs":[],"name":"NotAllowedAfterExpirationException","type":"error"},{"inputs":[],"name":"NotAllowedWhenNotExpirableException","type":"error"},{"inputs":[],"name":"NotApprovedBotException","type":"error"},{"inputs":[],"name":"PriceFeedDoesNotExistException","type":"error"},{"inputs":[],"name":"RemainingTokenBalanceIncreasedException","type":"error"},{"inputs":[],"name":"SafeTransferFailed","type":"error"},{"inputs":[],"name":"TargetContractNotAllowedException","type":"error"},{"inputs":[],"name":"UnexpectedPermissionsException","type":"error"},{"inputs":[],"name":"UnknownMethodException","type":"error"},{"inputs":[],"name":"ZeroAddressException","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AddCollateral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"borrower","type":"address"}],"name":"CloseCreditAccount","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DecreaseDebt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"targetContract","type":"address"}],"name":"Execute","type":"event"},{"anonymous":false,"inputs":[],"name":"FinishMultiCall","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"IncreaseDebt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"liquidator","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"remainingFunds","type":"uint256"}],"name":"LiquidateCreditAccount","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newController","type":"address"}],"name":"NewController","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"onBehalfOf","type":"address"},{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"referralCode","type":"uint256"}],"name":"OpenCreditAccount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"caller","type":"address"}],"name":"StartMultiCall","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creditAccount","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"WithdrawCollateral","type":"event"},{"inputs":[],"name":"acl","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"botList","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditAccount","type":"address"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct MultiCall[]","name":"calls","type":"tuple[]"}],"name":"botMulticall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"canLiquidateWhilePaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditAccount","type":"address"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct MultiCall[]","name":"calls","type":"tuple[]"}],"name":"closeCreditAccount","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"creditManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debtLimits","outputs":[{"internalType":"uint128","name":"minDebt","type":"uint128"},{"internalType":"uint128","name":"maxDebt","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"degenNFT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"expirable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"expirationDate","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forbiddenTokenMask","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditAccount","type":"address"},{"internalType":"address","name":"to","type":"address"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct MultiCall[]","name":"calls","type":"tuple[]"}],"name":"liquidateCreditAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lossParams","outputs":[{"internalType":"uint128","name":"currentCumulativeLoss","type":"uint128"},{"internalType":"uint128","name":"maxCumulativeLoss","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxDebtPerBlockMultiplier","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxQuotaMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditAccount","type":"address"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct MultiCall[]","name":"calls","type":"tuple[]"}],"name":"multicall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"onBehalfOf","type":"address"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct MultiCall[]","name":"calls","type":"tuple[]"},{"internalType":"uint256","name":"referralCode","type":"uint256"}],"name":"openCreditAccount","outputs":[{"internalType":"address","name":"creditAccount","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newBotList","type":"address"}],"name":"setBotList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"creditAccount","type":"address"},{"internalType":"address","name":"bot","type":"address"},{"internalType":"uint192","name":"permissions","type":"uint192"}],"name":"setBotPermissions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newController","type":"address"}],"name":"setController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"newMaxCumulativeLoss","type":"uint128"},{"internalType":"bool","name":"resetCumulativeLoss","type":"bool"}],"name":"setCumulativeLossParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"newMinDebt","type":"uint128"},{"internalType":"uint128","name":"newMaxDebt","type":"uint128"},{"internalType":"uint8","name":"newMaxDebtPerBlockMultiplier","type":"uint8"}],"name":"setDebtLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"liquidator","type":"address"},{"internalType":"enum AllowanceAction","name":"allowance","type":"uint8"}],"name":"setEmergencyLiquidator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint40","name":"newExpirationDate","type":"uint40"}],"name":"setExpirationDate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum AllowanceAction","name":"allowance","type":"uint8"}],"name":"setTokenAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]