Contract Name:
BalancerV3Worker
Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2025
pragma solidity ^0.8.23;
import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol";
import {RouterComponentConfigurator} from "../helpers/RouterComponentConfigurator.sol";
import {IBalancerV3RouterAdapter} from
"@gearbox-protocol/integrations-v3/contracts/interfaces/balancer/IBalancerV3RouterAdapter.sol";
import {IBalancerV3Pool} from "@gearbox-protocol/integrations-v3/contracts/integrations/balancer/IBalancerV3Pool.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {MultiCall} from "../lib/MultiCall.sol";
import {Graph, Vertex, Edge, GraphOps} from "../data/Graph.sol";
import {IWorkerNonLinear} from "../interfaces/IWorker.sol";
enum ActionType {
SWAP,
DEPOSIT_SINGLE_TOKEN,
WITHDRAW_SINGLE_TOKEN
}
struct PoolAction {
ActionType actionType;
IERC20 tokenIn;
IERC20 tokenOut;
uint256 amountIn;
}
interface IBalancerV3MultiActionQueries {
function queryActions(address pool, PoolAction[] calldata actions) external returns (uint256[] memory amountsOut);
}
contract BalancerV3Worker is IWorkerNonLinear, RouterComponentConfigurator {
using GraphOps for Graph;
bytes32 public constant override contractType = "RC::BALANCER_V3_WORKER";
uint256 public constant override version = 3_10;
bool public constant override isNonLinear = true;
IBalancerV3MultiActionQueries public immutable queries;
constructor(address _router, address _queries) RouterComponentConfigurator(_router) {
queries = IBalancerV3MultiActionQueries(_queries);
}
function getMulticalls(Edge memory edge, Graph memory graph)
external
view
returns (MultiCall[] memory calls, Graph memory)
{
address pool = abi.decode(edge.extraData, (address));
calls = new MultiCall[](1);
Vertex memory vertex = graph.getVertex(edge.tokenIn);
if (vertex.balance == edge.amountInTotal) {
edge.amountInTotal -= 1;
}
uint256 amount = vertex.balance - edge.amountInTotal;
vertex.balance -= edge.amountInTotal;
graph.getVertex(edge.tokenOut).balance += edge.amountOutTotal;
calls[0] = MultiCall({
target: edge.adapter,
callData: abi.encodeCall(
IBalancerV3RouterAdapter.swapSingleTokenDiffIn,
(pool, IERC20(edge.tokenIn), IERC20(edge.tokenOut), amount, 0, block.timestamp + 3600)
)
});
return (calls, graph);
}
function _hasInputThroughPool(Edge memory edge, Graph memory graph) internal view returns (bool) {
address edgePool = abi.decode(edge.extraData, (address));
Edge[] memory workerEdges = graph.getWorkerEdges(address(this));
for (uint256 i = 0; i < workerEdges.length; i++) {
address pool = abi.decode(workerEdges[i].extraData, (address));
if (
pool == edgePool && (workerEdges[i].tokenOut == edge.tokenIn || workerEdges[i].tokenIn == edge.tokenOut)
&& workerEdges[i].amountOutCurrent > 1
) {
return true;
}
}
return false;
}
function getEdgeAmountOutCurrent(Edge memory edge, Graph memory graph) external returns (uint256) {
if (_hasInputThroughPool(edge, graph)) return 0;
address pool = abi.decode(edge.extraData, (address));
Edge[] memory workerEdges = graph.getWorkerEdges(address(this));
uint256 swapCount = 0;
for (uint256 i = 0; i < workerEdges.length; i++) {
address currentPool = abi.decode(workerEdges[i].extraData, (address));
if (currentPool == pool) {
if (workerEdges[i].tokenIn == edge.tokenIn && workerEdges[i].tokenOut == edge.tokenOut) {
swapCount++;
} else if (workerEdges[i].amountInTotal > 0) {
swapCount++;
}
}
}
PoolAction[] memory actions = new PoolAction[](swapCount);
uint256 actionIndex = 0;
uint256 edgeActionIndex;
for (uint256 i = 0; i < workerEdges.length; i++) {
address currentPool = abi.decode(workerEdges[i].extraData, (address));
if (currentPool == pool) {
if (workerEdges[i].tokenIn == edge.tokenIn && workerEdges[i].tokenOut == edge.tokenOut) {
actions[actionIndex] = PoolAction({
actionType: ActionType.SWAP,
tokenIn: IERC20(edge.tokenIn),
tokenOut: IERC20(edge.tokenOut),
amountIn: edge.amountInCurrent + edge.amountInTotal
});
edgeActionIndex = actionIndex;
actionIndex++;
} else if (workerEdges[i].amountInTotal > 0) {
actions[actionIndex] = PoolAction({
actionType: ActionType.SWAP,
tokenIn: IERC20(workerEdges[i].tokenIn),
tokenOut: IERC20(workerEdges[i].tokenOut),
amountIn: workerEdges[i].amountInTotal
});
actionIndex++;
}
}
}
try queries.queryActions(pool, actions) returns (uint256[] memory amountsOut) {
uint256 amountOut = amountsOut[edgeActionIndex];
return amountOut > edge.amountOutTotal ? amountOut - edge.amountOutTotal : 0;
} catch {
return 0;
}
}
function buildEdges(Graph memory graph, address adapter) external view returns (Graph memory) {
address[] memory pools = IBalancerV3RouterAdapter(adapter).getAllowedPools();
for (uint256 i = 0; i < pools.length; ++i) {
IERC20[] memory tokens = IBalancerV3Pool(pools[i]).getTokens();
// Create edges between all token pairs in the pool
for (uint256 j = 0; j < tokens.length; ++j) {
for (uint256 k = j + 1; k < tokens.length; ++k) {
graph.appendEdge(
address(tokens[j]), address(tokens[k]), abi.encode(pools[i]), adapter, address(this)
);
graph.appendEdge(
address(tokens[k]), address(tokens[j]), abi.encode(pools[i]), adapter, address(this)
);
}
}
}
return graph;
}
function trimSpecialVertex(Edge memory, Graph memory, address) external pure returns (bool) {
return false;
}
function processClaims(Edge memory, Graph memory graph, address)
external
pure
returns (Graph memory, MultiCall[] memory)
{
return (graph, new MultiCall[](0));
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {IVersion} from "./IVersion.sol";
import {IStateSerializer} from "./IStateSerializer.sol";
/// @title Adapter interface
/// @notice Generic interface for an adapter that can be used to interact with external protocols.
/// Adapters can be assumed to be non-malicious since they are developed by Gearbox DAO.
/// @dev Adapters must have type `ADAPTER::{POSTFIX}`
interface IAdapter is IVersion, IStateSerializer {
/// @notice Credit manager this adapter is connected to
/// @dev Assumed to be an immutable state variable
function creditManager() external view returns (address);
/// @notice Target contract adapter helps to interact with
/// @dev Assumed to be an immutable state variable
function targetContract() external view returns (address);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2025
pragma solidity ^0.8.10;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol";
import {IGearboxRouter} from "../interfaces/IGearboxRouter.sol";
contract RouterComponentConfigurator {
IGearboxRouter public router;
event NewRouter(address indexed);
error RouterOnlyException();
error RouterOwnerOnlyException();
error FutureRouterOnlyException();
error MigrationErrorException();
constructor(address _router) {
router = IGearboxRouter(_router);
}
modifier routerOwnerOnly() {
if (!router.isRouterConfigurator(msg.sender)) {
revert RouterOwnerOnlyException();
}
_;
}
modifier routerOnly() {
if (msg.sender != address(router)) revert RouterOnlyException();
_;
}
modifier futureRouterOnly() {
if (msg.sender != router.futureRouter()) {
revert FutureRouterOnlyException();
}
_;
}
function migrate() external futureRouterOnly {
if (msg.sender != address(router)) {
router = IGearboxRouter(msg.sender);
emit NewRouter(msg.sender);
}
}
function updateRouter(address newRouter) external virtual routerOwnerOnly {
if (newRouter != address(router)) {
if (!IGearboxRouter(newRouter).isRouterConfigurator(msg.sender)) {
revert MigrationErrorException();
}
router = IGearboxRouter(newRouter);
emit NewRouter(newRouter);
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;
import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IBalancerV3RouterAdapterEvents {
/// @notice Emitted when pool status is changed
event SetPoolStatus(address indexed pool, bool allowed);
}
interface IBalancerV3RouterAdapterExceptions {
/// @notice Thrown when pool and status array lengths do not match
error InvalidLengthException();
/// @notice Thrown when trying to swap through a non-allowed pool
error InvalidPoolException();
}
/// @title Balancer V3 Router adapter interface
interface IBalancerV3RouterAdapter is IAdapter, IBalancerV3RouterAdapterEvents, IBalancerV3RouterAdapterExceptions {
function swapSingleTokenExactIn(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountIn,
uint256 minAmountOut,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external returns (bool);
function swapSingleTokenDiffIn(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 leftoverAmount,
uint256 rateMinRAY,
uint256 deadline
) external returns (bool);
// ------------- //
// CONFIGURATION //
// ------------- //
/// @notice Returns whether the pool is allowed to be traded through the adapter
function isPoolAllowed(address pool) external view returns (bool);
/// @notice Returns the list of all pools that were ever allowed in this adapter
function getAllowedPools() external view returns (address[] memory pools);
/// @notice Sets status for a batch of pools
function setPoolStatusBatch(address[] calldata pools, bool[] calldata statuses) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IBalancerV3Pool {
function getTokens() external view returns (IERC20[] memory);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// 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);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2025
pragma solidity ^0.8.17;
import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
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 - 1]);
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;
}
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2025
pragma solidity ^0.8.10;
import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
import {MultiCallOps} from "../lib/MultiCall.sol";
import {AddressList} from "../helpers/AddressList.sol";
import {IWorkerBase, IWorker, IWorkerNonLinear} from "../interfaces/IWorker.sol";
struct Edge {
uint256 id;
address tokenIn;
address tokenOut;
address adapter;
address worker;
bytes extraData;
uint256 amountInTotal;
uint256 amountOutTotal;
uint256 amountInCurrent;
uint256 amountOutCurrent;
}
struct Vertex {
address token;
uint256 balance;
uint256 leftoverBalance;
uint256 numSplits;
uint256 currentOptimalEdge;
}
struct Graph {
Vertex[] vertices;
Edge[] edges;
}
library EdgeOps {
function includes(Edge[] memory edges, Edge memory edge) internal pure returns (bool) {
uint256 len = edges.length;
for (uint256 i = 0; i < len; ++i) {
if (edges[i].id == edge.id) return true;
}
return false;
}
function append(Edge[] memory edges, Edge memory newEdge) internal pure returns (Edge[] memory res) {
uint256 len = edges.length;
res = new Edge[](len + 1);
for (uint256 i = 0; i < len; ++i) {
res[i] = edges[i];
}
res[len] = newEdge;
}
function prepend(Edge[] memory edges, Edge memory newEdge) internal pure returns (Edge[] memory res) {
uint256 len = edges.length;
res = new Edge[](len + 1);
for (uint256 i = 0; i < len; ++i) {
res[i + 1] = edges[i];
}
res[0] = newEdge;
}
function trim(Edge[] memory edges) internal pure returns (Edge[] memory res) {
uint256 len = edges.length;
uint256 foundLen = 0;
for (uint256 i = 0; i < len; ++i) {
if (edges[i].id == 0) {
break;
}
++foundLen;
}
res = new Edge[](foundLen);
for (uint256 i = 0; i < foundLen; ++i) {
res[i] = edges[i];
}
}
}
library GraphOps {
using AddressList for address[];
using MultiCallOps for MultiCall[];
using EdgeOps for Edge[];
function isVertex(Graph memory g, address token) internal pure returns (bool) {
uint256 len = g.vertices.length;
for (uint256 i = 0; i < len; ++i) {
if (g.vertices[i].token == token) return true;
}
return false;
}
function getVertex(Graph memory g, address token) internal pure returns (Vertex memory vertex) {
uint256 len = g.vertices.length;
for (uint256 i = 0; i < len; ++i) {
if (g.vertices[i].token == token) return g.vertices[i];
}
}
function getBalance(Graph memory g, address token) internal pure returns (uint256) {
return getVertex(g, token).balance;
}
function getBalanceDiff(Graph memory g, address token) internal pure returns (uint256) {
Vertex memory v = getVertex(g, token);
if (v.balance < v.leftoverBalance) return 0;
return v.balance - v.leftoverBalance;
}
function getTokenOptimalAmount(Graph memory g, address token) internal pure returns (uint256) {
Vertex memory v = getVertex(g, token);
if (v.currentOptimalEdge == 0) return 0;
Edge memory e = getEdgeById(g, v.currentOptimalEdge);
return e.amountOutCurrent;
}
function getEdgesIn(Graph memory g, address token) internal pure returns (Edge[] memory edgesIn) {
uint256 k = 0;
uint256 len = g.edges.length;
edgesIn = new Edge[](len);
for (uint256 i = 0; i < len; ++i) {
if (g.edges[i].tokenOut == token) {
edgesIn[k] = g.edges[i];
++k;
}
}
edgesIn = edgesIn.trim();
}
function getEdgesOut(Graph memory g, address token) internal pure returns (Edge[] memory edgesOut) {
uint256 k = 0;
uint256 len = g.edges.length;
edgesOut = new Edge[](len);
for (uint256 i = 0; i < len; ++i) {
if (g.edges[i].tokenIn == token) {
edgesOut[k] = g.edges[i];
++k;
}
}
edgesOut = edgesOut.trim();
}
function getEdgeById(Graph memory g, uint256 id) internal pure returns (Edge memory) {
uint256 lenE = g.edges.length;
for (uint256 i = 0; i < lenE; ++i) {
if (g.edges[i].id == id) return g.edges[i];
}
revert("Edge not found");
}
function getWorkerEdges(Graph memory g, address worker) internal pure returns (Edge[] memory workerEdges) {
uint256 k = 0;
uint256 len = g.edges.length;
workerEdges = new Edge[](len);
for (uint256 i = 0; i < len; ++i) {
if (g.edges[i].worker == worker) {
workerEdges[k] = g.edges[i];
++k;
}
}
workerEdges = workerEdges.trim();
}
function activateVertex(Graph memory g, address token, uint256 amount) internal pure {
Vertex memory vertex = getVertex(g, token);
vertex.balance = amount;
vertex.numSplits = vertex.numSplits == 0 ? 1 : vertex.numSplits;
}
function removeVertex(Graph memory g, address token) internal pure {
uint256 k = 0;
uint256 lenV = g.vertices.length;
uint256 lenE = g.edges.length;
for (uint256 i = 0; i < lenV; ++i) {
if (g.vertices[i].token == token) {
g.vertices[i] = g.vertices[lenV - 1];
Vertex[] memory vertices = g.vertices;
assembly {
mstore(vertices, sub(lenV, 1))
}
break;
}
}
Edge[] memory newEdges = new Edge[](lenE);
for (uint256 i = 0; i < lenE; ++i) {
if (g.edges[i].tokenIn != token && g.edges[i].tokenOut != token) {
newEdges[k] = g.edges[i];
++k;
}
}
g.edges = newEdges.trim();
}
function appendEdge(
Graph memory g,
address tokenIn,
address tokenOut,
bytes memory extraData,
address adapter,
address worker
) internal pure {
uint256 len = g.edges.length;
if (!isVertex(g, tokenIn) || !isVertex(g, tokenOut)) return;
Edge memory newEdge;
newEdge.id = len + 1;
newEdge.tokenIn = tokenIn;
newEdge.tokenOut = tokenOut;
newEdge.extraData = extraData;
newEdge.adapter = adapter;
newEdge.worker = worker;
g.edges = g.edges.append(newEdge);
}
function getNeighborsOf(Graph memory g, address token) internal pure returns (address[] memory neighbors) {
Edge[] memory edgesIn = getEdgesIn(g, token);
Edge[] memory edgesOut = getEdgesOut(g, token);
uint256 len = edgesIn.length;
for (uint256 i = 0; i < len; ++i) {
address tokenIn = edgesIn[i].tokenIn;
if (!neighbors.includes(tokenIn)) neighbors = neighbors.append(tokenIn);
}
len = edgesOut.length;
for (uint256 i = 0; i < len; ++i) {
address tokenOut = edgesOut[i].tokenOut;
if (!neighbors.includes(tokenOut)) neighbors = neighbors.append(tokenOut);
}
}
function hasOptimalOrCommittedOppositeEdge(Graph memory g, Edge memory edge) internal pure returns (bool) {
uint256 len = g.edges.length;
Vertex memory v = getVertex(g, edge.tokenIn);
for (uint256 i = 0; i < len; ++i) {
if (
g.edges[i].tokenIn == edge.tokenOut && g.edges[i].tokenOut == edge.tokenIn
&& (v.currentOptimalEdge == g.edges[i].id || g.edges[i].amountOutTotal > 1)
) return true;
}
return false;
}
function processClaims(Graph memory g, address token, address creditAccount)
internal
view
returns (Graph memory, MultiCall[] memory)
{
Edge[] memory edgesIn = getEdgesIn(g, token);
MultiCall[] memory calls;
for (uint256 i = 0; i < edgesIn.length; ++i) {
MultiCall[] memory callsCurrent;
(g, callsCurrent) = IWorkerBase(edgesIn[i].worker).processClaims(edgesIn[i], g, creditAccount);
calls = calls.concat(callsCurrent);
}
return (g, calls);
}
function trimUselessVerticesRec(Graph memory g, address token, address targetToken, address[] memory walked)
internal
view
returns (address[] memory)
{
if (!walked.includes(token)) walked = walked.append(token);
Edge[] memory edgesIn = getEdgesIn(g, token);
uint256 len = edgesIn.length;
for (uint256 i = 0; i < len; ++i) {
if (!walked.includes(edgesIn[i].tokenIn)) {
walked = trimUselessVerticesRec(g, edgesIn[i].tokenIn, targetToken, walked);
}
}
address[] memory neighbors = getNeighborsOf(g, token);
bool trimSpecial = false;
/// This is a special procedure that removes vertices which cannot be detected by a generic algorithm
/// E.g., a Curve LP is clearly redundant if it only connects to underlying coins and is non-target,
/// but will not be detected by the general pass, since it always has at least 2 underlyings as neighbors
for (uint256 i = 0; i < len; ++i) {
if (IWorkerBase(edgesIn[i].worker).trimSpecialVertex(edgesIn[i], g, targetToken)) {
trimSpecial = true;
break;
}
}
if (trimSpecial || (neighbors.length == 1 && token != targetToken && getBalanceDiff(g, token) <= 1)) {
removeVertex(g, token);
}
return walked;
}
function trimUselessVertices(Graph memory g, address targetToken) internal view {
address[] memory walked = trimUselessVerticesRec(g, targetToken, targetToken, new address[](0));
address[] memory unreachable = new address[](0);
uint256 lenV = g.vertices.length;
for (uint256 i = 0; i < lenV; ++i) {
if (!walked.includes(g.vertices[i].token)) unreachable.append(g.vertices[i].token);
}
uint256 len = unreachable.length;
for (uint256 i = 0; i < len; ++i) {
if (getBalanceDiff(g, unreachable[i]) > 1) {
revert("Error: Unreachable token has non-zero balance diff (is input token)");
}
removeVertex(g, unreachable[i]);
}
}
function clearCurrents(Graph memory g) internal pure {
uint256 lenV = g.vertices.length;
uint256 lenE = g.edges.length;
for (uint256 i = 0; i < lenV; ++i) {
g.vertices[i].currentOptimalEdge = 0;
}
for (uint256 i = 0; i < lenE; ++i) {
g.edges[i].amountInCurrent = 0;
g.edges[i].amountOutCurrent = 0;
}
}
function computeOptimalAmounts(Graph memory g, address inputToken, address targetToken, uint256 amount) internal {
clearCurrents(g);
uint256 lenV = g.vertices.length;
uint256 lenE = g.edges.length;
for (uint256 i = 0; i < lenV - 1; ++i) {
bool iterationRelaxed = false;
for (uint256 j = 0; j < lenE; ++j) {
if (
g.edges[j].tokenIn == targetToken || g.edges[j].tokenOut == inputToken
|| hasOptimalOrCommittedOppositeEdge(g, g.edges[j])
) {
continue;
}
uint256 amountInNew;
if (g.edges[j].tokenIn == inputToken) {
amountInNew = amount;
} else {
amountInNew = getTokenOptimalAmount(g, g.edges[j].tokenIn);
}
if (g.edges[j].amountInCurrent == amountInNew) {
continue;
} else {
g.edges[j].amountInCurrent = amountInNew;
}
if (g.edges[j].amountInCurrent != 0) {
address worker = g.edges[j].worker;
g.edges[j].amountOutCurrent = IWorkerBase(worker).isNonLinear()
? IWorkerNonLinear(worker).getEdgeAmountOutCurrent(g.edges[j], g)
: IWorker(worker).getEdgeAmountOutCurrent(g.edges[j]);
}
uint256 currentOptimalAmount = getTokenOptimalAmount(g, g.edges[j].tokenOut);
if (g.edges[j].amountOutCurrent > currentOptimalAmount) {
getVertex(g, g.edges[j].tokenOut).currentOptimalEdge = g.edges[j].id;
iterationRelaxed = true;
}
}
if (!iterationRelaxed) break;
}
address currentToken = targetToken;
while (currentToken != inputToken) {
Vertex memory vertex = getVertex(g, currentToken);
if (vertex.currentOptimalEdge == 0) {
revert(
"Error: no optimal edge found for token. The swapped amount may be too small to yield a positive output"
);
}
Edge memory edge = getEdgeById(g, vertex.currentOptimalEdge);
edge.amountInTotal += edge.amountInCurrent;
edge.amountOutTotal += edge.amountOutCurrent;
currentToken = edge.tokenIn;
}
}
function collectMulticalls(Graph memory g, address targetToken)
internal
returns (Graph memory, MultiCall[] memory calls)
{
Edge[] memory orderedEdges = new Edge[](0);
address[] memory queue = new address[](1);
queue[0] = targetToken;
while (queue.length > 0) {
address token = queue[0];
queue = queue.popFirst();
Edge[] memory edgesOut = getEdgesOut(g, token);
{
bool defer = false;
for (uint256 i = 0; i < edgesOut.length; ++i) {
if (
edgesOut[i].amountOutTotal > 1 && !queue.includes(edgesOut[i].tokenOut)
&& !orderedEdges.includes(edgesOut[i])
) {
defer = true;
break;
}
}
if (defer) {
queue = queue.append(token);
continue;
}
}
for (uint256 i = 0; i < edgesOut.length; ++i) {
if (edgesOut[i].amountOutTotal > 1 && !orderedEdges.includes(edgesOut[i])) {
orderedEdges = orderedEdges.prepend(edgesOut[i]);
}
}
Edge[] memory edgesIn = getEdgesIn(g, token);
for (uint256 i = 0; i < edgesIn.length; ++i) {
if (edgesIn[i].amountOutTotal > 1 && !orderedEdges.includes(edgesIn[i])) {
orderedEdges = orderedEdges.prepend(edgesIn[i]);
queue = queue.append(edgesIn[i].tokenIn);
}
}
}
for (uint256 i = 0; i < orderedEdges.length; ++i) {
(calls, g) = collectEdgeMulticalls(g, orderedEdges[i], calls);
}
return (g, calls);
}
function collectEdgeMulticalls(Graph memory g, Edge memory edge, MultiCall[] memory calls)
internal
returns (MultiCall[] memory, Graph memory)
{
Vertex memory vertexIn = getVertex(g, edge.tokenIn);
MultiCall[] memory newCalls;
if (IWorkerBase(edge.worker).isNonLinear()) {
(newCalls, g) = IWorkerNonLinear(edge.worker).getMulticalls(edge, g);
} else {
if (vertexIn.balance == edge.amountInTotal) {
edge.amountInTotal -= 1;
}
newCalls = IWorker(edge.worker).getMulticalls(edge, vertexIn.balance);
vertexIn.balance -= edge.amountInTotal;
getVertex(g, edge.tokenOut).balance += edge.amountOutTotal;
}
calls = calls.concat(newCalls);
return (calls, g);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2021
pragma solidity ^0.8.10;
import {IRouterComponent} from "./IRouterComponent.sol";
import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
import {Graph, Edge} from "../data/Graph.sol";
interface IWorkerBase is IRouterComponent {
function isNonLinear() external pure returns (bool);
function buildEdges(Graph memory graph, address adapter) external view returns (Graph memory);
function trimSpecialVertex(Edge memory edge, Graph memory graph, address targetToken)
external
view
returns (bool);
function processClaims(Edge memory edge, Graph memory graph, address creditAccount)
external
view
returns (Graph memory, MultiCall[] memory);
}
interface IWorker is IWorkerBase {
function getEdgeAmountOutCurrent(Edge memory edge) external returns (uint256 amountOutCurrent);
function getMulticalls(Edge memory edge, uint256 currentBalance) external returns (MultiCall[] memory calls);
}
interface IWorkerNonLinear is IWorkerBase {
function getEdgeAmountOutCurrent(Edge memory edge, Graph memory graph)
external
returns (uint256 amountOutCurrent);
function getMulticalls(Edge memory edge, Graph memory graph) external returns (MultiCall[] memory, Graph memory);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
/// @title Version interface
/// @notice Defines contract version and type
interface IVersion {
/// @notice Contract version
function version() external view returns (uint256);
/// @notice Contract type
function contractType() external view returns (bytes32);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
/// @title State serializer interface
/// @notice Generic interface for a contract that can serialize its state into a bytes array
interface IStateSerializer {
/// @notice Serializes the state of the contract into a bytes array `serializedData`
function serialize() external view returns (bytes memory serializedData);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2021
pragma solidity ^0.8.10;
import {IVersion} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IVersion.sol";
import {RouterResult} from "../data/RouterResult.sol";
struct TokenData {
address token;
uint256 balance;
uint256 leftoverBalance;
uint256 numSplits;
bool claimRewards;
}
error PathNotFoundExceptionTyped(uint8 ttIn, address tokenOut);
error PathNotFoundException(address tokenIn, address tokenOut);
error PathToTargetNotFound(address tokenOut);
error UnsupportedAdapterType();
error UnsupportedRouterComponent(address);
interface IGearboxRouter is IVersion {
/// @dev Emits each time when routerComponent is set / updated
event RouterComponentUpdate(bytes32 indexed, address indexed, uint256 version);
event SetFutureRouter(address indexed);
function routeOneToOne(
address creditAccount,
address tokenIn,
uint256 amount,
address target,
uint256 slippage,
uint256 numSplits
) external returns (RouterResult memory);
function routeOneToOneDiff(
address creditAccount,
address tokenIn,
uint256 balance,
uint256 leftoverBalance,
address target,
uint256 slippage,
uint256 numSplits
) external returns (RouterResult memory);
function routeOpenManyToOne(address creditManager, address target, uint256 slippage, TokenData[] calldata tData)
external
returns (RouterResult memory);
function routeManyToOne(address creditAccount, address target, uint256 slippage, TokenData[] calldata tData)
external
returns (RouterResult memory);
function componentAddressByType(bytes32) external view returns (address);
function isRouterConfigurator(address account) external view returns (bool);
function futureRouter() external view returns (address);
function knownComponentTypes() external view returns (bytes32[] memory);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {AllowanceAction} from "./ICreditConfiguratorV3.sol";
import "./ICreditFacadeV3Multicall.sol";
import {IACLTrait} from "./base/IACLTrait.sol";
import {PriceUpdate} from "./base/IPriceFeedStore.sol";
import {IVersion} from "./base/IVersion.sol";
/// @notice Multicall element
/// @param target Call target, which is either credit facade or adapter
/// @param callData Call data
struct MultiCall {
address target;
bytes callData;
}
/// @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 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
struct FullCheckParams {
uint256[] collateralHints;
uint16 minHealthFactor;
}
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 is partially liquidated
event PartiallyLiquidateCreditAccount(
address indexed creditAccount,
address indexed token,
address indexed liquidator,
uint256 repaidDebt,
uint256 seizedCollateral,
uint256 fee
);
/// @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 phantom token is withdrawn by account
event WithdrawPhantomToken(address indexed creditAccount, address indexed token, uint256 amount);
/// @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, IACLTrait, ICreditFacadeV3Events {
function creditManager() external view returns (address);
function underlying() external view returns (address);
function treasury() external view returns (address);
function priceFeedStore() 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 lossPolicy() external view returns (address);
function forbiddenTokenMask() external view returns (uint256);
// ------------------ //
// 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,
bytes memory lossPolicyData
) external;
function liquidateCreditAccount(address creditAccount, address to, MultiCall[] calldata calls) external;
function partiallyLiquidateCreditAccount(
address creditAccount,
address token,
uint256 repaidAmount,
uint256 minSeizedAmount,
address to,
PriceUpdate[] calldata priceUpdates
) external returns (uint256 seizedAmount);
function multicall(address creditAccount, MultiCall[] calldata calls) external payable;
function botMulticall(address creditAccount, MultiCall[] calldata calls) external;
// ------------- //
// CONFIGURATION //
// ------------- //
function setExpirationDate(uint40 newExpirationDate) external;
function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt, uint8 newMaxDebtPerBlockMultiplier) external;
function setLossPolicy(address newLossPolicy) external;
function setTokenAllowance(address token, AllowanceAction allowance) external;
function pause() external;
function unpause() external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2025
pragma solidity ^0.8.10;
library AddressList {
function includes(address[] memory array, address item) internal pure returns (bool) {
uint256 len = array.length;
for (uint256 i; i < len;) {
if (array[i] == item) return true;
unchecked {
++i;
}
}
return false;
}
function trim(address[] memory array) internal pure returns (address[] memory trimmed) {
uint256 len = array.length;
if (len == 0) return array;
uint256 foundLen;
while (array[foundLen] != address(0)) {
unchecked {
++foundLen;
if (foundLen == len) return array;
}
}
if (foundLen > 0) return copy(array, foundLen);
}
function copy(address[] memory array, uint256 len) internal pure returns (address[] memory res) {
res = new address[](len);
for (uint256 i; i < len;) {
res[i] = array[i];
unchecked {
++i;
}
}
}
function concat(address[] memory calls1, address[] memory calls2) internal pure returns (address[] memory res) {
uint256 len1 = calls1.length;
uint256 lenTotal = len1 + calls2.length;
if (lenTotal == len1) return calls1;
res = new address[](lenTotal);
for (uint256 i; i < lenTotal;) {
res[i] = (i < len1) ? calls1[i] : calls2[i - len1];
unchecked {
++i;
}
}
}
function append(address[] memory addrs, address newAddr) internal pure returns (address[] memory res) {
address[] memory newAddrArray = new address[](1);
newAddrArray[0] = newAddr;
return concat(addrs, newAddrArray);
}
function popFirst(address[] memory addrs) internal pure returns (address[] memory res) {
uint256 len = addrs.length;
res = new address[](len - 1);
for (uint256 i = 1; i < len; ++i) {
res[i - 1] = addrs[i];
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
// 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.fi, 2021
pragma solidity ^0.8.10;
interface IRouterComponent {
function contractType() external view returns (bytes32);
function version() external view returns (uint256);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (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;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2025
pragma solidity ^0.8.10;
import {RAY} from "@gearbox-protocol/core-v3/contracts/libraries/Constants.sol";
import {MultiCall, MultiCallOps} from "../lib/MultiCall.sol";
struct RouterResult {
uint256 amount;
uint256 minAmount;
MultiCall[] calls;
}
library RouterResultOps {
using MultiCallOps for MultiCall[];
function trim(RouterResult memory r) internal pure returns (RouterResult memory) {
r.calls = r.calls.trim();
return r;
}
function isBetter(RouterResult memory pfr1, RouterResult memory pfr2) internal pure returns (bool) {
return pfr1.amount > pfr2.amount;
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {IACLTrait} from "./base/IACLTrait.sol";
import {IVersion} from "./base/IVersion.sol";
enum AllowanceAction {
FORBID,
ALLOW
}
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);
// -------- //
// 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 new fee parameters are set in the credit manager
event UpdateFees(
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 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 new loss policy is set
event SetLossPolicy(address indexed lossPolicy);
/// @notice Emitted when a new expiration timestamp is set in the credit facade
event SetExpirationDate(uint40 expirationDate);
}
/// @title Credit configurator V3 interface
interface ICreditConfiguratorV3 is IVersion, IACLTrait, ICreditConfiguratorV3Events {
function creditManager() external view returns (address);
function creditFacade() external view returns (address);
function underlying() external view returns (address);
// ------ //
// TOKENS //
// ------ //
function makeAllTokensQuoted() external;
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;
// -------- //
// ADAPTERS //
// -------- //
function allowedAdapters() external view returns (address[] memory);
function allowAdapter(address adapter) external;
function forbidAdapter(address adapter) external;
// -------------- //
// CREDIT MANAGER //
// -------------- //
function setFees(
uint16 feeLiquidation,
uint16 liquidationPremium,
uint16 feeLiquidationExpired,
uint16 liquidationPremiumExpired
) external;
// -------- //
// UPGRADES //
// -------- //
function setPriceOracle(address newPriceOracle) external;
function setCreditFacade(address newCreditFacade, bool migrateParams) external;
function upgradeCreditConfigurator(address newCreditConfigurator) external;
// ------------- //
// CREDIT FACADE //
// ------------- //
function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt) external;
function setMaxDebtPerBlockMultiplier(uint8 newMaxDebtLimitPerBlockMultiplier) external;
function forbidBorrowing() external;
function setLossPolicy(address newLossPolicy) external;
function setExpirationDate(uint40 newExpirationDate) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {BalanceDelta} from "../libraries/BalancesLogic.sol";
import {PriceUpdate} from "./base/IPriceFeedStore.sol";
// ----------- //
// PERMISSIONS //
// ----------- //
// NOTE: permissions 1 << 3, 1 << 4 and 1 << 7 were used by now deprecated methods, thus non-consecutive values
uint192 constant ADD_COLLATERAL_PERMISSION = 1 << 0;
uint192 constant INCREASE_DEBT_PERMISSION = 1 << 1;
uint192 constant DECREASE_DEBT_PERMISSION = 1 << 2;
uint192 constant WITHDRAW_COLLATERAL_PERMISSION = 1 << 5;
uint192 constant UPDATE_QUOTA_PERMISSION = 1 << 6;
uint192 constant SET_BOT_PERMISSIONS_PERMISSION = 1 << 8;
uint192 constant EXTERNAL_CALLS_PERMISSION = 1 << 16;
uint192 constant ALL_PERMISSIONS = ADD_COLLATERAL_PERMISSION | WITHDRAW_COLLATERAL_PERMISSION | UPDATE_QUOTA_PERMISSION
| INCREASE_DEBT_PERMISSION | DECREASE_DEBT_PERMISSION | SET_BOT_PERMISSIONS_PERMISSION | EXTERNAL_CALLS_PERMISSION;
uint192 constant OPEN_CREDIT_ACCOUNT_PERMISSIONS = ALL_PERMISSIONS & ~DECREASE_DEBT_PERMISSION;
uint192 constant CLOSE_CREDIT_ACCOUNT_PERMISSIONS = ALL_PERMISSIONS & ~INCREASE_DEBT_PERMISSION;
uint192 constant LIQUIDATE_CREDIT_ACCOUNT_PERMISSIONS =
EXTERNAL_CALLS_PERMISSION | ADD_COLLATERAL_PERMISSION | WITHDRAW_COLLATERAL_PERMISSION;
// ----- //
// FLAGS //
// ----- //
/// @dev Indicates that collateral check after the multicall can be skipped, set to true on account closure or liquidation
uint256 constant SKIP_COLLATERAL_CHECK_FLAG = 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_FLAG = 1 << 193;
/// @dev Indicates that the price updates call should be skipped, set to true on liquidation when the first call
/// of the multicall is `onDemandPriceUpdates`
uint256 constant SKIP_PRICE_UPDATES_CALL_FLAG = 1 << 194;
/// @dev Indicates that collateral check must revert if any forbidden token is encountered on the account,
/// set to true after risky operations, such as `increaseDebt` or `withdrawCollateral`
uint256 constant REVERT_ON_FORBIDDEN_TOKENS_FLAG = 1 << 195;
/// @dev Indicates that collateral check must be performed using safe prices, set to true on `withdrawCollateral`
/// or if account has enabled forbidden tokens
uint256 constant USE_SAFE_PRICES_FLAG = 1 << 196;
/// @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 Applies on-demand price feed updates
/// @param updates Array of price updates, see `PriceUpdate` for details
/// @dev Reverts if placed not at the first position in the multicall
/// @dev This method is available in all kinds of multicalls
function onDemandPriceUpdates(PriceUpdate[] calldata updates) 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.
/// Only the underlying token counts towards account's collateral value by default, while all other tokens
/// must be enabled as collateral by "purchasing" quota for it. Holding non-enabled token on account with
/// non-zero debt poses a risk of losing it entirely to the liquidator. Adding non-enabled tokens is still
/// supported to allow users to later swap them into enabled ones in the same multicall.
/// @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.
/// Only the underlying token counts towards account's collateral value by default, while all other tokens
/// must be enabled as collateral by "purchasing" quota for it. Holding non-enabled token on account with
/// non-zero debt poses a risk of losing it entirely to the liquidator. Adding non-enabled tokens is still
/// supported to allow users to later swap them into enabled ones in the same multicall.
/// @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 limits
/// @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 above allowed minimum or zero (maximum is not checked here
/// to allow small repayments and partial liquidations in case configurator lowers it)
/// @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 Collateral token to update the quota for (can't be underlying)
/// @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 for forbidden tokens
/// @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
/// @dev If `token` is a phantom token, it's withdrawn first, and its `depositedToken` is then sent to the recipient.
/// No slippage prevention mechanism is provided as withdrawals are assumed to happen at non-manipulatable rate.
/// Although an adapter call is made in process, permission for external calls is not required.
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. Underlying token is always
/// checked last so it's forbidden to pass its mask.
/// @param minHealthFactor Min account's health factor in bps in order not to revert, must be at least 10000
/// @dev This method can't be called during closure or liquidation
function setFullCheckParams(uint256[] calldata collateralHints, uint16 minHealthFactor) external;
/// @notice Sets `bot`'s permissions to manage account to `permissions`
/// @param bot Bot to set permissions for
/// @param permissions A bitmask encoding bot permissions
/// @dev Reverts if `permissions` has unexpected bits enabled or doesn't match permissions required by `bot`
function setBotPermissions(address bot, uint192 permissions) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
interface IACLTrait {
function acl() external view returns (address);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {IVersion} from "./IVersion.sol";
struct PriceUpdate {
address priceFeed;
bytes data;
}
interface IPriceFeedStore {
function getStalenessPeriod(address priceFeed) external view returns (uint32);
function updatePrices(PriceUpdate[] calldata updates) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
bytes32 constant AP_GEAR_TOKEN = "GLOBAL::GEAR_TOKEN";
bytes32 constant AP_INSTANCE_MANAGER_PROXY = "INSTANCE_MANAGER_PROXY";
bytes32 constant AP_CROSS_CHAIN_GOVERNANCE_PROXY = "CROSS_CHAIN_GOVERNANCE_PROXY";
bytes32 constant AP_PRICE_FEED_STORE = "PRICE_FEED_STORE";
uint256 constant NO_VERSION_CONTROL = 0;
uint256 constant WAD = 1e18;
uint256 constant RAY = 1e27;
uint16 constant PERCENTAGE_FACTOR = 1e4;
uint256 constant SECONDS_PER_YEAR = 365 days;
uint256 constant EPOCH_LENGTH = 7 days;
uint256 constant FIRST_EPOCH_TIMESTAMP = 1702900800;
uint256 constant EPOCHS_TO_WITHDRAW = 4;
uint8 constant MAX_SANE_ENABLED_TOKENS = 20;
uint256 constant MAX_SANE_EPOCH_LENGTH = 28 days;
uint256 constant MAX_SANE_ACTIVE_BOTS = 5;
uint8 constant MAX_WITHDRAW_FEE = 100;
uint8 constant DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER = 2;
uint8 constant BOT_PERMISSIONS_SET_FLAG = 1;
uint256 constant UNDERLYING_TOKEN_MASK = 1;
address constant INACTIVE_CREDIT_ACCOUNT_ADDRESS = address(1);
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
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";
struct Balance {
address token;
uint256 balance;
}
struct BalanceWithMask {
address token;
uint256 tokenMask;
uint256 balance;
}
struct BalanceDelta {
address token;
int256 amount;
}
enum Comparison {
GREATER_OR_EQUAL,
LESS_OR_EQUAL
}
/// @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_OR_EQUAL && current >= value)
|| (comparison == Comparison.LESS_OR_EQUAL && 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 failedToken The first token for which the condition specified by `comparison` fails, if any
function compareBalances(address creditAccount, Balance[] memory balances, Comparison comparison)
internal
view
returns (address failedToken)
{
unchecked {
uint256 len = balances.length;
for (uint256 i; i < len; ++i) {
if (!BalancesLogic.checkBalance(creditAccount, balances[i].token, balances[i].balance, comparison)) {
return balances[i].token; // 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.lsbMask();
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 failedToken The first token for which the condition specified by `comparison` fails, if any
/// @dev This function assumes that `tokensMask` encodes a subset of tokens from `balances`
function compareBalances(
address creditAccount,
uint256 tokensMask,
BalanceWithMask[] memory balances,
Comparison comparison
) internal view returns (address failedToken) {
if (tokensMask == 0) return address(0);
unchecked {
uint256 len = balances.length;
for (uint256 i; i < len; ++i) {
if (tokensMask & balances[i].tokenMask == 0) continue;
if (!BalancesLogic.checkBalance(creditAccount, balances[i].token, balances[i].balance, comparison)) {
return balances[i].token; // U:[BLL-5]
}
}
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// 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);
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import "../interfaces/IDaiLikePermit.sol";
import "../interfaces/IPermit2.sol";
import "../interfaces/IERC7597Permit.sol";
import "../interfaces/IWETH.sol";
import "../libraries/RevertReasonForwarder.sol";
/**
* @title Implements efficient safe methods for ERC20 interface.
* @notice Compared to the standard ERC20, this implementation offers several enhancements:
* 1. more gas-efficient, providing significant savings in transaction costs.
* 2. support for different permit implementations
* 3. forceApprove functionality
* 4. support for WETH deposit and withdraw
*/
library SafeERC20 {
error SafeTransferFailed();
error SafeTransferFromFailed();
error ForceApproveFailed();
error SafeIncreaseAllowanceFailed();
error SafeDecreaseAllowanceFailed();
error SafePermitBadLength();
error Permit2TransferAmountTooHigh();
// Uniswap Permit2 address
address private constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
bytes4 private constant _PERMIT_LENGTH_ERROR = 0x68275857; // SafePermitBadLength.selector
uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
/**
* @notice Fetches the balance of a specific ERC20 token held by an account.
* Consumes less gas then regular `ERC20.balanceOf`.
* @dev Note that the implementation does not perform dirty bits cleaning, so it is the
* responsibility of the caller to make sure that the higher 96 bits of the `account` parameter are clean.
* @param token The IERC20 token contract for which the balance will be fetched.
* @param account The address of the account whose token balance will be fetched.
* @return tokenBalance The balance of the specified ERC20 token held by the account.
*/
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())
}
}
}
/**
* @notice Attempts to safely transfer tokens from one address to another.
* @dev If permit2 is true, uses the Permit2 standard; otherwise uses the standard ERC20 transferFrom.
* Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param from The address from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param amount The amount of tokens to transfer.
* @param permit2 If true, uses the Permit2 standard for the transfer; otherwise uses the standard ERC20 transferFrom.
*/
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);
}
}
/**
* @notice Attempts to safely transfer tokens from one address to another using the ERC20 standard.
* @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param from The address from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param amount The amount of tokens to transfer.
*/
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();
}
/**
* @notice Attempts to safely transfer tokens from one address to another using the Permit2 standard.
* @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param from The address from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param amount The amount of tokens to transfer.
*/
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();
}
/**
* @notice Attempts to safely transfer tokens to another address.
* @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `to` parameter are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param value The amount of tokens to transfer.
*/
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
if (!_makeCall(token, token.transfer.selector, to, value)) {
revert SafeTransferFailed();
}
}
/**
* @notice Attempts to approve a spender to spend a certain amount of tokens.
* @dev If `approve(from, to, amount)` fails, it tries to set the allowance to zero, and retries the `approve` call.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `spender` parameter are clean.
* @param token The IERC20 token contract on which the call will be made.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
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();
}
}
}
/**
* @notice Safely increases the allowance of a spender.
* @dev Increases with safe math check. Checks if the increased allowance will overflow, if yes, then it reverts the transaction.
* Then uses `forceApprove` to increase the allowance.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `spender` parameter are clean.
* @param token The IERC20 token contract on which the call will be made.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to increase the allowance by.
*/
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);
}
/**
* @notice Safely decreases the allowance of a spender.
* @dev Decreases with safe math check. Checks if the decreased allowance will underflow, if yes, then it reverts the transaction.
* Then uses `forceApprove` to increase the allowance.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `spender` parameter are clean.
* @param token The IERC20 token contract on which the call will be made.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to decrease the allowance by.
*/
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);
}
/**
* @notice Attempts to execute the `permit` function on the provided token with the sender and contract as parameters.
* Permit type is determined automatically based on permit calldata (IERC20Permit, IDaiLikePermit, and IPermit2).
* @dev Wraps `tryPermit` function and forwards revert reason if permit fails.
* @param token The IERC20 token to execute the permit function on.
* @param permit The permit data to be used in the function call.
*/
function safePermit(IERC20 token, bytes calldata permit) internal {
if (!tryPermit(token, msg.sender, address(this), permit)) RevertReasonForwarder.reRevert();
}
/**
* @notice Attempts to execute the `permit` function on the provided token with custom owner and spender parameters.
* Permit type is determined automatically based on permit calldata (IERC20Permit, IDaiLikePermit, and IPermit2).
* @dev Wraps `tryPermit` function and forwards revert reason if permit fails.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `owner` and `spender` parameters are clean.
* @param token The IERC20 token to execute the permit function on.
* @param owner The owner of the tokens for which the permit is made.
* @param spender The spender allowed to spend the tokens by the permit.
* @param permit The permit data to be used in the function call.
*/
function safePermit(IERC20 token, address owner, address spender, bytes calldata permit) internal {
if (!tryPermit(token, owner, spender, permit)) RevertReasonForwarder.reRevert();
}
/**
* @notice Attempts to execute the `permit` function on the provided token with the sender and contract as parameters.
* @dev Invokes `tryPermit` with sender as owner and contract as spender.
* @param token The IERC20 token to execute the permit function on.
* @param permit The permit data to be used in the function call.
* @return success Returns true if the permit function was successfully executed, false otherwise.
*/
function tryPermit(IERC20 token, bytes calldata permit) internal returns(bool success) {
return tryPermit(token, msg.sender, address(this), permit);
}
/**
* @notice The function attempts to call the permit function on a given ERC20 token.
* @dev The function is designed to support a variety of permit functions, namely: IERC20Permit, IDaiLikePermit, IERC7597Permit and IPermit2.
* It accommodates both Compact and Full formats of these permit types.
* Please note, it is expected that the `expiration` parameter for the compact Permit2 and the `deadline` parameter
* for the compact Permit are to be incremented by one before invoking this function. This approach is motivated by
* gas efficiency considerations; as the unlimited expiration period is likely to be the most common scenario, and
* zeros are cheaper to pass in terms of gas cost. Thus, callers should increment the expiration or deadline by one
* before invocation for optimized performance.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `owner` and `spender` parameters are clean.
* @param token The address of the ERC20 token on which to call the permit function.
* @param owner The owner of the tokens. This address should have signed the off-chain permit.
* @param spender The address which will be approved for transfer of tokens.
* @param permit The off-chain permit data, containing different fields depending on the type of permit function.
* @return success A boolean indicating whether the permit call was successful.
*/
function tryPermit(IERC20 token, address owner, address spender, bytes calldata permit) internal returns(bool success) {
// load function selectors for different permit standards
bytes4 permitSelector = IERC20Permit.permit.selector;
bytes4 daiPermitSelector = IDaiLikePermit.permit.selector;
bytes4 permit2Selector = IPermit2.permit.selector;
bytes4 erc7597PermitSelector = IERC7597Permit.permit.selector;
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
let ptr := mload(0x40)
// Switch case for different permit lengths, indicating different permit standards
switch permit.length
// Compact IERC20Permit
case 100 {
mstore(ptr, permitSelector) // store selector
mstore(add(ptr, 0x04), owner) // store owner
mstore(add(ptr, 0x24), spender) // store spender
// Compact IERC20Permit.permit(uint256 value, uint32 deadline, uint256 r, uint256 vs)
{ // stack too deep
let deadline := shr(224, calldataload(add(permit.offset, 0x20))) // loads permit.offset 0x20..0x23
let vs := calldataload(add(permit.offset, 0x44)) // loads permit.offset 0x44..0x63
calldatacopy(add(ptr, 0x44), permit.offset, 0x20) // store value = copy permit.offset 0x00..0x19
mstore(add(ptr, 0x64), sub(deadline, 1)) // store deadline = deadline - 1
mstore(add(ptr, 0x84), add(27, shr(255, vs))) // store v = most significant bit of vs + 27 (27 or 28)
calldatacopy(add(ptr, 0xa4), add(permit.offset, 0x24), 0x20) // store r = copy permit.offset 0x24..0x43
mstore(add(ptr, 0xc4), shr(1, shl(1, vs))) // store s = vs without most significant bit
}
// 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)
}
// Compact IDaiLikePermit
case 72 {
mstore(ptr, daiPermitSelector) // store selector
mstore(add(ptr, 0x04), owner) // store owner
mstore(add(ptr, 0x24), spender) // store spender
// Compact IDaiLikePermit.permit(uint32 nonce, uint32 expiry, uint256 r, uint256 vs)
{ // stack too deep
let expiry := shr(224, calldataload(add(permit.offset, 0x04))) // loads permit.offset 0x04..0x07
let vs := calldataload(add(permit.offset, 0x28)) // loads permit.offset 0x28..0x47
mstore(add(ptr, 0x44), shr(224, calldataload(permit.offset))) // store nonce = copy permit.offset 0x00..0x03
mstore(add(ptr, 0x64), sub(expiry, 1)) // store expiry = expiry - 1
mstore(add(ptr, 0x84), true) // store allowed = true
mstore(add(ptr, 0xa4), add(27, shr(255, vs))) // store v = most significant bit of vs + 27 (27 or 28)
calldatacopy(add(ptr, 0xc4), add(permit.offset, 0x08), 0x20) // store r = copy permit.offset 0x08..0x27
mstore(add(ptr, 0xe4), shr(1, shl(1, vs))) // store s = vs without most significant bit
}
// 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)
}
// IERC20Permit
case 224 {
mstore(ptr, permitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// 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)
}
// IDaiLikePermit
case 256 {
mstore(ptr, daiPermitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// 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)
}
// Compact IPermit2
case 96 {
// Compact IPermit2.permit(uint160 amount, uint32 expiration, uint32 nonce, uint32 sigDeadline, uint256 r, uint256 vs)
mstore(ptr, permit2Selector) // store selector
mstore(add(ptr, 0x04), owner) // store owner
mstore(add(ptr, 0x24), token) // store token
calldatacopy(add(ptr, 0x50), permit.offset, 0x14) // store amount = copy permit.offset 0x00..0x13
// and(0xffffffffffff, ...) - conversion to uint48
mstore(add(ptr, 0x64), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x14))), 1))) // store expiration = ((permit.offset 0x14..0x17 - 1) & 0xffffffffffff)
mstore(add(ptr, 0x84), shr(224, calldataload(add(permit.offset, 0x18)))) // store nonce = copy permit.offset 0x18..0x1b
mstore(add(ptr, 0xa4), spender) // store spender
// and(0xffffffffffff, ...) - conversion to uint48
mstore(add(ptr, 0xc4), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x1c))), 1))) // store sigDeadline = ((permit.offset 0x1c..0x1f - 1) & 0xffffffffffff)
mstore(add(ptr, 0xe4), 0x100) // store offset = 256
mstore(add(ptr, 0x104), 0x40) // store length = 64
calldatacopy(add(ptr, 0x124), add(permit.offset, 0x20), 0x20) // store r = copy permit.offset 0x20..0x3f
calldatacopy(add(ptr, 0x144), add(permit.offset, 0x40), 0x20) // store vs = copy permit.offset 0x40..0x5f
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
success := call(gas(), _PERMIT2, 0, ptr, 0x164, 0, 0)
}
// IPermit2
case 352 {
mstore(ptr, permit2Selector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
success := call(gas(), _PERMIT2, 0, ptr, 0x164, 0, 0)
}
// Dynamic length
default {
mstore(ptr, erc7597PermitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// IERC7597Permit.permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature)
success := call(gas(), token, 0, ptr, add(permit.length, 4), 0, 0)
}
}
}
/**
* @dev Executes a low level call to a token contract, making it resistant to reversion and erroneous boolean returns.
* @param token The IERC20 token contract on which the call will be made.
* @param selector The function signature that is to be called on the token contract.
* @param to The address to which the token amount will be transferred.
* @param amount The token amount to be transferred.
* @return success A boolean indicating if the call was successful. Returns 'true' on success and 'false' on failure.
* In case of success but no returned data, validates that the contract code exists.
* In case of returned data, ensures that it's a boolean `true`.
*/
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))
}
}
}
}
/**
* @notice Safely deposits a specified amount of Ether into the IWETH contract. Consumes less gas then regular `IWETH.deposit`.
* @param weth The IWETH token contract.
* @param amount The amount of Ether to deposit into the IWETH contract.
*/
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)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
}
/**
* @notice Safely withdraws a specified amount of wrapped Ether from the IWETH contract. Consumes less gas then regular `IWETH.withdraw`.
* @dev Uses inline assembly to interact with the IWETH contract.
* @param weth The IWETH token contract.
* @param amount The amount of wrapped Ether to withdraw from the IWETH contract.
*/
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())
}
}
}
/**
* @notice Safely withdraws a specified amount of wrapped Ether from the IWETH contract to a specified recipient.
* Consumes less gas then regular `IWETH.withdraw`.
* @param weth The IWETH token contract.
* @param amount The amount of wrapped Ether to withdraw from the IWETH contract.
* @param to The recipient of the withdrawn Ether.
*/
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())
}
}
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;
import {IncorrectParameterException} from "../interfaces/IExceptions.sol";
/// @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 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 Returns a mask with only the least significant bit of `mask` enabled
/// @dev This function can be used to efficiently iterate over enabled bits in a mask
function lsbMask(uint256 mask) internal pure returns (uint256) {
unchecked {
return mask & uint256(-int256(mask)); // U:[BM-6]
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (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.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
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].
*
* CAUTION: See Security Considerations above.
*/
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);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IDaiLikePermit
* @dev Interface for Dai-like permit function allowing token spending via signatures.
*/
interface IDaiLikePermit {
/**
* @notice Approves spending of tokens via off-chain signatures.
* @param holder Token holder's address.
* @param spender Spender's address.
* @param nonce Current nonce of the holder.
* @param expiry Time when the permit expires.
* @param allowed True to allow, false to disallow spending.
* @param v, r, s Signature components.
*/
function permit(
address holder,
address spender,
uint256 nonce,
uint256 expiry,
bool allowed,
uint8 v,
bytes32 r,
bytes32 s
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IPermit2
* @dev Interface for a flexible permit system that extends ERC20 tokens to support permits in tokens lacking native permit functionality.
*/
interface IPermit2 {
/**
* @dev Struct for holding permit details.
* @param token ERC20 token address for which the permit is issued.
* @param amount The maximum amount allowed to spend.
* @param expiration Timestamp until which the permit is valid.
* @param nonce An incrementing value for each signature, unique per owner, token, and spender.
*/
struct PermitDetails {
address token;
uint160 amount;
uint48 expiration;
uint48 nonce;
}
/**
* @dev Struct for a single token allowance permit.
* @param details Permit details including token, amount, expiration, and nonce.
* @param spender Address authorized to spend the tokens.
* @param sigDeadline Deadline for the permit signature, ensuring timeliness of the permit.
*/
struct PermitSingle {
PermitDetails details;
address spender;
uint256 sigDeadline;
}
/**
* @dev Struct for packed allowance data to optimize storage.
* @param amount Amount allowed.
* @param expiration Permission expiry timestamp.
* @param nonce Unique incrementing value for tracking allowances.
*/
struct PackedAllowance {
uint160 amount;
uint48 expiration;
uint48 nonce;
}
/**
* @notice Executes a token transfer from one address to another.
* @param user The token owner's address.
* @param spender The address authorized to spend the tokens.
* @param amount The amount of tokens to transfer.
* @param token The address of the token being transferred.
*/
function transferFrom(address user, address spender, uint160 amount, address token) external;
/**
* @notice Issues a permit for spending tokens via a signed authorization.
* @param owner The token owner's address.
* @param permitSingle Struct containing the permit details.
* @param signature The signature proving the owner authorized the permit.
*/
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
/**
* @notice Retrieves the allowance details between a token owner and spender.
* @param user The token owner's address.
* @param token The token address.
* @param spender The spender's address.
* @return The packed allowance details.
*/
function allowance(address user, address token, address spender) external view returns (PackedAllowance memory);
/**
* @notice Approves the spender to use up to amount of the specified token up until the expiration
* @param token The token to approve
* @param spender The spender address to approve
* @param amount The approved amount of the token
* @param expiration The timestamp at which the approval is no longer valid
* @dev The packed allowance also holds a nonce, which will stay unchanged in approve
* @dev Setting amount to type(uint160).max sets an unlimited approval
*/
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IERC7597Permit
* @dev A new extension for ERC-2612 permit, which has already been added to USDC v2.2.
*/
interface IERC7597Permit {
/**
* @notice Update allowance with a signed permit.
* @dev Signature bytes can be used for both EOA wallets and contract wallets.
* @param owner Token owner's address (Authorizer).
* @param spender Spender's address.
* @param value Amount of allowance.
* @param deadline The time at which the signature expires (unixtime).
* @param signature Unstructured bytes signature signed by an EOA wallet or a contract wallet.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
bytes memory signature
) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title IWETH
* @dev Interface for wrapper as WETH-like token.
*/
interface IWETH is IERC20 {
/**
* @notice Emitted when Ether is deposited to get wrapper tokens.
*/
event Deposit(address indexed dst, uint256 wad);
/**
* @notice Emitted when wrapper tokens is withdrawn as Ether.
*/
event Withdrawal(address indexed src, uint256 wad);
/**
* @notice Deposit Ether to get wrapper tokens.
*/
function deposit() external payable;
/**
* @notice Withdraw wrapped tokens as Ether.
* @param amount Amount of wrapped tokens to withdraw.
*/
function withdraw(uint256 amount) external;
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title RevertReasonForwarder
* @notice Provides utilities for forwarding and retrieving revert reasons from failed external calls.
*/
library RevertReasonForwarder {
/**
* @dev Forwards the revert reason from the latest external call.
* This method allows propagating the revert reason of a failed external call to the caller.
*/
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())
}
}
/**
* @dev Retrieves the revert reason from the latest external call.
* This method enables capturing the revert reason of a failed external call for inspection or processing.
* @return reason The latest external call revert reason.
*/
function reReason() internal pure returns (bytes memory reason) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
reason := mload(0x40)
let length := returndatasize()
mstore(reason, length)
returndatacopy(add(reason, 0x20), 0, length)
mstore(0x40, add(reason, add(0x20, length)))
}
}
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
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 attempting to connect a gauge to an incompatible pool quota keeper
error IncompatibleGaugeException();
/// @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();
/// @notice Thrown if new credit configurator's set of allowed adapters differs from the current one
error IncorrectAdaptersSetException();
/// @notice Thrown if attempting to schedule a token's LT ramping that is too short in duration
error RampDurationTooShortException();
/// @notice Thrown if attempting to set liquidation fees such that the sum of premium and fee changes
error InconsistentLiquidationFeesException();
/// @notice Thrown if attempting to set expired liquidation fees such that the sum of premium and fee changes
error InconsistentExpiredLiquidationFeesException();
// ------------- //
// 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(bytes4 selector);
/// @notice Thrown if a liquidator tries to liquidate an account with a health factor above 1
error CreditAccountNotLiquidatableException();
/// @notice Thrown if a liquidator tries to liquidate an account with loss but violates the loss policy
error CreditAccountNotLiquidatableWithLossException();
/// @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(address token);
/// @notice Thrown when trying to perform an action that is forbidden when credit account has enabled forbidden tokens
error ForbiddenTokensException(uint256 forbiddenTokensMask);
/// @notice Thrown when forbidden token quota is increased during the multicall
error ForbiddenTokenQuotaIncreasedException(address token);
/// @notice Thrown when enabled forbidden token balance is increased during the multicall
error ForbiddenTokenBalanceIncreasedException(address token);
/// @notice Thrown when the remaining token balance is increased during the liquidation
error RemainingTokenBalanceIncreasedException(address token);
/// @notice Thrown if `botMulticall` is called by an address that is not approved by account owner or is forbidden
error NotApprovedBotException(address bot);
/// @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(uint256 permissions);
/// @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(uint256 mask);
/// @notice Thrown when trying to seize underlying token during partial liquidation
error UnderlyingIsNotLiquidatableException();
/// @notice Thrown when amount of collateral seized during partial liquidation is less than required
error SeizedLessThanRequiredException(uint256 seizedAmount);
// ------ //
// 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 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();
// -------- //
// BOT LIST //
// -------- //
/// @notice Thrown when attempting to set non-zero permissions for a forbidden bot
error InvalidBotException();
/// @notice Thrown when attempting to set permissions for a bot that don't meet its requirements
error IncorrectBotPermissionsException();
/// @notice Thrown when attempting to set non-zero permissions for too many bots
error TooManyActiveBotsException();
// --------------- //
// 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 trying to apply an on-demand price update to a non-updatable price feed
error PriceFeedIsNotUpdatableException();
/// @notice Thrown when price feed returns incorrect price for a token
error IncorrectPriceException();
/// @notice Thrown when token's price feed becomes stale
error StalePriceException();