// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";
import { IBatchRouter } from "@balancer-labs/v3-interfaces/contracts/vault/IBatchRouter.sol";
import { IWETH } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { EVMCallModeHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/EVMCallModeHelpers.sol";
import { CastingHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/CastingHelpers.sol";
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
import {
TransientEnumerableSet
} from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/TransientEnumerableSet.sol";
import {
TransientStorageHelpers
} from "@balancer-labs/v3-solidity-utils/contracts/helpers/TransientStorageHelpers.sol";
import { BatchRouterCommon } from "./BatchRouterCommon.sol";
struct SwapStepLocals {
bool isFirstStep;
bool isLastStep;
}
/**
* @notice Entrypoint for batch swaps, and batch swap queries.
* @dev The external API functions unlock the Vault, which calls back into the corresponding hook functions.
* These interpret the steps and paths in the input data, perform token accounting (in transient storage, to save gas),
* settle with the Vault, and handle wrapping and unwrapping ETH.
*/
contract BatchRouter is IBatchRouter, BatchRouterCommon {
using CastingHelpers for *;
using TransientEnumerableSet for TransientEnumerableSet.AddressSet;
using TransientStorageHelpers for *;
using SafeERC20 for IERC20;
using SafeCast for *;
constructor(
IVault vault,
IWETH weth,
IPermit2 permit2,
string memory routerVersion
) BatchRouterCommon(vault, weth, permit2, routerVersion) {
// solhint-disable-previous-line no-empty-blocks
}
/***************************************************************************
Swaps
***************************************************************************/
/// @inheritdoc IBatchRouter
function swapExactIn(
SwapPathExactAmountIn[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
)
external
payable
saveSender(msg.sender)
returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut)
{
return
abi.decode(
_vault.unlock(
abi.encodeCall(
BatchRouter.swapExactInHook,
SwapExactInHookParams({
sender: msg.sender,
paths: paths,
deadline: deadline,
wethIsEth: wethIsEth,
userData: userData
})
)
),
(uint256[], address[], uint256[])
);
}
/// @inheritdoc IBatchRouter
function swapExactOut(
SwapPathExactAmountOut[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
)
external
payable
saveSender(msg.sender)
returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn)
{
return
abi.decode(
_vault.unlock(
abi.encodeCall(
BatchRouter.swapExactOutHook,
SwapExactOutHookParams({
sender: msg.sender,
paths: paths,
deadline: deadline,
wethIsEth: wethIsEth,
userData: userData
})
)
),
(uint256[], address[], uint256[])
);
}
function swapExactInHook(
SwapExactInHookParams calldata params
)
external
nonReentrant
onlyVault
returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut)
{
(pathAmountsOut, tokensOut, amountsOut) = _swapExactInHook(params);
_settlePaths(params.sender, params.wethIsEth);
}
function _swapExactInHook(
SwapExactInHookParams calldata params
) internal returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut) {
// The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy.
// solhint-disable-next-line not-rely-on-time
if (block.timestamp > params.deadline) {
revert SwapDeadline();
}
pathAmountsOut = _computePathAmountsOut(params);
// The hook writes current swap token and token amounts out.
// We copy that information to memory to return it before it is deleted during settlement.
tokensOut = _currentSwapTokensOut().values();
amountsOut = new uint256[](tokensOut.length);
for (uint256 i = 0; i < tokensOut.length; ++i) {
amountsOut[i] =
_currentSwapTokenOutAmounts().tGet(tokensOut[i]) +
_settledTokenAmounts().tGet(tokensOut[i]);
_settledTokenAmounts().tSet(tokensOut[i], 0);
}
}
function _computePathAmountsOut(
SwapExactInHookParams calldata params
) internal returns (uint256[] memory pathAmountsOut) {
pathAmountsOut = new uint256[](params.paths.length);
for (uint256 i = 0; i < params.paths.length; ++i) {
SwapPathExactAmountIn memory path = params.paths[i];
// These two variables shall be updated at the end of each step to be used as inputs of the next one.
// The initial values are the given token and amount in for the current path.
uint256 stepExactAmountIn = path.exactAmountIn;
IERC20 stepTokenIn = path.tokenIn;
if (path.steps[0].isBuffer && EVMCallModeHelpers.isStaticCall() == false) {
// If first step is a buffer, take the token in advance. We need this to wrap/unwrap.
_takeTokenIn(params.sender, stepTokenIn, stepExactAmountIn, params.wethIsEth);
} else {
// Paths may (or may not) share the same token in. To minimize token transfers, we store the addresses
// in a set with unique addresses that can be iterated later on.
// For example, if all paths share the same token in, the set will end up with only one entry.
_currentSwapTokensIn().add(address(stepTokenIn));
_currentSwapTokenInAmounts().tAdd(address(stepTokenIn), stepExactAmountIn);
}
for (uint256 j = 0; j < path.steps.length; ++j) {
SwapStepLocals memory stepLocals;
stepLocals.isLastStep = (j == path.steps.length - 1);
stepLocals.isFirstStep = (j == 0);
uint256 minAmountOut;
// minAmountOut only applies to the last step.
if (stepLocals.isLastStep) {
minAmountOut = path.minAmountOut;
} else {
minAmountOut = 0;
}
SwapPathStep memory step = path.steps[j];
if (step.isBuffer) {
(, , uint256 amountOut) = _vault.erc4626BufferWrapOrUnwrap(
BufferWrapOrUnwrapParams({
kind: SwapKind.EXACT_IN,
direction: step.pool == address(stepTokenIn)
? WrappingDirection.UNWRAP
: WrappingDirection.WRAP,
wrappedToken: IERC4626(step.pool),
amountGivenRaw: stepExactAmountIn,
limitRaw: minAmountOut
})
);
if (stepLocals.isLastStep) {
// The amount out for the last step of the path should be recorded for the return value, and the
// amount for the token should be sent back to the sender later on.
pathAmountsOut[i] = amountOut;
_currentSwapTokensOut().add(address(step.tokenOut));
_currentSwapTokenOutAmounts().tAdd(address(step.tokenOut), amountOut);
} else {
// Input for the next step is output of current step.
stepExactAmountIn = amountOut;
// The token in for the next step is the token out of the current step.
stepTokenIn = step.tokenOut;
}
} else if (address(stepTokenIn) == step.pool) {
// Token in is BPT: remove liquidity - Single token exact in
// Remove liquidity is not transient when it comes to BPT, meaning the caller needs to have the
// required amount when performing the operation. These tokens might be the output of a previous
// step, in which case the user will have a BPT credit.
if (stepLocals.isFirstStep) {
if (stepExactAmountIn > 0 && params.sender != address(this)) {
// If this is the first step, the sender must have the tokens. Therefore, we can transfer
// them to the Router, which acts as an intermediary. If the sender is the Router, we just
// skip this step (useful for queries).
//
// This saves one permit(1) approval for the BPT to the Router; if we burned tokens
// directly from the sender we would need their approval.
_permit2.transferFrom(
params.sender,
address(this),
stepExactAmountIn.toUint160(),
address(stepTokenIn)
);
}
// BPT is burned instantly, so we don't need to send it back later.
if (_currentSwapTokenInAmounts().tGet(address(stepTokenIn)) > 0) {
_currentSwapTokenInAmounts().tSub(address(stepTokenIn), stepExactAmountIn);
}
} else {
// If this is an intermediate step, we don't expect the sender to have BPT to burn.
// Then, we flashloan tokens here (which should in practice just use existing credit).
_vault.sendTo(IERC20(step.pool), address(this), stepExactAmountIn);
}
// minAmountOut cannot be 0 in this case, as that would send an array of 0s to the Vault, which
// wouldn't know which token to use.
(uint256[] memory amountsOut, uint256 tokenIndex) = _getSingleInputArrayAndTokenIndex(
step.pool,
step.tokenOut,
minAmountOut == 0 ? 1 : minAmountOut
);
// Router is always an intermediary in this case. The Vault will burn tokens from the Router, so
// Router is both owner and spender (which doesn't need approval).
// Reusing `amountsOut` as input argument and function output to prevent stack too deep error.
(, amountsOut, ) = _vault.removeLiquidity(
RemoveLiquidityParams({
pool: step.pool,
from: address(this),
maxBptAmountIn: stepExactAmountIn,
minAmountsOut: amountsOut,
kind: RemoveLiquidityKind.SINGLE_TOKEN_EXACT_IN,
userData: params.userData
})
);
if (stepLocals.isLastStep) {
// The amount out for the last step of the path should be recorded for the return value, and the
// amount for the token should be sent back to the sender later on.
pathAmountsOut[i] = amountsOut[tokenIndex];
_currentSwapTokensOut().add(address(step.tokenOut));
_currentSwapTokenOutAmounts().tAdd(address(step.tokenOut), amountsOut[tokenIndex]);
} else {
// Input for the next step is output of current step.
stepExactAmountIn = amountsOut[tokenIndex];
// The token in for the next step is the token out of the current step.
stepTokenIn = step.tokenOut;
}
} else if (address(step.tokenOut) == step.pool) {
// Token out is BPT: add liquidity - Single token exact in (unbalanced).
(uint256[] memory exactAmountsIn, ) = _getSingleInputArrayAndTokenIndex(
step.pool,
stepTokenIn,
stepExactAmountIn
);
(, uint256 bptAmountOut, ) = _vault.addLiquidity(
AddLiquidityParams({
pool: step.pool,
to: stepLocals.isLastStep ? params.sender : address(_vault),
maxAmountsIn: exactAmountsIn,
minBptAmountOut: minAmountOut,
kind: AddLiquidityKind.UNBALANCED,
userData: params.userData
})
);
if (stepLocals.isLastStep) {
// The amount out for the last step of the path should be recorded for the return value.
// We do not need to register the amount out in _currentSwapTokenOutAmounts since the BPT
// is minted directly to the sender, so this step can be considered settled at this point.
pathAmountsOut[i] = bptAmountOut;
_currentSwapTokensOut().add(address(step.tokenOut));
_settledTokenAmounts().tAdd(address(step.tokenOut), bptAmountOut);
} else {
// Input for the next step is output of current step.
stepExactAmountIn = bptAmountOut;
// The token in for the next step is the token out of the current step.
stepTokenIn = step.tokenOut;
// If this is an intermediate step, BPT is minted to the Vault so we just get the credit.
_vault.settle(IERC20(step.pool), bptAmountOut);
}
} else {
// No BPT involved in the operation: regular swap exact in.
(, , uint256 amountOut) = _vault.swap(
VaultSwapParams({
kind: SwapKind.EXACT_IN,
pool: step.pool,
tokenIn: stepTokenIn,
tokenOut: step.tokenOut,
amountGivenRaw: stepExactAmountIn,
limitRaw: minAmountOut,
userData: params.userData
})
);
if (stepLocals.isLastStep) {
// The amount out for the last step of the path should be recorded for the return value, and the
// amount for the token should be sent back to the sender later on.
pathAmountsOut[i] = amountOut;
_currentSwapTokensOut().add(address(step.tokenOut));
_currentSwapTokenOutAmounts().tAdd(address(step.tokenOut), amountOut);
} else {
// Input for the next step is output of current step.
stepExactAmountIn = amountOut;
// The token in for the next step is the token out of the current step.
stepTokenIn = step.tokenOut;
}
}
}
}
}
function swapExactOutHook(
SwapExactOutHookParams calldata params
)
external
nonReentrant
onlyVault
returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn)
{
(pathAmountsIn, tokensIn, amountsIn) = _swapExactOutHook(params);
_settlePaths(params.sender, params.wethIsEth);
}
function _swapExactOutHook(
SwapExactOutHookParams calldata params
) internal returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn) {
// The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy.
// solhint-disable-next-line not-rely-on-time
if (block.timestamp > params.deadline) {
revert SwapDeadline();
}
pathAmountsIn = _computePathAmountsIn(params);
// The hook writes current swap token and token amounts in.
// We copy that information to memory to return it before it is deleted during settlement.
tokensIn = _currentSwapTokensIn().values(); // Copy transient storage to memory
amountsIn = new uint256[](tokensIn.length);
for (uint256 i = 0; i < tokensIn.length; ++i) {
amountsIn[i] = _currentSwapTokenInAmounts().tGet(tokensIn[i]) + _settledTokenAmounts().tGet(tokensIn[i]);
_settledTokenAmounts().tSet(tokensIn[i], 0);
}
}
/**
* @dev Executes every swap path in the given input parameters.
* Computes inputs for the path, and aggregates them by token and amounts as well in transient storage.
*/
function _computePathAmountsIn(
SwapExactOutHookParams calldata params
) internal returns (uint256[] memory pathAmountsIn) {
pathAmountsIn = new uint256[](params.paths.length);
for (uint256 i = 0; i < params.paths.length; ++i) {
SwapPathExactAmountOut memory path = params.paths[i];
// This variable shall be updated at the end of each step to be used as input of the next one.
// The first value corresponds to the given amount out for the current path.
uint256 stepExactAmountOut = path.exactAmountOut;
// Paths may (or may not) share the same token in. To minimize token transfers, we store the addresses in
// a set with unique addresses that can be iterated later on.
//
// For example, if all paths share the same token in, the set will end up with only one entry.
// Since the path is 'given out', the output of the operation specified by the last step in each path will
// be added to calculate the amounts in for each token.
_currentSwapTokensIn().add(address(path.tokenIn));
// Backwards iteration: the exact amount out applies to the last step, so we cannot iterate from first to
// last. The calculated input of step (j) is the exact amount out for step (j - 1).
for (int256 j = int256(path.steps.length - 1); j >= 0; --j) {
SwapPathStep memory step = path.steps[uint256(j)];
SwapStepLocals memory stepLocals;
stepLocals.isLastStep = (j == 0);
stepLocals.isFirstStep = (uint256(j) == path.steps.length - 1);
// These two variables are set at the beginning of the iteration and are used as inputs for
// the operation described by the step.
uint256 stepMaxAmountIn;
IERC20 stepTokenIn;
if (stepLocals.isFirstStep) {
// The first step in the iteration is the last one in the given array of steps, and it
// specifies the output token for the step as well as the exact amount out for that token.
// Output amounts are stored to send them later on.
_currentSwapTokensOut().add(address(step.tokenOut));
_currentSwapTokenOutAmounts().tAdd(address(step.tokenOut), stepExactAmountOut);
}
if (stepLocals.isLastStep) {
// In backwards order, the last step is the first one in the given path.
// The given token in and max amount in apply for this step.
stepMaxAmountIn = path.maxAmountIn;
stepTokenIn = path.tokenIn;
} else {
// For every other intermediate step, no maximum input applies.
// The input token for this step is the output token of the previous given step.
// We use uint128 to prevent Vault's internal scaling from overflowing.
stepMaxAmountIn = _MAX_AMOUNT;
stepTokenIn = path.steps[uint256(j - 1)].tokenOut;
}
if (step.isBuffer) {
if (stepLocals.isLastStep && EVMCallModeHelpers.isStaticCall() == false) {
// The buffer will need this token to wrap/unwrap, so take it from the user in advance.
_takeTokenIn(params.sender, path.tokenIn, path.maxAmountIn, params.wethIsEth);
}
(, uint256 amountIn, ) = _vault.erc4626BufferWrapOrUnwrap(
BufferWrapOrUnwrapParams({
kind: SwapKind.EXACT_OUT,
direction: step.pool == address(stepTokenIn)
? WrappingDirection.UNWRAP
: WrappingDirection.WRAP,
wrappedToken: IERC4626(step.pool),
amountGivenRaw: stepExactAmountOut,
limitRaw: stepMaxAmountIn
})
);
if (stepLocals.isLastStep) {
pathAmountsIn[i] = amountIn;
// Since the token was taken in advance, returns to the user what is left from the
// wrap/unwrap operation.
_currentSwapTokensOut().add(address(stepTokenIn));
_currentSwapTokenOutAmounts().tAdd(address(stepTokenIn), path.maxAmountIn - amountIn);
// `settledTokenAmounts` is used to return the `amountsIn` at the end of the operation, which
// is only amountIn. The difference between maxAmountIn and amountIn will be paid during
// settle.
_settledTokenAmounts().tAdd(address(path.tokenIn), amountIn);
} else {
stepExactAmountOut = amountIn;
}
} else if (address(stepTokenIn) == step.pool) {
// Token in is BPT: remove liquidity - Single token exact out
// Remove liquidity is not transient when it comes to BPT, meaning the caller needs to have the
// required amount when performing the operation. In this case, the BPT amount needed for the
// operation is not known in advance, so we take a flashloan for all the available reserves.
//
// The last step is the one that defines the inputs for this path. The caller should have enough
// BPT to burn already if that's the case, so we just skip this step if so.
if (stepLocals.isLastStep == false) {
stepMaxAmountIn = _vault.getReservesOf(stepTokenIn);
_vault.sendTo(IERC20(step.pool), address(this), stepMaxAmountIn);
} else if (params.sender != address(this)) {
// The last step being executed is the first step in the swap path, meaning that it's the one
// that defines the inputs of the path.
//
// In that case, the sender must have the tokens. Therefore, we can transfer them
// to the Router, which acts as an intermediary. If the sender is the Router, we just skip this
// step (useful for queries).
_permit2.transferFrom(
params.sender,
address(this),
stepMaxAmountIn.toUint160(),
address(stepTokenIn)
);
}
(uint256[] memory exactAmountsOut, ) = _getSingleInputArrayAndTokenIndex(
step.pool,
step.tokenOut,
stepExactAmountOut
);
// Router is always an intermediary in this case. The Vault will burn tokens from the Router, so
// Router is both owner and spender (which doesn't need approval).
(uint256 bptAmountIn, , ) = _vault.removeLiquidity(
RemoveLiquidityParams({
pool: step.pool,
from: address(this),
maxBptAmountIn: stepMaxAmountIn,
minAmountsOut: exactAmountsOut,
kind: RemoveLiquidityKind.SINGLE_TOKEN_EXACT_OUT,
userData: params.userData
})
);
if (stepLocals.isLastStep) {
// BPT is burned instantly, so we don't need to send it to the Vault during settlement.
pathAmountsIn[i] = bptAmountIn;
_settledTokenAmounts().tAdd(address(stepTokenIn), bptAmountIn);
// Refund unused portion of BPT to the user.alias
if (bptAmountIn < stepMaxAmountIn && params.sender != address(this)) {
stepTokenIn.safeTransfer(address(params.sender), stepMaxAmountIn - bptAmountIn);
}
} else {
// Output for the step (j - 1) is the input of step (j).
stepExactAmountOut = bptAmountIn;
// Refund unused portion of BPT flashloan to the Vault.
if (bptAmountIn < stepMaxAmountIn) {
uint256 refundAmount = stepMaxAmountIn - bptAmountIn;
stepTokenIn.safeTransfer(address(_vault), refundAmount);
_vault.settle(stepTokenIn, refundAmount);
}
}
} else if (address(step.tokenOut) == step.pool) {
// Token out is BPT: add liquidity - Single token exact out.
(uint256[] memory stepAmountsIn, uint256 tokenIndex) = _getSingleInputArrayAndTokenIndex(
step.pool,
stepTokenIn,
stepMaxAmountIn
);
// Reusing `amountsIn` as input argument and function output to prevent stack too deep error.
(stepAmountsIn, , ) = _vault.addLiquidity(
AddLiquidityParams({
pool: step.pool,
to: stepLocals.isFirstStep ? params.sender : address(_vault),
maxAmountsIn: stepAmountsIn,
minBptAmountOut: stepExactAmountOut,
kind: AddLiquidityKind.SINGLE_TOKEN_EXACT_OUT,
userData: params.userData
})
);
if (stepLocals.isLastStep) {
// The amount out for the last step of the path should be recorded for the return value.
pathAmountsIn[i] = stepAmountsIn[tokenIndex];
_currentSwapTokenInAmounts().tAdd(address(stepTokenIn), stepAmountsIn[tokenIndex]);
} else {
stepExactAmountOut = stepAmountsIn[tokenIndex];
}
// The first step executed determines the outputs for the path, since this is given out.
if (stepLocals.isFirstStep) {
// Instead of sending tokens back to the Vault, we can just discount it from whatever
// the Vault owes the sender to make one less transfer.
_currentSwapTokenOutAmounts().tSub(address(step.tokenOut), stepExactAmountOut);
} else {
// If it's not the first step, BPT is minted to the Vault so we just get the credit.
_vault.settle(IERC20(step.pool), stepExactAmountOut);
}
} else {
// No BPT involved in the operation: regular swap exact out.
(, uint256 amountIn, ) = _vault.swap(
VaultSwapParams({
kind: SwapKind.EXACT_OUT,
pool: step.pool,
tokenIn: stepTokenIn,
tokenOut: step.tokenOut,
amountGivenRaw: stepExactAmountOut,
limitRaw: stepMaxAmountIn,
userData: params.userData
})
);
if (stepLocals.isLastStep) {
pathAmountsIn[i] = amountIn;
_currentSwapTokenInAmounts().tAdd(address(stepTokenIn), amountIn);
} else {
stepExactAmountOut = amountIn;
}
}
}
}
}
/***************************************************************************
Queries
***************************************************************************/
/// @inheritdoc IBatchRouter
function querySwapExactIn(
SwapPathExactAmountIn[] memory paths,
address sender,
bytes calldata userData
)
external
saveSender(sender)
returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut)
{
for (uint256 i = 0; i < paths.length; ++i) {
paths[i].minAmountOut = 0;
}
return
abi.decode(
_vault.quote(
abi.encodeCall(
BatchRouter.querySwapExactInHook,
SwapExactInHookParams({
sender: address(this),
paths: paths,
deadline: type(uint256).max,
wethIsEth: false,
userData: userData
})
)
),
(uint256[], address[], uint256[])
);
}
/// @inheritdoc IBatchRouter
function querySwapExactOut(
SwapPathExactAmountOut[] memory paths,
address sender,
bytes calldata userData
)
external
saveSender(sender)
returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn)
{
for (uint256 i = 0; i < paths.length; ++i) {
paths[i].maxAmountIn = _MAX_AMOUNT;
}
return
abi.decode(
_vault.quote(
abi.encodeCall(
BatchRouter.querySwapExactOutHook,
SwapExactOutHookParams({
sender: address(this),
paths: paths,
deadline: type(uint256).max,
wethIsEth: false,
userData: userData
})
)
),
(uint256[], address[], uint256[])
);
}
function querySwapExactInHook(
SwapExactInHookParams calldata params
)
external
nonReentrant
onlyVault
returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut)
{
(pathAmountsOut, tokensOut, amountsOut) = _swapExactInHook(params);
}
function querySwapExactOutHook(
SwapExactOutHookParams calldata params
)
external
nonReentrant
onlyVault
returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn)
{
(pathAmountsIn, tokensIn, amountsIn) = _swapExactOutHook(params);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";
import { IWETH } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import {
TransientEnumerableSet
} from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/TransientEnumerableSet.sol";
import {
TransientStorageHelpers,
AddressToUintMappingSlot
} from "@balancer-labs/v3-solidity-utils/contracts/helpers/TransientStorageHelpers.sol";
import { RouterCommon } from "./RouterCommon.sol";
/// @notice Transient storage for Batch and Composite Liquidity Router operations.
abstract contract BatchRouterCommon is RouterCommon {
using TransientEnumerableSet for TransientEnumerableSet.AddressSet;
using TransientStorageHelpers for *;
// solhint-disable var-name-mixedcase
// NOTE: If you use a constant, then it is simply replaced everywhere when this constant is used
// by what is written after =. If you use immutable, the value is first calculated and
// then replaced everywhere. That means that if a constant has executable variables,
// they will be executed every time the constant is used.
bytes32 private immutable _CURRENT_SWAP_TOKEN_IN_SLOT = _calculateBatchRouterStorageSlot("currentSwapTokensIn");
bytes32 private immutable _CURRENT_SWAP_TOKEN_OUT_SLOT = _calculateBatchRouterStorageSlot("currentSwapTokensOut");
bytes32 private immutable _CURRENT_SWAP_TOKEN_IN_AMOUNTS_SLOT =
_calculateBatchRouterStorageSlot("currentSwapTokenInAmounts");
bytes32 private immutable _CURRENT_SWAP_TOKEN_OUT_AMOUNTS_SLOT =
_calculateBatchRouterStorageSlot("currentSwapTokenOutAmounts");
bytes32 private immutable _SETTLED_TOKEN_AMOUNTS_SLOT = _calculateBatchRouterStorageSlot("settledTokenAmounts");
// solhint-enable var-name-mixedcase
// solhint-disable no-inline-assembly
constructor(
IVault vault,
IWETH weth,
IPermit2 permit2,
string memory routerVersion
) RouterCommon(vault, weth, permit2, routerVersion) {
// solhint-disable-previous-line no-empty-blocks
}
// We use transient storage to track tokens and amounts flowing in and out of a batch swap.
// Set of input tokens involved in a batch swap.
function _currentSwapTokensIn() internal view returns (TransientEnumerableSet.AddressSet storage enumerableSet) {
bytes32 slot = _CURRENT_SWAP_TOKEN_IN_SLOT;
assembly ("memory-safe") {
enumerableSet.slot := slot
}
}
function _currentSwapTokensOut() internal view returns (TransientEnumerableSet.AddressSet storage enumerableSet) {
bytes32 slot = _CURRENT_SWAP_TOKEN_OUT_SLOT;
assembly ("memory-safe") {
enumerableSet.slot := slot
}
}
// token in -> amount: tracks token in amounts within a batch swap.
function _currentSwapTokenInAmounts() internal view returns (AddressToUintMappingSlot slot) {
return AddressToUintMappingSlot.wrap(_CURRENT_SWAP_TOKEN_IN_AMOUNTS_SLOT);
}
// token out -> amount: tracks token out amounts within a batch swap.
function _currentSwapTokenOutAmounts() internal view returns (AddressToUintMappingSlot slot) {
return AddressToUintMappingSlot.wrap(_CURRENT_SWAP_TOKEN_OUT_AMOUNTS_SLOT);
}
// token -> amount that is part of the current input / output amounts, but is settled preemptively.
// This situation happens whenever there is BPT involved in the operation, which is minted and burned instantly.
// Since those amounts are not tracked in the inputs / outputs to settle, we need to track them elsewhere
// to return the correct total amounts in and out for each token involved in the operation.
function _settledTokenAmounts() internal view returns (AddressToUintMappingSlot slot) {
return AddressToUintMappingSlot.wrap(_SETTLED_TOKEN_AMOUNTS_SLOT);
}
function _calculateBatchRouterStorageSlot(string memory key) internal pure returns (bytes32) {
return TransientStorageHelpers.calculateSlot(type(BatchRouterCommon).name, key);
}
/*******************************************************************************
Settlement
*******************************************************************************/
/// @notice Settles batch and composite liquidity operations, after credits and debits are computed.
function _settlePaths(address sender, bool wethIsEth) internal {
// numTokensIn / Out may be 0 if the inputs and / or outputs are not transient.
// For example, a swap starting with a 'remove liquidity' step will already have burned the input tokens,
// in which case there is nothing to settle. Then, since we're iterating backwards below, we need to be able
// to subtract 1 from these quantities without reverting, which is why we use signed integers.
int256 numTokensIn = int256(_currentSwapTokensIn().length());
int256 numTokensOut = int256(_currentSwapTokensOut().length());
// Iterate backwards, from the last element to 0 (included).
// Removing the last element from a set is cheaper than removing the first one.
for (int256 i = int256(numTokensIn - 1); i >= 0; --i) {
address tokenIn = _currentSwapTokensIn().unchecked_at(uint256(i));
_takeTokenIn(sender, IERC20(tokenIn), _currentSwapTokenInAmounts().tGet(tokenIn), wethIsEth);
// Erases delta, in case more than one batch router operation is called in the same transaction.
_currentSwapTokenInAmounts().tSet(tokenIn, 0);
_currentSwapTokensIn().remove(tokenIn);
}
for (int256 i = int256(numTokensOut - 1); i >= 0; --i) {
address tokenOut = _currentSwapTokensOut().unchecked_at(uint256(i));
_sendTokenOut(sender, IERC20(tokenOut), _currentSwapTokenOutAmounts().tGet(tokenOut), wethIsEth);
// Erases delta, in case more than one batch router operation is called in the same transaction.
_currentSwapTokenOutAmounts().tSet(tokenOut, 0);
_currentSwapTokensOut().remove(tokenOut);
}
// Return the rest of ETH to sender.
_returnEth(sender);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @notice Library of helper functions related to typecasting arrays.
library CastingHelpers {
/// @dev Returns a native array of addresses as an IERC20[] array.
function asIERC20(address[] memory addresses) internal pure returns (IERC20[] memory tokens) {
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
tokens := addresses
}
}
/// @dev Returns an IERC20[] array as an address[] array.
function asAddress(IERC20[] memory tokens) internal pure returns (address[] memory addresses) {
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
addresses := tokens
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
/// @notice Library used to check whether the current operation was initiated through a static call.
library EVMCallModeHelpers {
/// @notice A state-changing transaction was initiated in a context that only allows static calls.
error NotStaticCall();
/**
* @dev Detects whether the current transaction is a static call.
* A static call is one where `tx.origin` equals 0x0 for most implementations.
* See this tweet for a table on how transaction parameters are set on different platforms:
* https://twitter.com/0xkarmacoma/status/1493380279309717505
*
* Solidity eth_call reference docs are here: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
*/
function isStaticCall() internal view returns (bool) {
return tx.origin == address(0);
// solhint-disable-previous-line avoid-tx-origin
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEIP712} from "./IEIP712.sol";
/// @title AllowanceTransfer
/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
/// @dev Requires user's token approval on the Permit2 contract
interface IAllowanceTransfer is IEIP712 {
/// @notice Thrown when an allowance on a token has expired.
/// @param deadline The timestamp at which the allowed amount is no longer valid
error AllowanceExpired(uint256 deadline);
/// @notice Thrown when an allowance on a token has been depleted.
/// @param amount The maximum amount allowed
error InsufficientAllowance(uint256 amount);
/// @notice Thrown when too many nonces are invalidated.
error ExcessiveInvalidation();
/// @notice Emits an event when the owner successfully invalidates an ordered nonce.
event NonceInvalidation(
address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
);
/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
event Approval(
address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
);
/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
event Permit(
address indexed owner,
address indexed token,
address indexed spender,
uint160 amount,
uint48 expiration,
uint48 nonce
);
/// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
event Lockdown(address indexed owner, address token, address spender);
/// @notice The permit data for a token
struct PermitDetails {
// ERC20 token address
address token;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice The permit message signed for a single token allowance
struct PermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The permit message signed for multiple token allowances
struct PermitBatch {
// the permit data for multiple token allowances
PermitDetails[] details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The saved permissions
/// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
/// @dev Setting amount to type(uint160).max sets an unlimited approval
struct PackedAllowance {
// amount allowed
uint160 amount;
// permission expiry
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice A token spender pair.
struct TokenSpenderPair {
// the token the spender is approved
address token;
// the spender address
address spender;
}
/// @notice Details for a token transfer.
struct AllowanceTransferDetails {
// the owner of the token
address from;
// the recipient of the token
address to;
// the amount of the token
uint160 amount;
// the token to be transferred
address token;
}
/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
/// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
/// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
function allowance(address user, address token, address spender)
external
view
returns (uint160 amount, uint48 expiration, uint48 nonce);
/// @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;
/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitSingle Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
/// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitBatch Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;
/// @notice Transfer approved tokens from one address to another
/// @param from The address to transfer from
/// @param to The address of the recipient
/// @param amount The amount of the token to transfer
/// @param token The token address to transfer
/// @dev Requires the from address to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(address from, address to, uint160 amount, address token) external;
/// @notice Transfer approved tokens in a batch
/// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
/// @dev Requires the from addresses to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
/// @notice Enables performing a "lockdown" of the sender's Permit2 identity
/// by batch revoking approvals
/// @param approvals Array of approvals to revoke.
function lockdown(TokenSpenderPair[] calldata approvals) external;
/// @notice Invalidate nonces for a given (token, spender) pair
/// @param token The token to invalidate nonces for
/// @param spender The spender to invalidate nonces for
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
/// @dev Can't invalidate more than 2**16 nonces per transaction.
function invalidateNonces(address token, address spender, uint48 newNonce) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
/// @notice Simple interface for permissioned calling of external functions.
interface IAuthentication {
/// @notice The sender does not have permission to call a function.
error SenderNotAllowed();
/**
* @notice Returns the action identifier associated with the external function described by `selector`.
* @param selector The 4-byte selector of the permissioned function
* @return actionId The computed actionId
*/
function getActionId(bytes4 selector) external view returns (bytes32 actionId);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
/// @notice Interface to the Vault's permission system.
interface IAuthorizer {
/**
* @notice Returns true if `account` can perform the action described by `actionId` in the contract `where`.
* @param actionId Identifier for the action to be performed
* @param account Account trying to perform the action
* @param where Target contract for the action
* @return success True if the action is permitted
*/
function canPerform(bytes32 actionId, address account, address where) external view returns (bool success);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { AddLiquidityKind, RemoveLiquidityKind, SwapKind } from "./VaultTypes.sol";
/// @notice Interface for the `BatchRouter`, supporting multi-hop swaps.
interface IBatchRouter {
/***************************************************************************
Swaps
***************************************************************************/
struct SwapPathStep {
address pool;
IERC20 tokenOut;
// If true, the "pool" is an ERC4626 Buffer. Used to wrap/unwrap tokens if pool doesn't have enough liquidity.
bool isBuffer;
}
struct SwapPathExactAmountIn {
IERC20 tokenIn;
// For each step:
// If tokenIn == pool, use removeLiquidity SINGLE_TOKEN_EXACT_IN.
// If tokenOut == pool, use addLiquidity UNBALANCED.
SwapPathStep[] steps;
uint256 exactAmountIn;
uint256 minAmountOut;
}
struct SwapPathExactAmountOut {
IERC20 tokenIn;
// for each step:
// If tokenIn == pool, use removeLiquidity SINGLE_TOKEN_EXACT_OUT.
// If tokenOut == pool, use addLiquidity SINGLE_TOKEN_EXACT_OUT.
SwapPathStep[] steps;
uint256 maxAmountIn;
uint256 exactAmountOut;
}
struct SwapExactInHookParams {
address sender;
SwapPathExactAmountIn[] paths;
uint256 deadline;
bool wethIsEth;
bytes userData;
}
struct SwapExactOutHookParams {
address sender;
SwapPathExactAmountOut[] paths;
uint256 deadline;
bool wethIsEth;
bytes userData;
}
/**
* @notice Executes a swap operation involving multiple paths (steps), specifying exact input token amounts.
* @param paths Swap paths from token in to token out, specifying exact amounts in
* @param deadline Deadline for the swap, after which it will revert
* @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
* @param userData Additional (optional) data required for the swap
* @return pathAmountsOut Calculated amounts of output tokens corresponding to the last step of each given path
* @return tokensOut Output token addresses
* @return amountsOut Calculated amounts of output tokens, ordered by output token address
*/
function swapExactIn(
SwapPathExactAmountIn[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
)
external
payable
returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut);
/**
* @notice Executes a swap operation involving multiple paths (steps), specifying exact output token amounts.
* @param paths Swap paths from token in to token out, specifying exact amounts out
* @param deadline Deadline for the swap
* @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
* @param userData Additional (optional) data required for the swap
* @return pathAmountsIn Calculated amounts of input tokens corresponding to the first step of each given path
* @return tokensIn Input token addresses
* @return amountsIn Calculated amounts of input tokens, ordered by input token address
*/
function swapExactOut(
SwapPathExactAmountOut[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn);
/***************************************************************************
Queries
***************************************************************************/
/**
* @notice Queries a swap operation involving multiple paths (steps), specifying exact input token amounts.
* @dev Min amounts out specified in the paths are ignored.
* @param paths Swap paths from token in to token out, specifying exact amounts in
* @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
* @param userData Additional (optional) data required for the query
* @return pathAmountsOut Calculated amounts of output tokens corresponding to the last step of each given path
* @return tokensOut Output token addresses
* @return amountsOut Calculated amounts of output tokens to be received, ordered by output token address
*/
function querySwapExactIn(
SwapPathExactAmountIn[] memory paths,
address sender,
bytes calldata userData
) external returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut);
/**
* @notice Queries a swap operation involving multiple paths (steps), specifying exact output token amounts.
* @dev Max amounts in specified in the paths are ignored.
* @param paths Swap paths from token in to token out, specifying exact amounts out
* @param sender The sender passed to the operation. It can influence results (e.g., with user-dependent hooks)
* @param userData Additional (optional) data required for the query
* @return pathAmountsIn Calculated amounts of input tokens corresponding to the last step of each given path
* @return tokensIn Input token addresses
* @return amountsIn Calculated amounts of input tokens to be received, ordered by input token address
*/
function querySwapExactOut(
SwapPathExactAmountOut[] memory paths,
address sender,
bytes calldata userData
) external returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEIP712 {
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
// Explicitly import VaultTypes structs because we expect this interface to be heavily used by external developers.
// Internally, when this list gets too long, we usually just do a simple import to keep things tidy.
import {
TokenConfig,
LiquidityManagement,
PoolSwapParams,
AfterSwapParams,
HookFlags,
AddLiquidityKind,
RemoveLiquidityKind,
SwapKind
} from "./VaultTypes.sol";
/**
* @notice Interface for pool hooks.
* @dev Hooks are functions invoked by the Vault at specific points in the flow of each operation. This guarantees that
* they are called in the correct order, and with the correct arguments. To maintain this security, these functions
* should only be called by the Vault. The recommended way to do this is to derive the hook contract from `BaseHooks`,
* then use the `onlyVault` modifier from `VaultGuard`. (See the examples in /pool-hooks.)
*/
interface IHooks {
/***************************************************************************
Register
***************************************************************************/
/**
* @notice Hook executed when a pool is registered with a non-zero hooks contract.
* @dev Returns true if registration was successful, and false to revert the pool registration.
* Make sure this function is properly implemented (e.g. check the factory, and check that the
* given pool is from the factory). The Vault address will be msg.sender.
*
* @param factory Address of the pool factory (contract deploying the pool)
* @param pool Address of the pool
* @param tokenConfig An array of descriptors for the tokens the pool will manage
* @param liquidityManagement Liquidity management flags indicating which functions are enabled
* @return success True if the hook allowed the registration, false otherwise
*/
function onRegister(
address factory,
address pool,
TokenConfig[] memory tokenConfig,
LiquidityManagement calldata liquidityManagement
) external returns (bool success);
/**
* @notice Return the set of hooks implemented by the contract.
* @dev The Vault will only call hooks the pool says it supports, and of course only if a hooks contract is defined
* (i.e., the `poolHooksContract` in `PoolRegistrationParams` is non-zero).
* `onRegister` is the only "mandatory" hook.
*
* @return hookFlags Flags indicating which hooks the contract supports
*/
function getHookFlags() external view returns (HookFlags memory hookFlags);
/***************************************************************************
Initialize
***************************************************************************/
/**
* @notice Hook executed before pool initialization.
* @dev Called if the `shouldCallBeforeInitialize` flag is set in the configuration. Hook contracts should use
* the `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param exactAmountsIn Exact amounts of input tokens
* @param userData Optional, arbitrary data sent with the encoded request
* @return success True if the pool wishes to proceed with initialization
*/
function onBeforeInitialize(uint256[] memory exactAmountsIn, bytes memory userData) external returns (bool success);
/**
* @notice Hook to be executed after pool initialization.
* @dev Called if the `shouldCallAfterInitialize` flag is set in the configuration. Hook contracts should use
* the `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param exactAmountsIn Exact amounts of input tokens
* @param bptAmountOut Amount of pool tokens minted during initialization
* @param userData Optional, arbitrary data sent with the encoded request
* @return success True if the pool accepts the initialization results
*/
function onAfterInitialize(
uint256[] memory exactAmountsIn,
uint256 bptAmountOut,
bytes memory userData
) external returns (bool success);
/***************************************************************************
Add Liquidity
***************************************************************************/
/**
* @notice Hook to be executed before adding liquidity.
* @dev Called if the `shouldCallBeforeAddLiquidity` flag is set in the configuration. Hook contracts should use
* the `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param router The address (usually a router contract) that initiated an add liquidity operation on the Vault
* @param pool Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.)
* @param kind The add liquidity operation type (e.g., proportional, custom)
* @param maxAmountsInScaled18 Maximum amounts of input tokens
* @param minBptAmountOut Minimum amount of output pool tokens
* @param balancesScaled18 Current pool balances, sorted in token registration order
* @param userData Optional, arbitrary data sent with the encoded request
* @return success True if the pool wishes to proceed with settlement
*/
function onBeforeAddLiquidity(
address router,
address pool,
AddLiquidityKind kind,
uint256[] memory maxAmountsInScaled18,
uint256 minBptAmountOut,
uint256[] memory balancesScaled18,
bytes memory userData
) external returns (bool success);
/**
* @notice Hook to be executed after adding liquidity.
* @dev Called if the `shouldCallAfterAddLiquidity` flag is set in the configuration. The Vault will ignore
* `hookAdjustedAmountsInRaw` unless `enableHookAdjustedAmounts` is true. Hook contracts should use the
* `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param router The address (usually a router contract) that initiated an add liquidity operation on the Vault
* @param pool Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.)
* @param kind The add liquidity operation type (e.g., proportional, custom)
* @param amountsInScaled18 Actual amounts of tokens added, sorted in token registration order
* @param amountsInRaw Actual amounts of tokens added, sorted in token registration order
* @param bptAmountOut Amount of pool tokens minted
* @param balancesScaled18 Current pool balances, sorted in token registration order
* @param userData Additional (optional) data provided by the user
* @return success True if the pool wishes to proceed with settlement
* @return hookAdjustedAmountsInRaw New amountsInRaw, potentially modified by the hook
*/
function onAfterAddLiquidity(
address router,
address pool,
AddLiquidityKind kind,
uint256[] memory amountsInScaled18,
uint256[] memory amountsInRaw,
uint256 bptAmountOut,
uint256[] memory balancesScaled18,
bytes memory userData
) external returns (bool success, uint256[] memory hookAdjustedAmountsInRaw);
/***************************************************************************
Remove Liquidity
***************************************************************************/
/**
* @notice Hook to be executed before removing liquidity.
* @dev Called if the `shouldCallBeforeRemoveLiquidity` flag is set in the configuration. Hook contracts should use
* the `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param router The address (usually a router contract) that initiated a remove liquidity operation on the Vault
* @param pool Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.)
* @param kind The type of remove liquidity operation (e.g., proportional, custom)
* @param maxBptAmountIn Maximum amount of input pool tokens
* @param minAmountsOutScaled18 Minimum output amounts, sorted in token registration order
* @param balancesScaled18 Current pool balances, sorted in token registration order
* @param userData Optional, arbitrary data sent with the encoded request
* @return success True if the pool wishes to proceed with settlement
*/
function onBeforeRemoveLiquidity(
address router,
address pool,
RemoveLiquidityKind kind,
uint256 maxBptAmountIn,
uint256[] memory minAmountsOutScaled18,
uint256[] memory balancesScaled18,
bytes memory userData
) external returns (bool success);
/**
* @notice Hook to be executed after removing liquidity.
* @dev Called if the `shouldCallAfterRemoveLiquidity` flag is set in the configuration. The Vault will ignore
* `hookAdjustedAmountsOutRaw` unless `enableHookAdjustedAmounts` is true. Hook contracts should use the
* `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param router The address (usually a router contract) that initiated a remove liquidity operation on the Vault
* @param pool Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.)
* @param kind The type of remove liquidity operation (e.g., proportional, custom)
* @param bptAmountIn Amount of pool tokens to burn
* @param amountsOutScaled18 Scaled amount of tokens to receive, sorted in token registration order
* @param amountsOutRaw Actual amount of tokens to receive, sorted in token registration order
* @param balancesScaled18 Current pool balances, sorted in token registration order
* @param userData Additional (optional) data provided by the user
* @return success True if the pool wishes to proceed with settlement
* @return hookAdjustedAmountsOutRaw New amountsOutRaw, potentially modified by the hook
*/
function onAfterRemoveLiquidity(
address router,
address pool,
RemoveLiquidityKind kind,
uint256 bptAmountIn,
uint256[] memory amountsOutScaled18,
uint256[] memory amountsOutRaw,
uint256[] memory balancesScaled18,
bytes memory userData
) external returns (bool success, uint256[] memory hookAdjustedAmountsOutRaw);
/***************************************************************************
Swap
***************************************************************************/
/**
* @notice Called before a swap to give the Pool an opportunity to perform actions.
* @dev Called if the `shouldCallBeforeSwap` flag is set in the configuration. Hook contracts should use the
* `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param params Swap parameters (see PoolSwapParams for struct definition)
* @param pool Pool address, used to get pool information from the Vault (poolData, token config, etc.)
* @return success True if the pool wishes to proceed with settlement
*/
function onBeforeSwap(PoolSwapParams calldata params, address pool) external returns (bool success);
/**
* @notice Called after a swap to perform further actions once the balances have been updated by the swap.
* @dev Called if the `shouldCallAfterSwap` flag is set in the configuration. The Vault will ignore
* `hookAdjustedAmountCalculatedRaw` unless `enableHookAdjustedAmounts` is true. Hook contracts should
* use the `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param params Swap parameters (see above for struct definition)
* @return success True if the pool wishes to proceed with settlement
* @return hookAdjustedAmountCalculatedRaw New amount calculated, potentially modified by the hook
*/
function onAfterSwap(
AfterSwapParams calldata params
) external returns (bool success, uint256 hookAdjustedAmountCalculatedRaw);
/**
* @notice Called after `onBeforeSwap` and before the main swap operation, if the pool has dynamic fees.
* @dev Called if the `shouldCallComputeDynamicSwapFee` flag is set in the configuration. Hook contracts should use
* the `onlyVault` modifier to guarantee this is only called by the Vault.
*
* @param params Swap parameters (see PoolSwapParams for struct definition)
* @param pool Pool address, used to get pool information from the Vault (poolData, token config, etc.)
* @param staticSwapFeePercentage 18-decimal FP value of the static swap fee percentage, for reference
* @return success True if the pool wishes to proceed with settlement
* @return dynamicSwapFeePercentage Value of the swap fee percentage, as an 18-decimal FP value
*/
function onComputeDynamicSwapFeePercentage(
PoolSwapParams calldata params,
address pool,
uint256 staticSwapFeePercentage
) external view returns (bool success, uint256 dynamicSwapFeePercentage);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISignatureTransfer} from "./ISignatureTransfer.sol";
import {IAllowanceTransfer} from "./IAllowanceTransfer.sol";
/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer.
/// @dev Users must approve Permit2 before calling any of the transfer functions.
interface IPermit2 is ISignatureTransfer, IAllowanceTransfer {
// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval.
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IVault } from "./IVault.sol";
/// @notice Contract that handles protocol and pool creator fees for the Vault.
interface IProtocolFeeController {
/**
* @notice Emitted when the protocol swap fee percentage is updated.
* @param swapFeePercentage The updated protocol swap fee percentage
*/
event GlobalProtocolSwapFeePercentageChanged(uint256 swapFeePercentage);
/**
* @notice Emitted when the protocol yield fee percentage is updated.
* @param yieldFeePercentage The updated protocol yield fee percentage
*/
event GlobalProtocolYieldFeePercentageChanged(uint256 yieldFeePercentage);
/**
* @notice Emitted when the protocol swap fee percentage is updated for a specific pool.
* @param pool The pool whose protocol swap fee will be changed
* @param swapFeePercentage The updated protocol swap fee percentage
*/
event ProtocolSwapFeePercentageChanged(address indexed pool, uint256 swapFeePercentage);
/**
* @notice Emitted when the protocol yield fee percentage is updated for a specific pool.
* @param pool The pool whose protocol yield fee will be changed
* @param yieldFeePercentage The updated protocol yield fee percentage
*/
event ProtocolYieldFeePercentageChanged(address indexed pool, uint256 yieldFeePercentage);
/**
* @notice Emitted when the pool creator swap fee percentage of a pool is updated.
* @param pool The pool whose pool creator swap fee will be changed
* @param poolCreatorSwapFeePercentage The new pool creator swap fee percentage for the pool
*/
event PoolCreatorSwapFeePercentageChanged(address indexed pool, uint256 poolCreatorSwapFeePercentage);
/**
* @notice Emitted when the pool creator yield fee percentage of a pool is updated.
* @param pool The pool whose pool creator yield fee will be changed
* @param poolCreatorYieldFeePercentage The new pool creator yield fee percentage for the pool
*/
event PoolCreatorYieldFeePercentageChanged(address indexed pool, uint256 poolCreatorYieldFeePercentage);
/**
* @notice Logs the collection of protocol swap fees in a specific token and amount.
* @dev Note that since charging protocol fees (i.e., distributing tokens between pool and fee balances) occurs
* in the Vault, but fee collection happens in the ProtocolFeeController, the swap fees reported here may encompass
* multiple operations.
*
* @param pool The pool on which the swap fee was charged
* @param token The token in which the swap fee was charged
* @param amount The amount of the token collected in fees
*/
event ProtocolSwapFeeCollected(address indexed pool, IERC20 indexed token, uint256 amount);
/**
* @notice Logs the collection of protocol yield fees in a specific token and amount.
* @dev Note that since charging protocol fees (i.e., distributing tokens between pool and fee balances) occurs
* in the Vault, but fee collection happens in the ProtocolFeeController, the yield fees reported here may encompass
* multiple operations.
*
* @param pool The pool on which the yield fee was charged
* @param token The token in which the yield fee was charged
* @param amount The amount of the token collected in fees
*/
event ProtocolYieldFeeCollected(address indexed pool, IERC20 indexed token, uint256 amount);
/**
* @notice Logs the withdrawal of protocol fees in a specific token and amount.
* @param pool The pool from which protocol fees are being withdrawn
* @param token The token being withdrawn
* @param recipient The recipient of the funds
* @param amount The amount of the fee token that was withdrawn
*/
event ProtocolFeesWithdrawn(address indexed pool, IERC20 indexed token, address indexed recipient, uint256 amount);
/**
* @notice Logs the withdrawal of pool creator fees in a specific token and amount.
* @param pool The pool from which pool creator fees are being withdrawn
* @param token The token being withdrawn
* @param recipient The recipient of the funds (the pool creator if permissionless, or another account)
* @param amount The amount of the fee token that was withdrawn
*/
event PoolCreatorFeesWithdrawn(
address indexed pool,
IERC20 indexed token,
address indexed recipient,
uint256 amount
);
/**
* @notice Error raised when the protocol swap fee percentage exceeds the maximum allowed value.
* @dev Note that this is checked for both the global and pool-specific protocol swap fee percentages.
*/
error ProtocolSwapFeePercentageTooHigh();
/**
* @notice Error raised when the protocol yield fee percentage exceeds the maximum allowed value.
* @dev Note that this is checked for both the global and pool-specific protocol yield fee percentages.
*/
error ProtocolYieldFeePercentageTooHigh();
/**
* @notice Error raised if there is no pool creator on a withdrawal attempt from the given pool.
* @param pool The pool with no creator
*/
error PoolCreatorNotRegistered(address pool);
/**
* @notice Error raised if the wrong account attempts to withdraw pool creator fees.
* @param caller The account attempting to withdraw pool creator fees
* @param pool The pool the caller tried to withdraw from
*/
error CallerIsNotPoolCreator(address caller, address pool);
/// @notice Error raised when the pool creator swap or yield fee percentage exceeds the maximum allowed value.
error PoolCreatorFeePercentageTooHigh();
/**
* @notice Get the address of the main Vault contract.
* @return vault The Vault address
*/
function vault() external view returns (IVault);
/**
* @notice Collects aggregate fees from the Vault for a given pool.
* @param pool The pool with aggregate fees
*/
function collectAggregateFees(address pool) external;
/**
* @notice Getter for the current global protocol swap fee.
* @return protocolSwapFeePercentage The global protocol swap fee percentage
*/
function getGlobalProtocolSwapFeePercentage() external view returns (uint256 protocolSwapFeePercentage);
/**
* @notice Getter for the current global protocol yield fee.
* @return protocolYieldFeePercentage The global protocol yield fee percentage
*/
function getGlobalProtocolYieldFeePercentage() external view returns (uint256 protocolYieldFeePercentage);
/**
* @notice Getter for the current protocol swap fee for a given pool.
* @param pool The address of the pool
* @return protocolSwapFeePercentage The global protocol swap fee percentage
* @return isOverride True if the protocol fee has been overridden
*/
function getPoolProtocolSwapFeeInfo(
address pool
) external view returns (uint256 protocolSwapFeePercentage, bool isOverride);
/**
* @notice Getter for the current protocol yield fee for a given pool.
* @param pool The address of the pool
* @return protocolYieldFeePercentage The global protocol yield fee percentage
* @return isOverride True if the protocol fee has been overridden
*/
function getPoolProtocolYieldFeeInfo(
address pool
) external view returns (uint256 protocolYieldFeePercentage, bool isOverride);
/**
* @notice Returns the amount of each pool token allocated to the protocol for withdrawal.
* @dev Includes both swap and yield fees.
* @param pool The address of the pool on which fees were collected
* @return feeAmounts The total amounts of each token available for withdrawal, sorted in token registration order
*/
function getProtocolFeeAmounts(address pool) external view returns (uint256[] memory feeAmounts);
/**
* @notice Returns the amount of each pool token allocated to the pool creator for withdrawal.
* @dev Includes both swap and yield fees.
* @param pool The address of the pool on which fees were collected
* @return feeAmounts The total amounts of each token available for withdrawal, sorted in token registration order
*/
function getPoolCreatorFeeAmounts(address pool) external view returns (uint256[] memory feeAmounts);
/**
* @notice Returns a calculated aggregate percentage from protocol and pool creator fee percentages.
* @dev Not tied to any particular pool; this just performs the low-level "additive fee" calculation. Note that
* pool creator fees are calculated based on creatorAndLpFees, and not in totalFees. Since aggregate fees are
* stored in the Vault with 24-bit precision, this will truncate any values that require greater precision.
* It is expected that pool creators will negotiate with the DAO and agree on reasonable values for these fee
* components, but the truncation ensures it will not revert for any valid set of fee percentages.
*
* See example below:
*
* tokenOutAmount = 10000; poolSwapFeePct = 10%; protocolFeePct = 40%; creatorFeePct = 60%
* totalFees = tokenOutAmount * poolSwapFeePct = 10000 * 10% = 1000
* protocolFees = totalFees * protocolFeePct = 1000 * 40% = 400
* creatorAndLpFees = totalFees - protocolFees = 1000 - 400 = 600
* creatorFees = creatorAndLpFees * creatorFeePct = 600 * 60% = 360
* lpFees (will stay in the pool) = creatorAndLpFees - creatorFees = 600 - 360 = 240
*
* @param protocolFeePercentage The protocol portion of the aggregate fee percentage
* @param poolCreatorFeePercentage The pool creator portion of the aggregate fee percentage
* @return aggregateFeePercentage The computed aggregate percentage
*/
function computeAggregateFeePercentage(
uint256 protocolFeePercentage,
uint256 poolCreatorFeePercentage
) external pure returns (uint256 aggregateFeePercentage);
/**
* @notice Override the protocol swap fee percentage for a specific pool.
* @dev This is a permissionless call, and will set the pool's fee to the current global fee, if it is different
* from the current value, and the fee is not controlled by governance (i.e., has never been overridden).
*
* @param pool The pool for which we are setting the protocol swap fee
*/
function updateProtocolSwapFeePercentage(address pool) external;
/**
* @notice Override the protocol yield fee percentage for a specific pool.
* @dev This is a permissionless call, and will set the pool's fee to the current global fee, if it is different
* from the current value, and the fee is not controlled by governance (i.e., has never been overridden).
*
* @param pool The pool for which we are setting the protocol yield fee
*/
function updateProtocolYieldFeePercentage(address pool) external;
/***************************************************************************
Permissioned Functions
***************************************************************************/
/**
* @notice Add pool-specific entries to the protocol swap and yield percentages.
* @dev This must be called from the Vault during pool registration. It will initialize the pool to the global
* protocol fee percentage values (or 0, if the `protocolFeeExempt` flags is set), and return the initial aggregate
* fee percentages, based on an initial pool creator fee of 0.
*
* @param pool The address of the pool being registered
* @param poolCreator The address of the pool creator (or 0 if there won't be a pool creator fee)
* @param protocolFeeExempt If true, the pool is initially exempt from protocol fees
* @return aggregateSwapFeePercentage The initial aggregate swap fee percentage
* @return aggregateYieldFeePercentage The initial aggregate yield fee percentage
*/
function registerPool(
address pool,
address poolCreator,
bool protocolFeeExempt
) external returns (uint256 aggregateSwapFeePercentage, uint256 aggregateYieldFeePercentage);
/**
* @notice Set the global protocol swap fee percentage, used by standard pools.
* @param newProtocolSwapFeePercentage The new protocol swap fee percentage
*/
function setGlobalProtocolSwapFeePercentage(uint256 newProtocolSwapFeePercentage) external;
/**
* @notice Set the global protocol yield fee percentage, used by standard pools.
* @param newProtocolYieldFeePercentage The new protocol yield fee percentage
*/
function setGlobalProtocolYieldFeePercentage(uint256 newProtocolYieldFeePercentage) external;
/**
* @notice Override the protocol swap fee percentage for a specific pool.
* @param pool The address of the pool for which we are setting the protocol swap fee
* @param newProtocolSwapFeePercentage The new protocol swap fee percentage for the pool
*/
function setProtocolSwapFeePercentage(address pool, uint256 newProtocolSwapFeePercentage) external;
/**
* @notice Override the protocol yield fee percentage for a specific pool.
* @param pool The address of the pool for which we are setting the protocol yield fee
* @param newProtocolYieldFeePercentage The new protocol yield fee percentage for the pool
*/
function setProtocolYieldFeePercentage(address pool, uint256 newProtocolYieldFeePercentage) external;
/**
* @notice Assigns a new pool creator swap fee percentage to the specified pool.
* @dev Fees are divided between the protocol, pool creator, and LPs. The pool creator percentage is applied to
* the "net" amount after protocol fees, and divides the remainder between the pool creator and LPs. If the
* pool creator fee is near 100%, almost none of the fee amount remains in the pool for LPs.
*
* @param pool The address of the pool for which the pool creator fee will be changed
* @param poolCreatorSwapFeePercentage The new pool creator swap fee percentage to apply to the pool
*/
function setPoolCreatorSwapFeePercentage(address pool, uint256 poolCreatorSwapFeePercentage) external;
/**
* @notice Assigns a new pool creator yield fee percentage to the specified pool.
* @dev Fees are divided between the protocol, pool creator, and LPs. The pool creator percentage is applied to
* the "net" amount after protocol fees, and divides the remainder between the pool creator and LPs. If the
* pool creator fee is near 100%, almost none of the fee amount remains in the pool for LPs.
*
* @param pool The address of the pool for which the pool creator fee will be changed
* @param poolCreatorYieldFeePercentage The new pool creator yield fee percentage to apply to the pool
*/
function setPoolCreatorYieldFeePercentage(address pool, uint256 poolCreatorYieldFeePercentage) external;
/**
* @notice Withdraw collected protocol fees for a given pool (all tokens). This is a permissioned function.
* @dev Sends swap and yield protocol fees to the recipient.
* @param pool The pool on which fees were collected
* @param recipient Address to send the tokens
*/
function withdrawProtocolFees(address pool, address recipient) external;
/**
* @notice Withdraw collected protocol fees for a given pool and a given token. This is a permissioned function.
* @dev Sends swap and yield protocol fees to the recipient.
* @param pool The pool on which fees were collected
* @param recipient Address to send the tokens
* @param token Token to withdraw
*/
function withdrawProtocolFeesForToken(address pool, address recipient, IERC20 token) external;
/**
* @notice Withdraw collected pool creator fees for a given pool. This is a permissioned function.
* @dev Sends swap and yield pool creator fees to the recipient.
* @param pool The pool on which fees were collected
* @param recipient Address to send the tokens
*/
function withdrawPoolCreatorFees(address pool, address recipient) external;
/**
* @notice Withdraw collected pool creator fees for a given pool.
* @dev Sends swap and yield pool creator fees to the registered poolCreator. Since this is a known and immutable
* value, this function is permissionless.
*
* @param pool The pool on which fees were collected
*/
function withdrawPoolCreatorFees(address pool) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
/// @notice General interface for token exchange rates.
interface IRateProvider {
/**
* @notice An 18 decimal fixed point number representing the exchange rate of one token to another related token.
* @dev The meaning of this rate depends on the context. Note that there may be an error associated with a token
* rate, and the caller might require a certain rounding direction to ensure correctness. This (legacy) interface
* does not take a rounding direction or return an error, so great care must be taken when interpreting and using
* rates in downstream computations.
*
* @return rate The current token rate
*/
function getRate() external view returns (uint256 rate);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol";
import { AddLiquidityKind, RemoveLiquidityKind } from "./VaultTypes.sol";
/// @notice Interface for functions shared between the `Router` and `BatchRouter`.
interface IRouterCommon {
/**
* @notice Data for the add liquidity hook.
* @param sender Account originating the add liquidity operation
* @param pool Address of the liquidity pool
* @param maxAmountsIn Maximum amounts of tokens to be added, sorted in token registration order
* @param minBptAmountOut Minimum amount of pool tokens to be received
* @param kind Type of join (e.g., single or multi-token)
* @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
* @param userData Additional (optional) data sent with the request to add liquidity
*/
struct AddLiquidityHookParams {
address sender;
address pool;
uint256[] maxAmountsIn;
uint256 minBptAmountOut;
AddLiquidityKind kind;
bool wethIsEth;
bytes userData;
}
/**
* @notice Data for the remove liquidity hook.
* @param sender Account originating the remove liquidity operation
* @param pool Address of the liquidity pool
* @param minAmountsOut Minimum amounts of tokens to be received, sorted in token registration order
* @param maxBptAmountIn Maximum amount of pool tokens provided
* @param kind Type of exit (e.g., single or multi-token)
* @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
* @param userData Additional (optional) data sent with the request to remove liquidity
*/
struct RemoveLiquidityHookParams {
address sender;
address pool;
uint256[] minAmountsOut;
uint256 maxBptAmountIn;
RemoveLiquidityKind kind;
bool wethIsEth;
bytes userData;
}
/**
* @notice Get the first sender which initialized the call to Router.
* @return sender The address of the sender
*/
function getSender() external view returns (address sender);
/*******************************************************************************
Utils
*******************************************************************************/
struct PermitApproval {
address token;
address owner;
address spender;
uint256 amount;
uint256 nonce;
uint256 deadline;
}
/**
* @notice Permits multiple allowances and executes a batch of function calls on this contract.
* @param permitBatch An array of `PermitApproval` structs, each representing an ERC20 permit request
* @param permitSignatures An array of bytes, corresponding to the permit request signature in `permitBatch`
* @param permit2Batch A batch of permit2 approvals
* @param permit2Signature A permit2 signature for the batch approval
* @param multicallData An array of bytes arrays, each representing an encoded function call on this contract
* @return results Array of bytes arrays, each representing the return data from each function call executed
*/
function permitBatchAndCall(
PermitApproval[] calldata permitBatch,
bytes[] calldata permitSignatures,
IAllowanceTransfer.PermitBatch calldata permit2Batch,
bytes calldata permit2Signature,
bytes[] calldata multicallData
) external payable returns (bytes[] memory results);
/**
* @notice Executes a batch of function calls on this contract.
* @param data Encoded function calls to be executed in the batch.
* @return results Array of bytes arrays, each representing the return data from each function call executed.
*/
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEIP712} from "./IEIP712.sol";
/// @title SignatureTransfer
/// @notice Handles ERC20 token transfers through signature based actions
/// @dev Requires user's token approval on the Permit2 contract
interface ISignatureTransfer is IEIP712 {
/// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount
/// @param maxAmount The maximum amount a spender can request to transfer
error InvalidAmount(uint256 maxAmount);
/// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred
/// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred
error LengthMismatch();
/// @notice Emits an event when the owner successfully invalidates an unordered nonce.
event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);
/// @notice The token and amount details for a transfer signed in the permit transfer signature
struct TokenPermissions {
// ERC20 token address
address token;
// the maximum amount that can be spent
uint256 amount;
}
/// @notice The signed permit message for a single token transfer
struct PermitTransferFrom {
TokenPermissions permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}
/// @notice Specifies the recipient address and amount for batched transfers.
/// @dev Recipients and amounts correspond to the index of the signed token permissions array.
/// @dev Reverts if the requested amount is greater than the permitted signed amount.
struct SignatureTransferDetails {
// recipient address
address to;
// spender requested amount
uint256 requestedAmount;
}
/// @notice Used to reconstruct the signed permit message for multiple token transfers
/// @dev Do not need to pass in spender address as it is required that it is msg.sender
/// @dev Note that a user still signs over a spender address
struct PermitBatchTransferFrom {
// the tokens and corresponding amounts permitted for a transfer
TokenPermissions[] permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}
/// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection
/// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order
/// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce
/// @dev It returns a uint256 bitmap
/// @dev The index, or wordPosition is capped at type(uint248).max
function nonceBitmap(address, uint256) external view returns (uint256);
/// @notice Transfers a token using a signed permit message
/// @dev Reverts if the requested amount is greater than the permitted signed amount
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails The spender's requested transfer details for the permitted token
/// @param signature The signature to verify
function permitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external;
/// @notice Transfers a token using a signed permit message
/// @notice Includes extra data provided by the caller to verify signature over
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
/// @dev Reverts if the requested amount is greater than the permitted signed amount
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails The spender's requested transfer details for the permitted token
/// @param witness Extra data to include when checking the user signature
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
/// @param signature The signature to verify
function permitWitnessTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;
/// @notice Transfers multiple tokens using a signed permit message
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails Specifies the recipient and requested amount for the token transfer
/// @param signature The signature to verify
function permitTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes calldata signature
) external;
/// @notice Transfers multiple tokens using a signed permit message
/// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
/// @notice Includes extra data provided by the caller to verify signature over
/// @param permit The permit data signed over by the owner
/// @param owner The owner of the tokens to transfer
/// @param transferDetails Specifies the recipient and requested amount for the token transfer
/// @param witness Extra data to include when checking the user signature
/// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
/// @param signature The signature to verify
function permitWitnessTransferFrom(
PermitBatchTransferFrom memory permit,
SignatureTransferDetails[] calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;
/// @notice Invalidates the bits specified in mask for the bitmap at the word position
/// @dev The wordPos is maxed at type(uint248).max
/// @param wordPos A number to index the nonceBitmap at
/// @param mask A bitmap masked against msg.sender's current bitmap at the word position
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IAuthentication } from "../solidity-utils/helpers/IAuthentication.sol";
import { IVaultExtension } from "./IVaultExtension.sol";
import { IVaultErrors } from "./IVaultErrors.sol";
import { IVaultEvents } from "./IVaultEvents.sol";
import { IVaultAdmin } from "./IVaultAdmin.sol";
import { IVaultMain } from "./IVaultMain.sol";
/// @notice Composite interface for all Vault operations: swap, add/remove liquidity, and associated queries.
interface IVault is IVaultMain, IVaultExtension, IVaultAdmin, IVaultErrors, IVaultEvents, IAuthentication {
/// @return vault The main Vault address.
function vault() external view override(IVaultAdmin, IVaultExtension) returns (IVault);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IProtocolFeeController } from "./IProtocolFeeController.sol";
import { IAuthorizer } from "./IAuthorizer.sol";
import { IVault } from "./IVault.sol";
/**
* @notice Interface for functions defined on the `VaultAdmin` contract.
* @dev `VaultAdmin` is the Proxy extension of `VaultExtension`, and handles the least critical operations,
* as two delegate calls add gas to each call. Most of the permissioned calls are here.
*/
interface IVaultAdmin {
/*******************************************************************************
Constants and immutables
*******************************************************************************/
/**
* @notice Returns the main Vault address.
* @dev The main Vault contains the entrypoint and main liquidity operation implementations.
* @return vault The address of the main Vault
*/
function vault() external view returns (IVault);
/**
* @notice Returns the Vault's pause window end time.
* @dev This value is immutable, and represents the timestamp after which the Vault can no longer be paused
* by governance. Balancer timestamps are 32 bits.
*
* @return pauseWindowEndTime The timestamp when the Vault's pause window ends
*/
function getPauseWindowEndTime() external view returns (uint32 pauseWindowEndTime);
/**
* @notice Returns the Vault's buffer period duration.
* @dev This value is immutable. It represents the period during which, if paused, the Vault will remain paused.
* This ensures there is time available to address whatever issue caused the Vault to be paused. Balancer
* timestamps are 32 bits.
*
* @return bufferPeriodDuration The length of the buffer period in seconds
*/
function getBufferPeriodDuration() external view returns (uint32 bufferPeriodDuration);
/**
* @notice Returns the Vault's buffer period end time.
* @dev This value is immutable. If already paused, the Vault can be unpaused until this timestamp. Balancer
* timestamps are 32 bits.
*
* @return bufferPeriodEndTime The timestamp after which the Vault remains permanently unpaused
*/
function getBufferPeriodEndTime() external view returns (uint32 bufferPeriodEndTime);
/**
* @notice Get the minimum number of tokens in a pool.
* @dev We expect the vast majority of pools to be 2-token.
* @return minTokens The minimum token count of a pool
*/
function getMinimumPoolTokens() external pure returns (uint256 minTokens);
/**
* @notice Get the maximum number of tokens in a pool.
* @return maxTokens The maximum token count of a pool
*/
function getMaximumPoolTokens() external pure returns (uint256 maxTokens);
/**
* @notice Get the minimum total supply of pool tokens (BPT) for an initialized pool.
* @dev This prevents pools from being completely drained. When the pool is initialized, this minimum amount of BPT
* is minted to the zero address. This is an 18-decimal floating point number; BPT are always 18 decimals.
*
* @return poolMinimumTotalSupply The minimum total supply a pool can have after initialization
*/
function getPoolMinimumTotalSupply() external pure returns (uint256 poolMinimumTotalSupply);
/**
* @notice Get the minimum total supply of an ERC4626 wrapped token buffer in the Vault.
* @dev This prevents buffers from being completely drained. When the buffer is initialized, this minimum number
* of shares is added to the shares resulting from the initial deposit. Buffer total supply accounting is internal
* to the Vault, as buffers are not tokenized.
*
* @return bufferMinimumTotalSupply The minimum total supply a buffer can have after initialization
*/
function getBufferMinimumTotalSupply() external pure returns (uint256 bufferMinimumTotalSupply);
/**
* @notice Get the minimum trade amount in a pool operation.
* @dev This limit is applied to the 18-decimal "upscaled" amount in any operation (swap, add/remove liquidity).
* @return minimumTradeAmount The minimum trade amount as an 18-decimal floating point number
*/
function getMinimumTradeAmount() external view returns (uint256 minimumTradeAmount);
/**
* @notice Get the minimum wrap amount in a buffer operation.
* @dev This limit is applied to the wrap operation amount, in native underlying token decimals.
* @return minimumWrapAmount The minimum wrap amount in native underlying token decimals
*/
function getMinimumWrapAmount() external view returns (uint256 minimumWrapAmount);
/*******************************************************************************
Vault Pausing
*******************************************************************************/
/**
* @notice Indicates whether the Vault is paused.
* @dev If the Vault is paused, all non-Recovery Mode state-changing operations on pools will revert. Note that
* ERC4626 buffers and the Vault have separate and independent pausing mechanisms. Pausing the Vault does not
* also pause buffers (though we anticipate they would likely be paused and unpaused together). Call
* `areBuffersPaused` to check the pause state of the buffers.
*
* @return vaultPaused True if the Vault is paused
*/
function isVaultPaused() external view returns (bool vaultPaused);
/**
* @notice Returns the paused status, and end times of the Vault's pause window and buffer period.
* @dev Balancer timestamps are 32 bits.
* @return vaultPaused True if the Vault is paused
* @return vaultPauseWindowEndTime The timestamp of the end of the Vault's pause window
* @return vaultBufferPeriodEndTime The timestamp of the end of the Vault's buffer period
*/
function getVaultPausedState()
external
view
returns (bool vaultPaused, uint32 vaultPauseWindowEndTime, uint32 vaultBufferPeriodEndTime);
/**
* @notice Pause the Vault: an emergency action which disables all operational state-changing functions on pools.
* @dev This is a permissioned function that will only work during the Pause Window set during deployment.
* Note that ERC4626 buffer operations have an independent pause mechanism, which is not affected by pausing
* the Vault. Custom routers could still wrap/unwrap using buffers while the Vault is paused, unless buffers
* are also paused (with `pauseVaultBuffers`).
*/
function pauseVault() external;
/**
* @notice Reverse a `pause` operation, and restore Vault pool operations to normal functionality.
* @dev This is a permissioned function that will only work on a paused Vault within the Buffer Period set during
* deployment. Note that the Vault will automatically unpause after the Buffer Period expires. As noted above,
* ERC4626 buffers and Vault operations on pools are independent. Unpausing the Vault does not reverse
* `pauseVaultBuffers`. If buffers were also paused, they will remain in that state until explicitly unpaused.
*/
function unpauseVault() external;
/*******************************************************************************
Pool Pausing
*******************************************************************************/
/**
* @notice Pause the Pool: an emergency action which disables all pool functions.
* @dev This is a permissioned function that will only work during the Pause Window set during pool factory
* deployment.
*
* @param pool The pool being paused
*/
function pausePool(address pool) external;
/**
* @notice Reverse a `pause` operation, and restore the Pool to normal functionality.
* @dev This is a permissioned function that will only work on a paused Pool within the Buffer Period set during
* deployment. Note that the Pool will automatically unpause after the Buffer Period expires.
*
* @param pool The pool being unpaused
*/
function unpausePool(address pool) external;
/*******************************************************************************
Fees
*******************************************************************************/
/**
* @notice Assigns a new static swap fee percentage to the specified pool.
* @dev This is a permissioned function, disabled if the pool is paused. The swap fee percentage must be within
* the bounds specified by the pool's implementation of `ISwapFeePercentageBounds`.
* Emits the SwapFeePercentageChanged event.
*
* @param pool The address of the pool for which the static swap fee will be changed
* @param swapFeePercentage The new swap fee percentage to apply to the pool
*/
function setStaticSwapFeePercentage(address pool, uint256 swapFeePercentage) external;
/**
* @notice Collects accumulated aggregate swap and yield fees for the specified pool.
* @dev Fees are sent to the ProtocolFeeController address.
* @param pool The pool on which all aggregate fees should be collected
* @return swapFeeAmounts An array with the total swap fees collected, sorted in token registration order
* @return yieldFeeAmounts An array with the total yield fees collected, sorted in token registration order
*/
function collectAggregateFees(
address pool
) external returns (uint256[] memory swapFeeAmounts, uint256[] memory yieldFeeAmounts);
/**
* @notice Update an aggregate swap fee percentage.
* @dev Can only be called by the current protocol fee controller. Called when governance overrides a protocol fee
* for a specific pool, or to permissionlessly update a pool to a changed global protocol fee value (if the pool's
* fee has not previously been set by governance). Ensures the aggregate percentage <= FixedPoint.ONE, and also
* that the final value does not lose precision when stored in 24 bits (see `FEE_BITLENGTH` in VaultTypes.sol).
* Emits an `AggregateSwapFeePercentageChanged` event.
*
* @param pool The pool whose swap fee percentage will be updated
* @param newAggregateSwapFeePercentage The new aggregate swap fee percentage
*/
function updateAggregateSwapFeePercentage(address pool, uint256 newAggregateSwapFeePercentage) external;
/**
* @notice Update an aggregate yield fee percentage.
* @dev Can only be called by the current protocol fee controller. Called when governance overrides a protocol fee
* for a specific pool, or to permissionlessly update a pool to a changed global protocol fee value (if the pool's
* fee has not previously been set by governance). Ensures the aggregate percentage <= FixedPoint.ONE, and also
* that the final value does not lose precision when stored in 24 bits (see `FEE_BITLENGTH` in VaultTypes.sol).
* Emits an `AggregateYieldFeePercentageChanged` event.
*
* @param pool The pool whose yield fee percentage will be updated
* @param newAggregateYieldFeePercentage The new aggregate yield fee percentage
*/
function updateAggregateYieldFeePercentage(address pool, uint256 newAggregateYieldFeePercentage) external;
/**
* @notice Sets a new Protocol Fee Controller for the Vault.
* @dev This is a permissioned call. Emits a `ProtocolFeeControllerChanged` event.
* @param newProtocolFeeController The address of the new Protocol Fee Controller
*/
function setProtocolFeeController(IProtocolFeeController newProtocolFeeController) external;
/*******************************************************************************
Recovery Mode
*******************************************************************************/
/**
* @notice Enable recovery mode for a pool.
* @dev This is a permissioned function. It enables a safe proportional withdrawal, with no external calls.
* Since there are no external calls, ensuring that entering Recovery Mode cannot fail, we cannot compute and so
* must forfeit any yield fees between the last operation and enabling Recovery Mode. For the same reason, live
* balances cannot be updated while in Recovery Mode, as doing so might cause withdrawals to fail.
*
* @param pool The address of the pool
*/
function enableRecoveryMode(address pool) external;
/**
* @notice Disable recovery mode for a pool.
* @dev This is a permissioned function. It re-syncs live balances (which could not be updated during
* Recovery Mode), forfeiting any yield fees that accrued while enabled. It makes external calls, and could
* potentially fail if there is an issue with any associated Rate Providers.
*
* @param pool The address of the pool
*/
function disableRecoveryMode(address pool) external;
/*******************************************************************************
Query Functionality
*******************************************************************************/
/**
* @notice Disables query functionality on the Vault. Can only be called by governance.
* @dev The query functions rely on a specific EVM feature to detect static calls. Query operations are exempt from
* settlement constraints, so it's critical that no state changes can occur. We retain the ability to disable
* queries in the unlikely event that EVM changes violate its assumptions (perhaps on an L2).
* This function can be acted upon as an emergency measure in ambiguous contexts where it's not 100% clear whether
* disabling queries is completely necessary; queries can still be re-enabled after this call.
*/
function disableQuery() external;
/**
* @notice Disables query functionality permanently on the Vault. Can only be called by governance.
* @dev Shall only be used when there is no doubt that queries pose a fundamental threat to the system.
*/
function disableQueryPermanently() external;
/**
* @notice Enables query functionality on the Vault. Can only be called by governance.
* @dev Only works if queries are not permanently disabled.
*/
function enableQuery() external;
/*******************************************************************************
ERC4626 Buffers
*******************************************************************************/
/**
* @notice Indicates whether the Vault buffers are paused.
* @dev When buffers are paused, all buffer operations (i.e., calls on the Router with `isBuffer` true)
* will revert. Pausing buffers is reversible. Note that ERC4626 buffers and the Vault have separate and
* independent pausing mechanisms. Pausing the Vault does not also pause buffers (though we anticipate they
* would likely be paused and unpaused together). Call `isVaultPaused` to check the pause state of the Vault.
*
* @return buffersPaused True if the Vault buffers are paused
*/
function areBuffersPaused() external view returns (bool buffersPaused);
/**
* @notice Pauses native vault buffers globally.
* @dev When buffers are paused, it's not possible to add liquidity or wrap/unwrap tokens using the Vault's
* `erc4626BufferWrapOrUnwrap` primitive. However, it's still possible to remove liquidity. Currently it's not
* possible to pause vault buffers individually.
*
* This is a permissioned call, and is reversible (see `unpauseVaultBuffers`). Note that the Vault has a separate
* and independent pausing mechanism. It is possible to pause the Vault (i.e. pool operations), without affecting
* buffers, and vice versa.
*/
function pauseVaultBuffers() external;
/**
* @notice Unpauses native vault buffers globally.
* @dev When buffers are paused, it's not possible to add liquidity or wrap/unwrap tokens using the Vault's
* `erc4626BufferWrapOrUnwrap` primitive. However, it's still possible to remove liquidity. As noted above,
* ERC4626 buffers and Vault operations on pools are independent. Unpausing buffers does not reverse `pauseVault`.
* If the Vault was also paused, it will remain in that state until explicitly unpaused.
*
* This is a permissioned call.
*/
function unpauseVaultBuffers() external;
/**
* @notice Initializes buffer for the given wrapped token.
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @param amountUnderlyingRaw Amount of underlying tokens that will be deposited into the buffer
* @param amountWrappedRaw Amount of wrapped tokens that will be deposited into the buffer
* @param minIssuedShares Minimum amount of shares to receive from the buffer, expressed in underlying token
* native decimals
* @param sharesOwner Address that will own the deposited liquidity. Only this address will be able to remove
* liquidity from the buffer
* @return issuedShares the amount of tokens sharesOwner has in the buffer, expressed in underlying token amounts.
* (it is the BPT of an internal ERC4626 buffer). It is expressed in underlying token native decimals.
*/
function initializeBuffer(
IERC4626 wrappedToken,
uint256 amountUnderlyingRaw,
uint256 amountWrappedRaw,
uint256 minIssuedShares,
address sharesOwner
) external returns (uint256 issuedShares);
/**
* @notice Adds liquidity to an internal ERC4626 buffer in the Vault, proportionally.
* @dev The buffer needs to be initialized beforehand.
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @param maxAmountUnderlyingInRaw Maximum amount of underlying tokens to add to the buffer. It is expressed in
* underlying token native decimals
* @param maxAmountWrappedInRaw Maximum amount of wrapped tokens to add to the buffer. It is expressed in wrapped
* token native decimals
* @param exactSharesToIssue The value in underlying tokens that `sharesOwner` wants to add to the buffer,
* in underlying token decimals
* @param sharesOwner Address that will own the deposited liquidity. Only this address will be able to remove
* liquidity from the buffer
* @return amountUnderlyingRaw Amount of underlying tokens deposited into the buffer
* @return amountWrappedRaw Amount of wrapped tokens deposited into the buffer
*/
function addLiquidityToBuffer(
IERC4626 wrappedToken,
uint256 maxAmountUnderlyingInRaw,
uint256 maxAmountWrappedInRaw,
uint256 exactSharesToIssue,
address sharesOwner
) external returns (uint256 amountUnderlyingRaw, uint256 amountWrappedRaw);
/**
* @notice Removes liquidity from an internal ERC4626 buffer in the Vault.
* @dev Only proportional exits are supported, and the sender has to be the owner of the shares.
* This function unlocks the Vault just for this operation; it does not work with a Router as an entrypoint.
*
* Pre-conditions:
* - The buffer needs to be initialized.
* - sharesOwner is the original msg.sender, it needs to be checked in the Router. That's why
* this call is authenticated; only routers approved by the DAO can remove the liquidity of a buffer.
* - The buffer needs to have some liquidity and have its asset registered in `_bufferAssets` storage.
*
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @param sharesToRemove Amount of shares to remove from the buffer. Cannot be greater than sharesOwner's
* total shares. It is expressed in underlying token native decimals
* @param minAmountUnderlyingOutRaw Minimum amount of underlying tokens to receive from the buffer. It is expressed
* in underlying token native decimals
* @param minAmountWrappedOutRaw Minimum amount of wrapped tokens to receive from the buffer. It is expressed in
* wrapped token native decimals
* @return removedUnderlyingBalanceRaw Amount of underlying tokens returned to the user
* @return removedWrappedBalanceRaw Amount of wrapped tokens returned to the user
*/
function removeLiquidityFromBuffer(
IERC4626 wrappedToken,
uint256 sharesToRemove,
uint256 minAmountUnderlyingOutRaw,
uint256 minAmountWrappedOutRaw
) external returns (uint256 removedUnderlyingBalanceRaw, uint256 removedWrappedBalanceRaw);
/**
* @notice Returns the asset registered for a given wrapped token.
* @dev The asset can never change after buffer initialization.
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @return underlyingToken Address of the underlying token registered for the wrapper; `address(0)` if the buffer
* has not been initialized.
*/
function getBufferAsset(IERC4626 wrappedToken) external view returns (address underlyingToken);
/**
* @notice Returns the shares (internal buffer BPT) of a liquidity owner: a user that deposited assets
* in the buffer.
*
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @param liquidityOwner Address of the user that owns liquidity in the wrapped token's buffer
* @return ownerShares Amount of shares allocated to the liquidity owner, in native underlying token decimals
*/
function getBufferOwnerShares(
IERC4626 wrappedToken,
address liquidityOwner
) external view returns (uint256 ownerShares);
/**
* @notice Returns the supply shares (internal buffer BPT) of the ERC4626 buffer.
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @return bufferShares Amount of supply shares of the buffer, in native underlying token decimals
*/
function getBufferTotalShares(IERC4626 wrappedToken) external view returns (uint256 bufferShares);
/**
* @notice Returns the amount of underlying and wrapped tokens deposited in the internal buffer of the Vault.
* @dev All values are in native token decimals of the wrapped or underlying tokens.
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @return underlyingBalanceRaw Amount of underlying tokens deposited into the buffer, in native token decimals
* @return wrappedBalanceRaw Amount of wrapped tokens deposited into the buffer, in native token decimals
*/
function getBufferBalance(
IERC4626 wrappedToken
) external view returns (uint256 underlyingBalanceRaw, uint256 wrappedBalanceRaw);
/*******************************************************************************
Authentication
*******************************************************************************/
/**
* @notice Sets a new Authorizer for the Vault.
* @dev This is a permissioned call. Emits an `AuthorizerChanged` event.
* @param newAuthorizer The address of the new authorizer
*/
function setAuthorizer(IAuthorizer newAuthorizer) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @notice Errors are declared inside an interface (namespace) to improve DX with Typechain.
interface IVaultErrors {
/*******************************************************************************
Registration and Initialization
*******************************************************************************/
/**
* @notice A pool has already been registered. `registerPool` may only be called once.
* @param pool The already registered pool
*/
error PoolAlreadyRegistered(address pool);
/**
* @notice A pool has already been initialized. `initialize` may only be called once.
* @param pool The already initialized pool
*/
error PoolAlreadyInitialized(address pool);
/**
* @notice A pool has not been registered.
* @param pool The unregistered pool
*/
error PoolNotRegistered(address pool);
/**
* @notice A referenced pool has not been initialized.
* @param pool The uninitialized pool
*/
error PoolNotInitialized(address pool);
/**
* @notice A hook contract rejected a pool on registration.
* @param poolHooksContract Address of the hook contract that rejected the pool registration
* @param pool Address of the rejected pool
* @param poolFactory Address of the pool factory
*/
error HookRegistrationFailed(address poolHooksContract, address pool, address poolFactory);
/**
* @notice A token was already registered (i.e., it is a duplicate in the pool).
* @param token The duplicate token
*/
error TokenAlreadyRegistered(IERC20 token);
/// @notice The token count is below the minimum allowed.
error MinTokens();
/// @notice The token count is above the maximum allowed.
error MaxTokens();
/// @notice Invalid tokens (e.g., zero) cannot be registered.
error InvalidToken();
/// @notice The token type given in a TokenConfig during pool registration is invalid.
error InvalidTokenType();
/// @notice The data in a TokenConfig struct is inconsistent or unsupported.
error InvalidTokenConfiguration();
/// @notice Tokens with more than 18 decimals are not supported.
error InvalidTokenDecimals();
/**
* @notice The token list passed into an operation does not match the pool tokens in the pool.
* @param pool Address of the pool
* @param expectedToken The correct token at a given index in the pool
* @param actualToken The actual token found at that index
*/
error TokensMismatch(address pool, address expectedToken, address actualToken);
/*******************************************************************************
Transient Accounting
*******************************************************************************/
/// @notice A transient accounting operation completed with outstanding token deltas.
error BalanceNotSettled();
/// @notice A user called a Vault function (swap, add/remove liquidity) outside the lock context.
error VaultIsNotUnlocked();
/// @notice The pool has returned false to the beforeSwap hook, indicating the transaction should revert.
error DynamicSwapFeeHookFailed();
/// @notice The pool has returned false to the beforeSwap hook, indicating the transaction should revert.
error BeforeSwapHookFailed();
/// @notice The pool has returned false to the afterSwap hook, indicating the transaction should revert.
error AfterSwapHookFailed();
/// @notice The pool has returned false to the beforeInitialize hook, indicating the transaction should revert.
error BeforeInitializeHookFailed();
/// @notice The pool has returned false to the afterInitialize hook, indicating the transaction should revert.
error AfterInitializeHookFailed();
/// @notice The pool has returned false to the beforeAddLiquidity hook, indicating the transaction should revert.
error BeforeAddLiquidityHookFailed();
/// @notice The pool has returned false to the afterAddLiquidity hook, indicating the transaction should revert.
error AfterAddLiquidityHookFailed();
/// @notice The pool has returned false to the beforeRemoveLiquidity hook, indicating the transaction should revert.
error BeforeRemoveLiquidityHookFailed();
/// @notice The pool has returned false to the afterRemoveLiquidity hook, indicating the transaction should revert.
error AfterRemoveLiquidityHookFailed();
/// @notice An unauthorized Router tried to call a permissioned function (i.e., using the Vault's token allowance).
error RouterNotTrusted();
/*******************************************************************************
Swaps
*******************************************************************************/
/// @notice The user tried to swap zero tokens.
error AmountGivenZero();
/// @notice The user attempted to swap a token for itself.
error CannotSwapSameToken();
/**
* @notice The user attempted to operate with a token that is not in the pool.
* @param token The unregistered token
*/
error TokenNotRegistered(IERC20 token);
/**
* @notice An amount in or out has exceeded the limit specified in the swap request.
* @param amount The total amount in or out
* @param limit The amount of the limit that has been exceeded
*/
error SwapLimit(uint256 amount, uint256 limit);
/**
* @notice A hook adjusted amount in or out has exceeded the limit specified in the swap request.
* @param amount The total amount in or out
* @param limit The amount of the limit that has been exceeded
*/
error HookAdjustedSwapLimit(uint256 amount, uint256 limit);
/// @notice The amount given or calculated for an operation is below the minimum limit.
error TradeAmountTooSmall();
/*******************************************************************************
Add Liquidity
*******************************************************************************/
/// @notice Add liquidity kind not supported.
error InvalidAddLiquidityKind();
/**
* @notice A required amountIn exceeds the maximum limit specified for the operation.
* @param tokenIn The incoming token
* @param amountIn The total token amount in
* @param maxAmountIn The amount of the limit that has been exceeded
*/
error AmountInAboveMax(IERC20 tokenIn, uint256 amountIn, uint256 maxAmountIn);
/**
* @notice A hook adjusted amountIn exceeds the maximum limit specified for the operation.
* @param tokenIn The incoming token
* @param amountIn The total token amount in
* @param maxAmountIn The amount of the limit that has been exceeded
*/
error HookAdjustedAmountInAboveMax(IERC20 tokenIn, uint256 amountIn, uint256 maxAmountIn);
/**
* @notice The BPT amount received from adding liquidity is below the minimum specified for the operation.
* @param amountOut The total BPT amount out
* @param minAmountOut The amount of the limit that has been exceeded
*/
error BptAmountOutBelowMin(uint256 amountOut, uint256 minAmountOut);
/// @notice Pool does not support adding liquidity with a customized input.
error DoesNotSupportAddLiquidityCustom();
/// @notice Pool does not support adding liquidity through donation.
error DoesNotSupportDonation();
/*******************************************************************************
Remove Liquidity
*******************************************************************************/
/// @notice Remove liquidity kind not supported.
error InvalidRemoveLiquidityKind();
/**
* @notice The actual amount out is below the minimum limit specified for the operation.
* @param tokenOut The outgoing token
* @param amountOut The total BPT amount out
* @param minAmountOut The amount of the limit that has been exceeded
*/
error AmountOutBelowMin(IERC20 tokenOut, uint256 amountOut, uint256 minAmountOut);
/**
* @notice The hook adjusted amount out is below the minimum limit specified for the operation.
* @param tokenOut The outgoing token
* @param amountOut The total BPT amount out
* @param minAmountOut The amount of the limit that has been exceeded
*/
error HookAdjustedAmountOutBelowMin(IERC20 tokenOut, uint256 amountOut, uint256 minAmountOut);
/**
* @notice The required BPT amount in exceeds the maximum limit specified for the operation.
* @param amountIn The total BPT amount in
* @param maxAmountIn The amount of the limit that has been exceeded
*/
error BptAmountInAboveMax(uint256 amountIn, uint256 maxAmountIn);
/// @notice Pool does not support removing liquidity with a customized input.
error DoesNotSupportRemoveLiquidityCustom();
/*******************************************************************************
Fees
*******************************************************************************/
/**
* @notice Error raised when there is an overflow in the fee calculation.
* @dev This occurs when the sum of the parts (aggregate swap or yield fee) is greater than the whole
* (total swap or yield fee). Also validated when the protocol fee controller updates aggregate fee
* percentages in the Vault.
*/
error ProtocolFeesExceedTotalCollected();
/**
* @notice Error raised when the swap fee percentage is less than the minimum allowed value.
* @dev The Vault itself does not impose a universal minimum. Rather, it validates against the
* range specified by the `ISwapFeePercentageBounds` interface. and reverts with this error
* if it is below the minimum value returned by the pool.
*
* Pools with dynamic fees do not check these limits.
*/
error SwapFeePercentageTooLow();
/**
* @notice Error raised when the swap fee percentage is greater than the maximum allowed value.
* @dev The Vault itself does not impose a universal minimum. Rather, it validates against the
* range specified by the `ISwapFeePercentageBounds` interface. and reverts with this error
* if it is above the maximum value returned by the pool.
*
* Pools with dynamic fees do not check these limits.
*/
error SwapFeePercentageTooHigh();
/**
* @notice Primary fee percentages result in an aggregate fee that cannot be stored with the required precision.
* @dev Primary fee percentages are 18-decimal values, stored here in 64 bits, and calculated with full 256-bit
* precision. However, the resulting aggregate fees are stored in the Vault with 24-bit precision, which
* corresponds to 0.00001% resolution (i.e., a fee can be 1%, 1.00001%, 1.00002%, but not 1.000005%).
* Disallow setting fees such that there would be precision loss in the Vault, leading to a discrepancy between
* the aggregate fee calculated here and that stored in the Vault.
*/
error FeePrecisionTooHigh();
/// @notice A given percentage is above the maximum (usually a value close to FixedPoint.ONE, or 1e18 wei).
error PercentageAboveMax();
/*******************************************************************************
Queries
*******************************************************************************/
/// @notice A user tried to execute a query operation when they were disabled.
error QueriesDisabled();
/// @notice An admin tried to re-enable queries, but they were disabled permanently.
error QueriesDisabledPermanently();
/*******************************************************************************
Recovery Mode
*******************************************************************************/
/**
* @notice Cannot enable recovery mode when already enabled.
* @param pool The pool
*/
error PoolInRecoveryMode(address pool);
/**
* @notice Cannot disable recovery mode when not enabled.
* @param pool The pool
*/
error PoolNotInRecoveryMode(address pool);
/*******************************************************************************
Authentication
*******************************************************************************/
/**
* @notice Error indicating the sender is not the Vault (e.g., someone is trying to call a permissioned function).
* @param sender The account attempting to call a permissioned function
*/
error SenderIsNotVault(address sender);
/*******************************************************************************
Pausing
*******************************************************************************/
/// @notice The caller specified a pause window period longer than the maximum.
error VaultPauseWindowDurationTooLarge();
/// @notice The caller specified a buffer period longer than the maximum.
error PauseBufferPeriodDurationTooLarge();
/// @notice A user tried to perform an operation while the Vault was paused.
error VaultPaused();
/// @notice Governance tried to unpause the Vault when it was not paused.
error VaultNotPaused();
/// @notice Governance tried to pause the Vault after the pause period expired.
error VaultPauseWindowExpired();
/**
* @notice A user tried to perform an operation involving a paused Pool.
* @param pool The paused pool
*/
error PoolPaused(address pool);
/**
* @notice Governance tried to unpause the Pool when it was not paused.
* @param pool The unpaused pool
*/
error PoolNotPaused(address pool);
/**
* @notice Governance tried to pause a Pool after the pause period expired.
* @param pool The pool
*/
error PoolPauseWindowExpired(address pool);
/*******************************************************************************
ERC4626 token buffers
*******************************************************************************/
/**
* @notice The buffer for the given wrapped token was already initialized.
* @param wrappedToken The wrapped token corresponding to the buffer
*/
error BufferAlreadyInitialized(IERC4626 wrappedToken);
/**
* @notice The buffer for the given wrapped token was not initialized.
* @param wrappedToken The wrapped token corresponding to the buffer
*/
error BufferNotInitialized(IERC4626 wrappedToken);
/// @notice The user is trying to remove more than their allocated shares from the buffer.
error NotEnoughBufferShares();
/**
* @notice The wrapped token asset does not match the underlying token.
* @dev This should never happen, but a malicious wrapper contract might not return the correct address.
* Legitimate wrapper contracts should make the asset a constant or immutable value.
*
* @param wrappedToken The wrapped token corresponding to the buffer
* @param underlyingToken The underlying token returned by `asset`
*/
error WrongUnderlyingToken(IERC4626 wrappedToken, address underlyingToken);
/**
* @notice A wrapped token reported the zero address as its underlying token asset.
* @dev This should never happen, but a malicious wrapper contract might do this (e.g., in an attempt to
* re-initialize the buffer).
*
* @param wrappedToken The wrapped token corresponding to the buffer
*/
error InvalidUnderlyingToken(IERC4626 wrappedToken);
/**
* @notice The amount given to wrap/unwrap was too small, which can introduce rounding issues.
* @param wrappedToken The wrapped token corresponding to the buffer
*/
error WrapAmountTooSmall(IERC4626 wrappedToken);
/// @notice Buffer operation attempted while vault buffers are paused.
error VaultBuffersArePaused();
/// @notice Buffer shares were minted to the zero address.
error BufferSharesInvalidReceiver();
/// @notice Buffer shares were burned from the zero address.
error BufferSharesInvalidOwner();
/**
* @notice The total supply of a buffer can't be lower than the absolute minimum.
* @param totalSupply The total supply value that was below the minimum
*/
error BufferTotalSupplyTooLow(uint256 totalSupply);
/// @dev A wrap/unwrap operation consumed more or returned less underlying tokens than it should.
error NotEnoughUnderlying(IERC4626 wrappedToken, uint256 expectedUnderlyingAmount, uint256 actualUnderlyingAmount);
/// @dev A wrap/unwrap operation consumed more or returned less wrapped tokens than it should.
error NotEnoughWrapped(IERC4626 wrappedToken, uint256 expectedWrappedAmount, uint256 actualWrappedAmount);
/// @dev Shares issued during initialization are below the requested amount.
error IssuedSharesBelowMin(uint256 issuedShares, uint256 minIssuedShares);
/*******************************************************************************
Miscellaneous
*******************************************************************************/
/// @notice Pool does not support adding / removing liquidity with an unbalanced input.
error DoesNotSupportUnbalancedLiquidity();
/// @notice The contract should not receive ETH.
error CannotReceiveEth();
/**
* @notice The `VaultExtension` contract was called by an account directly.
* @dev It can only be called by the Vault via delegatecall.
*/
error NotVaultDelegateCall();
/// @notice The `VaultExtension` contract was configured with an incorrect Vault address.
error WrongVaultExtensionDeployment();
/// @notice The `ProtocolFeeController` contract was configured with an incorrect Vault address.
error WrongProtocolFeeControllerDeployment();
/// @notice The `VaultAdmin` contract was configured with an incorrect Vault address.
error WrongVaultAdminDeployment();
/// @notice Quote reverted with a reserved error code.
error QuoteResultSpoofed();
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IProtocolFeeController } from "./IProtocolFeeController.sol";
import { IAuthorizer } from "./IAuthorizer.sol";
import { IHooks } from "./IHooks.sol";
import "./VaultTypes.sol";
/// @dev Events are declared inside an interface (namespace) to improve DX with Typechain.
interface IVaultEvents {
/**
* @notice A Pool was registered by calling `registerPool`.
* @param pool The pool being registered
* @param factory The factory creating the pool
* @param tokenConfig An array of descriptors for the tokens the pool will manage
* @param swapFeePercentage The static swap fee of the pool
* @param pauseWindowEndTime The pool's pause window end time
* @param roleAccounts Addresses the Vault will allow to change certain pool settings
* @param hooksConfig Flags indicating which hooks the pool supports and address of hooks contract
* @param liquidityManagement Supported liquidity management hook flags
*/
event PoolRegistered(
address indexed pool,
address indexed factory,
TokenConfig[] tokenConfig,
uint256 swapFeePercentage,
uint32 pauseWindowEndTime,
PoolRoleAccounts roleAccounts,
HooksConfig hooksConfig,
LiquidityManagement liquidityManagement
);
/**
* @notice A Pool was initialized by calling `initialize`.
* @param pool The pool being initialized
*/
event PoolInitialized(address indexed pool);
/**
* @notice A swap has occurred.
* @param pool The pool with the tokens being swapped
* @param tokenIn The token entering the Vault (balance increases)
* @param tokenOut The token leaving the Vault (balance decreases)
* @param amountIn Number of tokenIn tokens
* @param amountOut Number of tokenOut tokens
* @param swapFeePercentage Swap fee percentage applied (can differ if dynamic)
* @param swapFeeAmount Swap fee amount paid
*/
event Swap(
address indexed pool,
IERC20 indexed tokenIn,
IERC20 indexed tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 swapFeePercentage,
uint256 swapFeeAmount
);
/**
* @notice A wrap operation has occurred.
* @param wrappedToken The wrapped token address
* @param depositedUnderlying Number of underlying tokens deposited
* @param mintedShares Number of shares (wrapped tokens) minted
* @param bufferBalances The final buffer balances, packed in 128-bit words (underlying, wrapped)
*/
event Wrap(
IERC4626 indexed wrappedToken,
uint256 depositedUnderlying,
uint256 mintedShares,
bytes32 bufferBalances
);
/**
* @notice An unwrap operation has occurred.
* @param wrappedToken The wrapped token address
* @param burnedShares Number of shares (wrapped tokens) burned
* @param withdrawnUnderlying Number of underlying tokens withdrawn
* @param bufferBalances The final buffer balances, packed in 128-bit words (underlying, wrapped)
*/
event Unwrap(
IERC4626 indexed wrappedToken,
uint256 burnedShares,
uint256 withdrawnUnderlying,
bytes32 bufferBalances
);
/**
* @notice Liquidity has been added to a pool (including initialization).
* @param pool The pool with liquidity added
* @param liquidityProvider The user performing the operation
* @param kind The add liquidity operation type (e.g., proportional, custom)
* @param totalSupply The total supply of the pool after the operation
* @param amountsAddedRaw The amount of each token that was added, sorted in token registration order
* @param swapFeeAmountsRaw The total swap fees charged, sorted in token registration order
*/
event LiquidityAdded(
address indexed pool,
address indexed liquidityProvider,
AddLiquidityKind indexed kind,
uint256 totalSupply,
uint256[] amountsAddedRaw,
uint256[] swapFeeAmountsRaw
);
/**
* @notice Liquidity has been removed from a pool.
* @param pool The pool with liquidity removed
* @param liquidityProvider The user performing the operation
* @param kind The remove liquidity operation type (e.g., proportional, custom)
* @param totalSupply The total supply of the pool after the operation
* @param amountsRemovedRaw The amount of each token that was removed, sorted in token registration order
* @param swapFeeAmountsRaw The total swap fees charged, sorted in token registration order
*/
event LiquidityRemoved(
address indexed pool,
address indexed liquidityProvider,
RemoveLiquidityKind indexed kind,
uint256 totalSupply,
uint256[] amountsRemovedRaw,
uint256[] swapFeeAmountsRaw
);
/**
* @notice The Vault's pause status has changed.
* @param paused True if the Vault was paused
*/
event VaultPausedStateChanged(bool paused);
/// @notice `disableQuery` has been called on the Vault, disabling query functionality.
event VaultQueriesDisabled();
/// @notice `enableQuery` has been called on the Vault, enabling query functionality.
event VaultQueriesEnabled();
/**
* @notice A Pool's pause status has changed.
* @param pool The pool that was just paused or unpaused
* @param paused True if the pool was paused
*/
event PoolPausedStateChanged(address indexed pool, bool paused);
/**
* @notice Emitted when the swap fee percentage of a pool is updated.
* @param swapFeePercentage The new swap fee percentage for the pool
*/
event SwapFeePercentageChanged(address indexed pool, uint256 swapFeePercentage);
/**
* @notice Recovery mode has been enabled or disabled for a pool.
* @param pool The pool
* @param recoveryMode True if recovery mode was enabled
*/
event PoolRecoveryModeStateChanged(address indexed pool, bool recoveryMode);
/**
* @notice A protocol or pool creator fee has changed, causing an update to the aggregate swap fee.
* @dev The `ProtocolFeeController` will emit an event with the underlying change.
* @param pool The pool whose aggregate swap fee percentage changed
* @param aggregateSwapFeePercentage The new aggregate swap fee percentage
*/
event AggregateSwapFeePercentageChanged(address indexed pool, uint256 aggregateSwapFeePercentage);
/**
* @notice A protocol or pool creator fee has changed, causing an update to the aggregate yield fee.
* @dev The `ProtocolFeeController` will emit an event with the underlying change.
* @param pool The pool whose aggregate yield fee percentage changed
* @param aggregateYieldFeePercentage The new aggregate yield fee percentage
*/
event AggregateYieldFeePercentageChanged(address indexed pool, uint256 aggregateYieldFeePercentage);
/**
* @notice A new authorizer is set by `setAuthorizer`.
* @param newAuthorizer The address of the new authorizer
*/
event AuthorizerChanged(IAuthorizer indexed newAuthorizer);
/**
* @notice A new protocol fee controller is set by `setProtocolFeeController`.
* @param newProtocolFeeController The address of the new protocol fee controller
*/
event ProtocolFeeControllerChanged(IProtocolFeeController indexed newProtocolFeeController);
/**
* @notice Liquidity was added to an ERC4626 buffer corresponding to the given wrapped token.
* @dev The underlying token can be derived from the wrapped token, so it's not included here.
*
* @param wrappedToken The wrapped token that identifies the buffer
* @param amountUnderlying The amount of the underlying token that was deposited
* @param amountWrapped The amount of the wrapped token that was deposited
* @param bufferBalances The final buffer balances, packed in 128-bit words (underlying, wrapped)
*/
event LiquidityAddedToBuffer(
IERC4626 indexed wrappedToken,
uint256 amountUnderlying,
uint256 amountWrapped,
bytes32 bufferBalances
);
/**
* @notice Buffer shares were minted for an ERC4626 buffer corresponding to a given wrapped token.
* @dev The shares are not tokenized like pool BPT, but accounted for in the Vault. `getBufferOwnerShares`
* retrieves the current total shares for a given buffer and address, and `getBufferTotalShares` returns the
* "totalSupply" of a buffer.
*
* @param wrappedToken The wrapped token that identifies the buffer
* @param to The owner of the minted shares
* @param issuedShares The amount of "internal BPT" shares created
*/
event BufferSharesMinted(IERC4626 indexed wrappedToken, address indexed to, uint256 issuedShares);
/**
* @notice Buffer shares were burned for an ERC4626 buffer corresponding to a given wrapped token.
* @dev The shares are not tokenized like pool BPT, but accounted for in the Vault. `getBufferOwnerShares`
* retrieves the current total shares for a given buffer and address, and `getBufferTotalShares` returns the
* "totalSupply" of a buffer.
*
* @param wrappedToken The wrapped token that identifies the buffer
* @param from The owner of the burned shares
* @param burnedShares The amount of "internal BPT" shares burned
*/
event BufferSharesBurned(IERC4626 indexed wrappedToken, address indexed from, uint256 burnedShares);
/**
* @notice Liquidity was removed from an ERC4626 buffer.
* @dev The underlying token can be derived from the wrapped token, so it's not included here.
* @param wrappedToken The wrapped token that identifies the buffer
* @param amountUnderlying The amount of the underlying token that was withdrawn
* @param amountWrapped The amount of the wrapped token that was withdrawn
* @param bufferBalances The final buffer balances, packed in 128-bit words (underlying, wrapped)
*/
event LiquidityRemovedFromBuffer(
IERC4626 indexed wrappedToken,
uint256 amountUnderlying,
uint256 amountWrapped,
bytes32 bufferBalances
);
/**
* @notice The Vault buffers pause status has changed.
* @dev If buffers all paused, all buffer operations (i.e., all calls through the Router with `isBuffer`
* set to true) will revert.
*
* @param paused True if the Vault buffers were paused
*/
event VaultBuffersPausedStateChanged(bool paused);
/**
* @notice Pools can use this event to emit event data from the Vault.
* @param pool Pool address
* @param eventKey Event key
* @param eventData Encoded event data
*/
event VaultAuxiliary(address indexed pool, bytes32 indexed eventKey, bytes eventData);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IAuthorizer } from "./IAuthorizer.sol";
import { IProtocolFeeController } from "./IProtocolFeeController.sol";
import { IVault } from "./IVault.sol";
import { IHooks } from "./IHooks.sol";
import "./VaultTypes.sol";
/**
* @notice Interface for functions defined on the `VaultExtension` contract.
* @dev `VaultExtension` handles less critical or frequently used functions, since delegate calls through
* the Vault are more expensive than direct calls. The main Vault contains the core code for swaps and
* liquidity operations.
*/
interface IVaultExtension {
/*******************************************************************************
Constants and immutables
*******************************************************************************/
/**
* @notice Returns the main Vault address.
* @dev The main Vault contains the entrypoint and main liquidity operation implementations.
* @return vault The address of the main Vault
*/
function vault() external view returns (IVault);
/**
* @notice Returns the VaultAdmin contract address.
* @dev The VaultAdmin contract mostly implements permissioned functions.
* @return vaultAdmin The address of the Vault admin
*/
function getVaultAdmin() external view returns (address vaultAdmin);
/*******************************************************************************
Transient Accounting
*******************************************************************************/
/**
* @notice Returns whether the Vault is unlocked (i.e., executing an operation).
* @dev The Vault must be unlocked to perform state-changing liquidity operations.
* @return unlocked True if the Vault is unlocked, false otherwise
*/
function isUnlocked() external view returns (bool unlocked);
/**
* @notice Returns the count of non-zero deltas.
* @return nonzeroDeltaCount The current value of `_nonzeroDeltaCount`
*/
function getNonzeroDeltaCount() external view returns (uint256 nonzeroDeltaCount);
/**
* @notice Retrieves the token delta for a specific token.
* @dev This function allows reading the value from the `_tokenDeltas` mapping.
* @param token The token for which the delta is being fetched
* @return tokenDelta The delta of the specified token
*/
function getTokenDelta(IERC20 token) external view returns (int256 tokenDelta);
/**
* @notice Retrieves the reserve (i.e., total Vault balance) of a given token.
* @param token The token for which to retrieve the reserve
* @return reserveAmount The amount of reserves for the given token
*/
function getReservesOf(IERC20 token) external view returns (uint256 reserveAmount);
/**
* @notice This flag is used to detect and tax "round-trip" interactions (adding and removing liquidity in the
* same pool).
* @dev Taxing remove liquidity proportional whenever liquidity was added in the same `unlock` call adds an extra
* layer of security, discouraging operations that try to undo others for profit. Remove liquidity proportional
* is the only standard way to exit a position without fees, and this flag is used to enable fees in that case.
* It also discourages indirect swaps via unbalanced add and remove proportional, as they are expected to be worse
* than a simple swap for every pool type.
*
* @param pool Address of the pool to check
* @return liquidityAdded True if liquidity has been added to this pool in the current transaction
* Note that there is no `sessionId` argument; it always returns the value for the current (i.e., latest) session.
*/
function getAddLiquidityCalledFlag(address pool) external view returns (bool liquidityAdded);
/*******************************************************************************
Pool Registration
*******************************************************************************/
/**
* @notice Registers a pool, associating it with its factory and the tokens it manages.
* @dev A pool can opt-out of pausing by providing a zero value for the pause window, or allow pausing indefinitely
* by providing a large value. (Pool pause windows are not limited by the Vault maximums.) The vault defines an
* additional buffer period during which a paused pool will stay paused. After the buffer period passes, a paused
* pool will automatically unpause. Balancer timestamps are 32 bits.
*
* A pool can opt out of Balancer governance pausing by providing a custom `pauseManager`. This might be a
* multi-sig contract or an arbitrary smart contract with its own access controls, that forwards calls to
* the Vault.
*
* If the zero address is provided for the `pauseManager`, permissions for pausing the pool will default to the
* authorizer.
*
* @param pool The address of the pool being registered
* @param tokenConfig An array of descriptors for the tokens the pool will manage
* @param swapFeePercentage The initial static swap fee percentage of the pool
* @param pauseWindowEndTime The timestamp after which it is no longer possible to pause the pool
* @param protocolFeeExempt If true, the pool's initial aggregate fees will be set to 0
* @param roleAccounts Addresses the Vault will allow to change certain pool settings
* @param poolHooksContract Contract that implements the hooks for the pool
* @param liquidityManagement Liquidity management flags with implemented methods
*/
function registerPool(
address pool,
TokenConfig[] memory tokenConfig,
uint256 swapFeePercentage,
uint32 pauseWindowEndTime,
bool protocolFeeExempt,
PoolRoleAccounts calldata roleAccounts,
address poolHooksContract,
LiquidityManagement calldata liquidityManagement
) external;
/**
* @notice Checks whether a pool is registered.
* @param pool Address of the pool to check
* @return registered True if the pool is registered, false otherwise
*/
function isPoolRegistered(address pool) external view returns (bool registered);
/**
* @notice Initializes a registered pool by adding liquidity; mints BPT tokens for the first time in exchange.
* @param pool Address of the pool to initialize
* @param to Address that will receive the output BPT
* @param tokens Tokens used to seed the pool (must match the registered tokens)
* @param exactAmountsIn Exact amounts of input tokens
* @param minBptAmountOut Minimum amount of output pool tokens
* @param userData Additional (optional) data required for adding initial liquidity
* @return bptAmountOut Output pool token amount
*/
function initialize(
address pool,
address to,
IERC20[] memory tokens,
uint256[] memory exactAmountsIn,
uint256 minBptAmountOut,
bytes memory userData
) external returns (uint256 bptAmountOut);
/*******************************************************************************
Pool Information
*******************************************************************************/
/**
* @notice Checks whether a pool is initialized.
* @dev An initialized pool can be considered registered as well.
* @param pool Address of the pool to check
* @return initialized True if the pool is initialized, false otherwise
*/
function isPoolInitialized(address pool) external view returns (bool initialized);
/**
* @notice Gets the tokens registered to a pool.
* @param pool Address of the pool
* @return tokens List of tokens in the pool
*/
function getPoolTokens(address pool) external view returns (IERC20[] memory tokens);
/**
* @notice Gets pool token rates.
* @dev This function performs external calls if tokens are yield-bearing. All returned arrays are in token
* registration order.
*
* @param pool Address of the pool
* @return decimalScalingFactors Conversion factor used to adjust for token decimals for uniform precision in
* calculations. FP(1) for 18-decimal tokens
* @return tokenRates 18-decimal FP values for rate tokens (e.g., yield-bearing), or FP(1) for standard tokens
*/
function getPoolTokenRates(
address pool
) external view returns (uint256[] memory decimalScalingFactors, uint256[] memory tokenRates);
/**
* @notice Returns comprehensive pool data for the given pool.
* @dev This contains the pool configuration (flags), tokens and token types, rates, scaling factors, and balances.
* @param pool The address of the pool
* @return poolData The `PoolData` result
*/
function getPoolData(address pool) external view returns (PoolData memory poolData);
/**
* @notice Gets the raw data for a pool: tokens, raw balances, scaling factors.
* @param pool Address of the pool
* @return tokens The pool tokens, sorted in registration order
* @return tokenInfo Token info structs (type, rate provider, yield flag), sorted in token registration order
* @return balancesRaw Current native decimal balances of the pool tokens, sorted in token registration order
* @return lastBalancesLiveScaled18 Last saved live balances, sorted in token registration order
*/
function getPoolTokenInfo(
address pool
)
external
view
returns (
IERC20[] memory tokens,
TokenInfo[] memory tokenInfo,
uint256[] memory balancesRaw,
uint256[] memory lastBalancesLiveScaled18
);
/**
* @notice Gets current live balances of a given pool (fixed-point, 18 decimals), corresponding to its tokens in
* registration order.
*
* @param pool Address of the pool
* @return balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates
*/
function getCurrentLiveBalances(address pool) external view returns (uint256[] memory balancesLiveScaled18);
/**
* @notice Gets the configuration parameters of a pool.
* @dev The `PoolConfig` contains liquidity management and other state flags, fee percentages, the pause window.
* @param pool Address of the pool
* @return poolConfig The pool configuration as a `PoolConfig` struct
*/
function getPoolConfig(address pool) external view returns (PoolConfig memory poolConfig);
/**
* @notice Gets the hooks configuration parameters of a pool.
* @dev The `HooksConfig` contains flags indicating which pool hooks are implemented.
* @param pool Address of the pool
* @return hooksConfig The hooks configuration as a `HooksConfig` struct
*/
function getHooksConfig(address pool) external view returns (HooksConfig memory hooksConfig);
/**
* @notice The current rate of a pool token (BPT) = invariant / totalSupply.
* @param pool Address of the pool
* @return rate BPT rate
*/
function getBptRate(address pool) external view returns (uint256 rate);
/*******************************************************************************
Balancer Pool Tokens
*******************************************************************************/
/**
* @notice Gets the total supply of a given ERC20 token.
* @param token The token address
* @return tokenTotalSupply Total supply of the token
*/
function totalSupply(address token) external view returns (uint256 tokenTotalSupply);
/**
* @notice Gets the balance of an account for a given ERC20 token.
* @param token Address of the token
* @param account Address of the account
* @return tokenBalance Token balance of the account
*/
function balanceOf(address token, address account) external view returns (uint256 tokenBalance);
/**
* @notice Gets the allowance of a spender for a given ERC20 token and owner.
* @param token Address of the token
* @param owner Address of the owner
* @param spender Address of the spender
* @return tokenAllowance Amount of tokens the spender is allowed to spend
*/
function allowance(address token, address owner, address spender) external view returns (uint256 tokenAllowance);
/**
* @notice Approves a spender to spend pool tokens on behalf of sender.
* @dev Notice that the pool token address is not included in the params. This function is exclusively called by
* the pool contract, so msg.sender is used as the token address.
*
* @param owner Address of the owner
* @param spender Address of the spender
* @param amount Amount of tokens to approve
* @return success True if successful, false otherwise
*/
function approve(address owner, address spender, uint256 amount) external returns (bool success);
/*******************************************************************************
Pool Pausing
*******************************************************************************/
/**
* @notice Indicates whether a pool is paused.
* @dev If a pool is paused, all non-Recovery Mode state-changing operations will revert.
* @param pool The pool to be checked
* @return poolPaused True if the pool is paused
*/
function isPoolPaused(address pool) external view returns (bool poolPaused);
/**
* @notice Returns the paused status, and end times of the Pool's pause window and buffer period.
* @dev Note that even when set to a paused state, the pool will automatically unpause at the end of
* the buffer period. Balancer timestamps are 32 bits.
*
* @param pool The pool whose data is requested
* @return poolPaused True if the Pool is paused
* @return poolPauseWindowEndTime The timestamp of the end of the Pool's pause window
* @return poolBufferPeriodEndTime The timestamp after which the Pool unpauses itself (if paused)
* @return pauseManager The pause manager, or the zero address
*/
function getPoolPausedState(
address pool
)
external
view
returns (bool poolPaused, uint32 poolPauseWindowEndTime, uint32 poolBufferPeriodEndTime, address pauseManager);
/*******************************************************************************
ERC4626 Buffers
*******************************************************************************/
/**
* @notice Checks if the wrapped token has an initialized buffer in the Vault.
* @dev An initialized buffer should have an asset registered in the Vault.
* @param wrappedToken Address of the wrapped token that implements IERC4626
* @return isBufferInitialized True if the ERC4626 buffer is initialized
*/
function isERC4626BufferInitialized(IERC4626 wrappedToken) external view returns (bool isBufferInitialized);
/**
* @notice Gets the registered asset for a given buffer.
* @dev To avoid malicious wrappers (e.g., that might potentially change their asset after deployment), routers
* should never call `wrapper.asset()` directly, at least without checking it against the asset registered with
* the Vault on initialization.
*
* @param wrappedToken The wrapped token specifying the buffer
* @return asset The underlying asset of the wrapped token
*/
function getERC4626BufferAsset(IERC4626 wrappedToken) external view returns (address asset);
/*******************************************************************************
Fees
*******************************************************************************/
/**
* @notice Returns the accumulated swap fees (including aggregate fees) in `token` collected by the pool.
* @param pool The address of the pool for which aggregate fees have been collected
* @param token The address of the token in which fees have been accumulated
* @return swapFeeAmount The total amount of fees accumulated in the specified token
*/
function getAggregateSwapFeeAmount(address pool, IERC20 token) external view returns (uint256 swapFeeAmount);
/**
* @notice Returns the accumulated yield fees (including aggregate fees) in `token` collected by the pool.
* @param pool The address of the pool for which aggregate fees have been collected
* @param token The address of the token in which fees have been accumulated
* @return yieldFeeAmount The total amount of fees accumulated in the specified token
*/
function getAggregateYieldFeeAmount(address pool, IERC20 token) external view returns (uint256 yieldFeeAmount);
/**
* @notice Fetches the static swap fee percentage for a given pool.
* @param pool The address of the pool whose static swap fee percentage is being queried
* @return swapFeePercentage The current static swap fee percentage for the specified pool
*/
function getStaticSwapFeePercentage(address pool) external view returns (uint256 swapFeePercentage);
/**
* @notice Fetches the role accounts for a given pool (pause manager, swap manager, pool creator)
* @param pool The address of the pool whose roles are being queried
* @return roleAccounts A struct containing the role accounts for the pool (or 0 if unassigned)
*/
function getPoolRoleAccounts(address pool) external view returns (PoolRoleAccounts memory roleAccounts);
/**
* @notice Query the current dynamic swap fee percentage of a pool, given a set of swap parameters.
* @dev Reverts if the hook doesn't return the success flag set to `true`.
* @param pool The pool
* @param swapParams The swap parameters used to compute the fee
* @return dynamicSwapFeePercentage The dynamic swap fee percentage
*/
function computeDynamicSwapFeePercentage(
address pool,
PoolSwapParams memory swapParams
) external view returns (uint256 dynamicSwapFeePercentage);
/**
* @notice Returns the Protocol Fee Controller address.
* @return protocolFeeController Address of the ProtocolFeeController
*/
function getProtocolFeeController() external view returns (IProtocolFeeController protocolFeeController);
/*******************************************************************************
Recovery Mode
*******************************************************************************/
/**
* @notice Checks whether a pool is in Recovery Mode.
* @dev Recovery Mode enables a safe proportional withdrawal path, with no external calls.
* @param pool Address of the pool to check
* @return inRecoveryMode True if the pool is in Recovery Mode, false otherwise
*/
function isPoolInRecoveryMode(address pool) external view returns (bool inRecoveryMode);
/**
* @notice Remove liquidity from a pool specifying exact pool tokens in, with proportional token amounts out.
* The request is implemented by the Vault without any interaction with the pool, ensuring that
* it works the same for all pools, and cannot be disabled by a new pool type.
*
* @param pool Address of the pool
* @param from Address of user to burn pool tokens from
* @param exactBptAmountIn Input pool token amount
* @param minAmountsOut Minimum amounts of tokens to be received, sorted in token registration order
* @return amountsOut Actual calculated amounts of output tokens, sorted in token registration order
*/
function removeLiquidityRecovery(
address pool,
address from,
uint256 exactBptAmountIn,
uint256[] memory minAmountsOut
) external returns (uint256[] memory amountsOut);
/*******************************************************************************
Queries
*******************************************************************************/
/**
* @notice Performs a callback on msg.sender with arguments provided in `data`.
* @dev Used to query a set of operations on the Vault. Only off-chain eth_call are allowed,
* anything else will revert.
*
* Allows querying any operation on the Vault that has the `onlyWhenUnlocked` modifier.
*
* Allows the external calling of a function via the Vault contract to
* access Vault's functions guarded by `onlyWhenUnlocked`.
* `transient` modifier ensuring balances changes within the Vault are settled.
*
* @param data Contains function signature and args to be passed to the msg.sender
* @return result Resulting data from the call
*/
function quote(bytes calldata data) external returns (bytes memory result);
/**
* @notice Performs a callback on msg.sender with arguments provided in `data`.
* @dev Used to query a set of operations on the Vault. Only off-chain eth_call are allowed,
* anything else will revert.
*
* Allows querying any operation on the Vault that has the `onlyWhenUnlocked` modifier.
*
* Allows the external calling of a function via the Vault contract to
* access Vault's functions guarded by `onlyWhenUnlocked`.
* `transient` modifier ensuring balances changes within the Vault are settled.
*
* This call always reverts, returning the result in the revert reason.
*
* @param data Contains function signature and args to be passed to the msg.sender
*/
function quoteAndRevert(bytes calldata data) external;
/**
* @notice Returns true if queries are disabled on the Vault.
* @dev If true, queries might either be disabled temporarily or permanently.
* @return queryDisabled True if query functionality is reversibly disabled
*/
function isQueryDisabled() external view returns (bool queryDisabled);
/**
* @notice Returns true if queries are disabled permanently; false if they are enabled.
* @dev This is a one-way switch. Once queries are disabled permanently, they can never be re-enabled.
* @return queryDisabledPermanently True if query functionality is permanently disabled
*/
function isQueryDisabledPermanently() external view returns (bool queryDisabledPermanently);
/**
* @notice Pools can use this event to emit event data from the Vault.
* @param eventKey Event key
* @param eventData Encoded event data
*/
function emitAuxiliaryEvent(bytes32 eventKey, bytes calldata eventData) external;
/*******************************************************************************
Authentication
*******************************************************************************/
/**
* @notice Returns the Authorizer address.
* @dev The authorizer holds the permissions granted by governance. It is set on Vault deployment,
* and can be changed through a permissioned call.
*
* @return authorizer Address of the authorizer contract
*/
function getAuthorizer() external view returns (IAuthorizer authorizer);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./VaultTypes.sol";
/**
* @notice Interface for functions defined on the main Vault contract.
* @dev These are generally "critical path" functions (swap, add/remove liquidity) that are in the main contract
* for technical or performance reasons.
*/
interface IVaultMain {
/*******************************************************************************
Transient Accounting
*******************************************************************************/
/**
* @notice Creates a context for a sequence of operations (i.e., "unlocks" the Vault).
* @dev Performs a callback on msg.sender with arguments provided in `data`. The Callback is `transient`,
* meaning all balances for the caller have to be settled at the end.
*
* @param data Contains function signature and args to be passed to the msg.sender
* @return result Resulting data from the call
*/
function unlock(bytes calldata data) external returns (bytes memory result);
/**
* @notice Settles deltas for a token; must be successful for the current lock to be released.
* @dev Protects the caller against leftover dust in the Vault for the token being settled. The caller
* should know in advance how many tokens were paid to the Vault, so it can provide it as a hint to discard any
* excess in the Vault balance.
*
* If the given hint is equal to or higher than the difference in reserves, the difference in reserves is given as
* credit to the caller. If it's higher, the caller sent fewer tokens than expected, so settlement would fail.
*
* If the given hint is lower than the difference in reserves, the hint is given as credit to the caller.
* In this case, the excess would be absorbed by the Vault (and reflected correctly in the reserves), but would
* not affect settlement.
*
* The credit supplied by the Vault can be calculated as `min(reserveDifference, amountHint)`, where the reserve
* difference equals current balance of the token minus existing reserves of the token when the function is called.
*
* @param token Address of the token
* @param amountHint Amount paid as reported by the caller
* @return credit Credit received in return of the payment
*/
function settle(IERC20 token, uint256 amountHint) external returns (uint256 credit);
/**
* @notice Sends tokens to a recipient.
* @dev There is no inverse operation for this function. Transfer funds to the Vault and call `settle` to cancel
* debts.
*
* @param token Address of the token
* @param to Recipient address
* @param amount Amount of tokens to send
*/
function sendTo(IERC20 token, address to, uint256 amount) external;
/***************************************************************************
Swaps
***************************************************************************/
/**
* @notice Swaps tokens based on provided parameters.
* @dev All parameters are given in raw token decimal encoding.
* @param vaultSwapParams Parameters for the swap (see above for struct definition)
* @return amountCalculatedRaw Calculated swap amount
* @return amountInRaw Amount of input tokens for the swap
* @return amountOutRaw Amount of output tokens from the swap
*/
function swap(
VaultSwapParams memory vaultSwapParams
) external returns (uint256 amountCalculatedRaw, uint256 amountInRaw, uint256 amountOutRaw);
/***************************************************************************
Add Liquidity
***************************************************************************/
/**
* @notice Adds liquidity to a pool.
* @dev Caution should be exercised when adding liquidity because the Vault has the capability
* to transfer tokens from any user, given that it holds all allowances.
*
* @param params Parameters for the add liquidity (see above for struct definition)
* @return amountsIn Actual amounts of input tokens
* @return bptAmountOut Output pool token amount
* @return returnData Arbitrary (optional) data with an encoded response from the pool
*/
function addLiquidity(
AddLiquidityParams memory params
) external returns (uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData);
/***************************************************************************
Remove Liquidity
***************************************************************************/
/**
* @notice Removes liquidity from a pool.
* @dev Trusted routers can burn pool tokens belonging to any user and require no prior approval from the user.
* Untrusted routers require prior approval from the user. This is the only function allowed to call
* _queryModeBalanceIncrease (and only in a query context).
*
* @param params Parameters for the remove liquidity (see above for struct definition)
* @return bptAmountIn Actual amount of BPT burned
* @return amountsOut Actual amounts of output tokens
* @return returnData Arbitrary (optional) data with an encoded response from the pool
*/
function removeLiquidity(
RemoveLiquidityParams memory params
) external returns (uint256 bptAmountIn, uint256[] memory amountsOut, bytes memory returnData);
/*******************************************************************************
Pool Information
*******************************************************************************/
/**
* @notice Gets the index of a token in a given pool.
* @dev Reverts if the pool is not registered, or if the token does not belong to the pool.
* @param pool Address of the pool
* @param token Address of the token
* @return tokenCount Number of tokens in the pool
* @return index Index corresponding to the given token in the pool's token list
*/
function getPoolTokenCountAndIndexOfToken(
address pool,
IERC20 token
) external view returns (uint256 tokenCount, uint256 index);
/*******************************************************************************
Balancer Pool Tokens
*******************************************************************************/
/**
* @notice Transfers pool token from owner to a recipient.
* @dev Notice that the pool token address is not included in the params. This function is exclusively called by
* the pool contract, so msg.sender is used as the token address.
*
* @param owner Address of the owner
* @param to Address of the recipient
* @param amount Amount of tokens to transfer
* @return success True if successful, false otherwise
*/
function transfer(address owner, address to, uint256 amount) external returns (bool);
/**
* @notice Transfers pool token from a sender to a recipient using an allowance.
* @dev Notice that the pool token address is not included in the params. This function is exclusively called by
* the pool contract, so msg.sender is used as the token address.
*
* @param spender Address allowed to perform the transfer
* @param from Address of the sender
* @param to Address of the recipient
* @param amount Amount of tokens to transfer
* @return success True if successful, false otherwise
*/
function transferFrom(address spender, address from, address to, uint256 amount) external returns (bool success);
/*******************************************************************************
ERC4626 Buffers
*******************************************************************************/
/**
* @notice Wraps/unwraps tokens based on the parameters provided.
* @dev All parameters are given in raw token decimal encoding. It requires the buffer to be initialized,
* and uses the internal wrapped token buffer when it has enough liquidity to avoid external calls.
*
* @param params Parameters for the wrap/unwrap operation (see struct definition)
* @return amountCalculatedRaw Calculated swap amount
* @return amountInRaw Amount of input tokens for the swap
* @return amountOutRaw Amount of output tokens from the swap
*/
function erc4626BufferWrapOrUnwrap(
BufferWrapOrUnwrapParams memory params
) external returns (uint256 amountCalculatedRaw, uint256 amountInRaw, uint256 amountOutRaw);
/*******************************************************************************
Miscellaneous
*******************************************************************************/
/**
* @notice Returns the VaultExtension contract address.
* @dev Function is in the main Vault contract. The VaultExtension handles less critical or frequently used
* functions, since delegate calls through the Vault are more expensive than direct calls.
*
* @return vaultExtension Address of the VaultExtension
*/
function getVaultExtension() external view returns (address vaultExtension);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
/// @notice Simple interface to retrieve the version of a deployed contract.
interface IVersion {
/**
* @notice Return arbitrary text representing the version of a contract.
* @dev For standard Balancer contracts, returns a JSON representation of the contract version containing name,
* version number and task ID. See real examples in the deployment repo; local tests just use plain text strings.
*
* @return version The version string corresponding to the current deployed contract
*/
function version() external view returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @notice Interface for WETH9.
* See https://github.com/gnosis/canonical-weth/blob/0dd1ea3e295eef916d0c6223ec63141137d22d67/contracts/WETH9.sol
*/
interface IWETH is IERC20 {
/**
* @notice "wrap" native ETH to WETH.
* @dev The amount is msg.value.
*/
function deposit() external payable;
/**
* @notice "unwrap" WETH to native ETH.
* @param amount The amount to withdraw
*/
function withdraw(uint256 amount) external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { CastingHelpers } from "./CastingHelpers.sol";
library InputHelpers {
/// @notice Arrays passed to a function and intended to be parallel have different lengths.
error InputLengthMismatch();
/**
* @notice More than one non-zero value was given for a single token operation.
* @dev Input arrays for single token add/remove liquidity operations are expected to have only one non-zero value,
* corresponding to the token being added or removed. This error results if there are multiple non-zero entries.
*/
error MultipleNonZeroInputs();
/**
* @notice No valid input was given for a single token operation.
* @dev Input arrays for single token add/remove liquidity operations are expected to have one non-zero value,
* corresponding to the token being added or removed. This error results if all entries are zero.
*/
error AllZeroInputs();
/**
* @notice The tokens supplied to an array argument were not sorted in numerical order.
* @dev Tokens are not sorted by address on registration. This is an optimization so that off-chain processes can
* predict the token order without having to query the Vault. (It is also legacy v2 behavior.)
*/
error TokensNotSorted();
function ensureInputLengthMatch(uint256 a, uint256 b) internal pure {
if (a != b) {
revert InputLengthMismatch();
}
}
function ensureInputLengthMatch(uint256 a, uint256 b, uint256 c) internal pure {
if (a != b || b != c) {
revert InputLengthMismatch();
}
}
// Find the single non-zero input; revert if there is not exactly one such value.
function getSingleInputIndex(uint256[] memory maxAmountsIn) internal pure returns (uint256 inputIndex) {
uint256 length = maxAmountsIn.length;
inputIndex = length;
for (uint256 i = 0; i < length; ++i) {
if (maxAmountsIn[i] != 0) {
if (inputIndex != length) {
revert MultipleNonZeroInputs();
}
inputIndex = i;
}
}
if (inputIndex >= length) {
revert AllZeroInputs();
}
return inputIndex;
}
/**
* @dev Sort an array of tokens, mutating in place (and also returning them).
* This assumes the tokens have been (or will be) validated elsewhere for length
* and non-duplication. All this does is the sorting.
*
* A bubble sort should be gas- and bytecode-efficient enough for such small arrays.
* Could have also done "manual" comparisons for each of the cases, but this is
* about the same number of operations, and more concise.
*
* This is less efficient for larger token count (i.e., above 4), but such pools should
* be rare. And in any case, sorting is only done on-chain in test code.
*/
function sortTokens(IERC20[] memory tokens) internal pure returns (IERC20[] memory) {
for (uint256 i = 0; i < tokens.length - 1; ++i) {
for (uint256 j = 0; j < tokens.length - i - 1; ++j) {
if (tokens[j] > tokens[j + 1]) {
// Swap if they're out of order.
(tokens[j], tokens[j + 1]) = (tokens[j + 1], tokens[j]);
}
}
}
return tokens;
}
/// @dev Ensure an array of tokens is sorted. As above, does not validate length or uniqueness.
function ensureSortedTokens(IERC20[] memory tokens) internal pure {
IERC20 previous = tokens[0];
for (uint256 i = 1; i < tokens.length; ++i) {
IERC20 current = tokens[i];
if (previous > current) {
revert TokensNotSorted();
}
previous = current;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { StorageSlotExtension } from "./StorageSlotExtension.sol";
/**
* @notice Variant of {ReentrancyGuard} that uses transient storage.
* @dev NOTE: This variant only works on networks where EIP-1153 is available.
*/
abstract contract ReentrancyGuardTransient {
using StorageSlotExtension for *;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant _REENTRANCY_GUARD_STORAGE =
0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
/// @notice Unauthorized reentrant call.
error ReentrancyGuardReentrantCall();
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED.
if (_reentrancyGuardEntered()) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail.
_REENTRANCY_GUARD_STORAGE.asBoolean().tstore(true);
}
function _nonReentrantAfter() private {
_REENTRANCY_GUARD_STORAGE.asBoolean().tstore(false);
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _REENTRANCY_GUARD_STORAGE.asBoolean().tload();
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
// solhint-disable no-inline-assembly
/// @notice Support `quoteAndRevert`: a v2-style query which always reverts, and returns the result in the return data.
library RevertCodec {
/**
* @notice On success of the primary operation in a `quoteAndRevert`, this error is thrown with the return data.
* @param result The result of the query operation
*/
error Result(bytes result);
/// @notice Handle the "reverted without a reason" case (i.e., no return data).
error ErrorSelectorNotFound();
function catchEncodedResult(bytes memory resultRaw) internal pure returns (bytes memory) {
bytes4 errorSelector = RevertCodec.parseSelector(resultRaw);
if (errorSelector != Result.selector) {
// Bubble up error message if the revert reason is not the expected one.
RevertCodec.bubbleUpRevert(resultRaw);
}
uint256 resultRawLength = resultRaw.length;
assembly ("memory-safe") {
resultRaw := add(resultRaw, 0x04) // Slice the sighash
mstore(resultRaw, sub(resultRawLength, 4)) // Set proper length
}
return abi.decode(resultRaw, (bytes));
}
/// @dev Returns the first 4 bytes in an array, reverting if the length is < 4.
function parseSelector(bytes memory callResult) internal pure returns (bytes4 errorSelector) {
if (callResult.length < 4) {
revert ErrorSelectorNotFound();
}
assembly ("memory-safe") {
errorSelector := mload(add(callResult, 0x20)) // Load the first 4 bytes from data (skip length offset)
}
}
/// @dev Taken from Openzeppelin's Address.
function bubbleUpRevert(bytes memory returnData) internal pure {
// Look for revert reason and bubble it up if present.
if (returnData.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly.
assembly ("memory-safe") {
let return_data_size := mload(returnData)
revert(add(32, returnData), return_data_size)
}
} else {
revert ErrorSelectorNotFound();
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";
import { IRouterCommon } from "@balancer-labs/v3-interfaces/contracts/vault/IRouterCommon.sol";
import { IWETH } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol";
import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import { StorageSlotExtension } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/StorageSlotExtension.sol";
import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol";
import { RevertCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/RevertCodec.sol";
import {
ReentrancyGuardTransient
} from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/ReentrancyGuardTransient.sol";
import { Version } from "@balancer-labs/v3-solidity-utils/contracts/helpers/Version.sol";
import {
TransientStorageHelpers
} from "@balancer-labs/v3-solidity-utils/contracts/helpers/TransientStorageHelpers.sol";
import { VaultGuard } from "./VaultGuard.sol";
/**
* @notice Abstract base contract for functions shared among all Routers.
* @dev Common functionality includes access to the sender (which would normally be obscured, since msg.sender in the
* Vault is the Router contract itself, not the account that invoked the Router), versioning, and the external
* invocation functions (`permitBatchAndCall` and `multicall`).
*/
abstract contract RouterCommon is IRouterCommon, VaultGuard, ReentrancyGuardTransient, Version {
using Address for address payable;
using StorageSlotExtension for *;
using SafeERC20 for IWETH;
using SafeCast for *;
// NOTE: If you use a constant, then it is simply replaced everywhere when this constant is used by what is written
// after =. If you use immutable, the value is first calculated and then replaced everywhere. That means that if a
// constant has executable variables, they will be executed every time the constant is used.
// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _SENDER_SLOT = TransientStorageHelpers.calculateSlot(type(RouterCommon).name, "sender");
// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _IS_RETURN_ETH_LOCKED_SLOT =
TransientStorageHelpers.calculateSlot(type(RouterCommon).name, "isReturnEthLocked");
/// @notice Incoming ETH transfer from an address that is not WETH.
error EthTransfer();
/// @notice The amount of ETH paid is insufficient to complete this operation.
error InsufficientEth();
/// @notice The swap transaction was not validated before the specified deadline timestamp.
error SwapDeadline();
// Raw token balances are stored in half a slot, so the max is uint128. Moreover, given that amounts are usually
// scaled inside the Vault, sending type(uint256).max would result in an overflow and revert.
uint256 internal constant _MAX_AMOUNT = type(uint128).max;
// solhint-disable-next-line var-name-mixedcase
IWETH internal immutable _weth;
IPermit2 internal immutable _permit2;
/**
* @notice Saves the user or contract that initiated the current operation.
* @dev It is possible to nest router calls (e.g., with reentrant hooks), but the sender returned by the Router's
* `getSender` function will always be the "outermost" caller. Some transactions require the Router to identify
* multiple senders. Consider the following example:
*
* - ContractA has a function that calls the Router, then calls ContractB with the output. ContractB in turn
* calls back into the Router.
* - Imagine further that ContractA is a pool with a "before" hook that also calls the Router.
*
* When the user calls the function on ContractA, there are three calls to the Router in the same transaction:
* - 1st call: When ContractA calls the Router directly, to initiate an operation on the pool (say, a swap).
* (Sender is contractA, initiator of the operation.)
*
* - 2nd call: When the pool operation invokes a hook (say onBeforeSwap), which calls back into the Router.
* This is a "nested" call within the original pool operation. The nested call returns, then the
* before hook returns, the Router completes the operation, and finally returns back to ContractA
* with the result (e.g., a calculated amount of tokens).
* (Nested call; sender is still ContractA through all of this.)
*
* - 3rd call: When the first operation is complete, ContractA calls ContractB, which in turn calls the Router.
* (Not nested, as the original router call from contractA has returned. Sender is now ContractB.)
*/
modifier saveSender(address sender) {
bool isExternalSender = _saveSender(sender);
_;
_discardSenderIfRequired(isExternalSender);
}
/**
* @notice Locks the return of excess ETH to the sender until the end of the function.
* @dev This also encompasses the `saveSender` functionality.
*/
modifier saveSenderAndManageEth() {
bool isExternalSender = _saveSender(msg.sender);
// Revert if a function with this modifier is called recursively (e.g., multicall).
if (_isReturnEthLockedSlot().tload()) {
revert ReentrancyGuardReentrantCall();
}
// Lock the return of ETH during execution
_isReturnEthLockedSlot().tstore(true);
_;
_isReturnEthLockedSlot().tstore(false);
address sender = _getSenderSlot().tload();
_discardSenderIfRequired(isExternalSender);
_returnEth(sender);
}
function _saveSender(address sender) internal returns (bool isExternalSender) {
address savedSender = _getSenderSlot().tload();
// NOTE: Only the most external sender will be saved by the Router.
if (savedSender == address(0)) {
_getSenderSlot().tstore(sender);
isExternalSender = true;
}
}
function _discardSenderIfRequired(bool isExternalSender) internal {
// Only the external sender shall be cleaned up; if it's not an external sender it means that
// the value was not saved in this modifier.
if (isExternalSender) {
_getSenderSlot().tstore(address(0));
}
}
constructor(
IVault vault,
IWETH weth,
IPermit2 permit2,
string memory routerVersion
) VaultGuard(vault) Version(routerVersion) {
_weth = weth;
_permit2 = permit2;
}
/*******************************************************************************
Utilities
*******************************************************************************/
struct SignatureParts {
bytes32 r;
bytes32 s;
uint8 v;
}
/// @inheritdoc IRouterCommon
function permitBatchAndCall(
PermitApproval[] calldata permitBatch,
bytes[] calldata permitSignatures,
IAllowanceTransfer.PermitBatch calldata permit2Batch,
bytes calldata permit2Signature,
bytes[] calldata multicallData
) external payable virtual returns (bytes[] memory results) {
_permitBatch(permitBatch, permitSignatures, permit2Batch, permit2Signature);
// Execute all the required operations once permissions have been granted.
return multicall(multicallData);
}
function _permitBatch(
PermitApproval[] calldata permitBatch,
bytes[] calldata permitSignatures,
IAllowanceTransfer.PermitBatch calldata permit2Batch,
bytes calldata permit2Signature
) internal nonReentrant {
InputHelpers.ensureInputLengthMatch(permitBatch.length, permitSignatures.length);
// Use Permit (ERC-2612) to grant allowances to Permit2 for tokens to swap,
// and grant allowances to Vault for BPT tokens.
for (uint256 i = 0; i < permitBatch.length; ++i) {
bytes memory signature = permitSignatures[i];
SignatureParts memory signatureParts = _getSignatureParts(signature);
PermitApproval memory permitApproval = permitBatch[i];
try
IERC20Permit(permitApproval.token).permit(
permitApproval.owner,
address(this),
permitApproval.amount,
permitApproval.deadline,
signatureParts.v,
signatureParts.r,
signatureParts.s
)
{
// solhint-disable-previous-line no-empty-blocks
// OK; carry on.
} catch (bytes memory returnData) {
// Did it fail because the permit was executed (possible DoS attack to make the transaction revert),
// or was it something else (e.g., deadline, invalid signature)?
if (
IERC20(permitApproval.token).allowance(permitApproval.owner, address(this)) != permitApproval.amount
) {
// It was something else, or allowance was used, so we should revert. Bubble up the revert reason.
RevertCodec.bubbleUpRevert(returnData);
}
}
}
// Only call permit2 if there's something to do.
if (permit2Batch.details.length > 0) {
// Use Permit2 for tokens that are swapped and added into the Vault. Note that this call on Permit2 is
// theoretically also vulnerable to the same DoS attack as above. This edge case was not mitigated
// on-chain, mainly due to the increased complexity and cost of protecting the batch call.
//
// If this is a concern, we recommend submitting through a private node to avoid front-running the public
// mempool. In any case, best practice is to always use expiring, limited approvals, and only with known
// and trusted contracts.
//
// See https://www.immunebytes.com/blog/permit2-erc-20-token-approvals-and-associated-risks/.
_permit2.permit(msg.sender, permit2Batch, permit2Signature);
}
}
/// @inheritdoc IRouterCommon
function multicall(
bytes[] calldata data
) public payable virtual saveSenderAndManageEth returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; ++i) {
results[i] = Address.functionDelegateCall(address(this), data[i]);
}
return results;
}
function _getSignatureParts(bytes memory signature) private pure returns (SignatureParts memory signatureParts) {
bytes32 r;
bytes32 s;
uint8 v;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
signatureParts.r = r;
signatureParts.s = s;
signatureParts.v = v;
}
/**
* @dev Returns excess ETH back to the contract caller. Checks for sufficient ETH balance are made right before
* each deposit, ensuring it will revert with a friendly custom error. If there is any balance remaining when
* `_returnEth` is called, return it to the sender.
*
* Because the caller might not know exactly how much ETH a Vault action will require, they may send extra.
* Note that this excess value is returned *to the contract caller* (msg.sender). If caller and e.g. swap sender
* are not the same (because the caller is a relayer for the sender), then it is up to the caller to manage this
* returned ETH.
*/
function _returnEth(address sender) internal {
// It's cheaper to check the balance and return early than checking a transient variable.
// Moreover, most operations will not have ETH to return.
uint256 excess = address(this).balance;
if (excess == 0) {
return;
}
// If the return of ETH is locked, then don't return it,
// because _returnEth will be called again at the end of the call.
if (_isReturnEthLockedSlot().tload()) {
return;
}
payable(sender).sendValue(excess);
}
/**
* @dev Returns an array with `amountGiven` at `tokenIndex`, and 0 for every other index.
* The returned array length matches the number of tokens in the pool.
* Reverts if the given index is greater than or equal to the pool number of tokens.
*/
function _getSingleInputArrayAndTokenIndex(
address pool,
IERC20 token,
uint256 amountGiven
) internal view returns (uint256[] memory amountsGiven, uint256 tokenIndex) {
uint256 numTokens;
(numTokens, tokenIndex) = _vault.getPoolTokenCountAndIndexOfToken(pool, token);
amountsGiven = new uint256[](numTokens);
amountsGiven[tokenIndex] = amountGiven;
}
function _takeTokenIn(address sender, IERC20 tokenIn, uint256 amountIn, bool wethIsEth) internal {
// If the tokenIn is ETH, then wrap `amountIn` into WETH.
if (wethIsEth && tokenIn == _weth) {
if (address(this).balance < amountIn) {
revert InsufficientEth();
}
// wrap amountIn to WETH.
_weth.deposit{ value: amountIn }();
// send WETH to Vault.
_weth.safeTransfer(address(_vault), amountIn);
// update Vault accounting.
_vault.settle(_weth, amountIn);
} else {
if (amountIn > 0) {
// Send the tokenIn amount to the Vault.
_permit2.transferFrom(sender, address(_vault), amountIn.toUint160(), address(tokenIn));
_vault.settle(tokenIn, amountIn);
}
}
}
function _sendTokenOut(address sender, IERC20 tokenOut, uint256 amountOut, bool wethIsEth) internal {
if (amountOut == 0) {
return;
}
// If the tokenOut is ETH, then unwrap `amountOut` into ETH.
if (wethIsEth && tokenOut == _weth) {
// Receive the WETH amountOut.
_vault.sendTo(tokenOut, address(this), amountOut);
// Withdraw WETH to ETH.
_weth.withdraw(amountOut);
// Send ETH to sender.
payable(sender).sendValue(amountOut);
} else {
// Receive the tokenOut amountOut.
_vault.sendTo(tokenOut, sender, amountOut);
}
}
function _maxTokenLimits(address pool) internal view returns (uint256[] memory maxLimits) {
uint256 numTokens = _vault.getPoolTokens(pool).length;
maxLimits = new uint256[](numTokens);
for (uint256 i = 0; i < numTokens; ++i) {
maxLimits[i] = _MAX_AMOUNT;
}
}
/**
* @dev Enables the Router to receive ETH. This is required for it to be able to unwrap WETH, which sends ETH to the
* caller.
*
* Any ETH sent to the Router outside of the WETH unwrapping mechanism would be forever locked inside the Router, so
* we prevent that from happening. Other mechanisms used to send ETH to the Router (such as being the recipient of
* an ETH swap, Pool exit or withdrawal, contract self-destruction, or receiving the block mining reward) will
* result in locked funds, but are not otherwise a security or soundness issue. This check only exists as an attempt
* to prevent user error.
*/
receive() external payable {
if (msg.sender != address(_weth)) {
revert EthTransfer();
}
}
/// @inheritdoc IRouterCommon
function getSender() external view returns (address) {
return _getSenderSlot().tload();
}
function _getSenderSlot() internal view returns (StorageSlotExtension.AddressSlotType) {
return _SENDER_SLOT.asAddress();
}
function _isReturnEthLockedSlot() internal view returns (StorageSlotExtension.BooleanSlotType) {
return _IS_RETURN_ETH_LOCKED_SLOT.asBoolean();
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @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.
*/
library SafeCast {
/**
* @dev Value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev An uint value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
/**
* @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
*/
function toUint248(uint256 value) internal pure returns (uint248) {
if (value > type(uint248).max) {
revert SafeCastOverflowedUintDowncast(248, value);
}
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
*/
function toUint240(uint256 value) internal pure returns (uint240) {
if (value > type(uint240).max) {
revert SafeCastOverflowedUintDowncast(240, value);
}
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
*/
function toUint232(uint256 value) internal pure returns (uint232) {
if (value > type(uint232).max) {
revert SafeCastOverflowedUintDowncast(232, value);
}
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
*/
function toUint224(uint256 value) internal pure returns (uint224) {
if (value > type(uint224).max) {
revert SafeCastOverflowedUintDowncast(224, value);
}
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
*/
function toUint216(uint256 value) internal pure returns (uint216) {
if (value > type(uint216).max) {
revert SafeCastOverflowedUintDowncast(216, value);
}
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
*/
function toUint208(uint256 value) internal pure returns (uint208) {
if (value > type(uint208).max) {
revert SafeCastOverflowedUintDowncast(208, value);
}
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
*/
function toUint200(uint256 value) internal pure returns (uint200) {
if (value > type(uint200).max) {
revert SafeCastOverflowedUintDowncast(200, value);
}
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
*/
function toUint192(uint256 value) internal pure returns (uint192) {
if (value > type(uint192).max) {
revert SafeCastOverflowedUintDowncast(192, value);
}
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
*/
function toUint184(uint256 value) internal pure returns (uint184) {
if (value > type(uint184).max) {
revert SafeCastOverflowedUintDowncast(184, value);
}
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
*/
function toUint176(uint256 value) internal pure returns (uint176) {
if (value > type(uint176).max) {
revert SafeCastOverflowedUintDowncast(176, value);
}
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
*/
function toUint168(uint256 value) internal pure returns (uint168) {
if (value > type(uint168).max) {
revert SafeCastOverflowedUintDowncast(168, value);
}
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
*/
function toUint160(uint256 value) internal pure returns (uint160) {
if (value > type(uint160).max) {
revert SafeCastOverflowedUintDowncast(160, value);
}
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
*/
function toUint152(uint256 value) internal pure returns (uint152) {
if (value > type(uint152).max) {
revert SafeCastOverflowedUintDowncast(152, value);
}
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
*/
function toUint144(uint256 value) internal pure returns (uint144) {
if (value > type(uint144).max) {
revert SafeCastOverflowedUintDowncast(144, value);
}
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
*/
function toUint136(uint256 value) internal pure returns (uint136) {
if (value > type(uint136).max) {
revert SafeCastOverflowedUintDowncast(136, value);
}
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
*/
function toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) {
revert SafeCastOverflowedUintDowncast(128, value);
}
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
*/
function toUint120(uint256 value) internal pure returns (uint120) {
if (value > type(uint120).max) {
revert SafeCastOverflowedUintDowncast(120, value);
}
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
*/
function toUint112(uint256 value) internal pure returns (uint112) {
if (value > type(uint112).max) {
revert SafeCastOverflowedUintDowncast(112, value);
}
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
*/
function toUint104(uint256 value) internal pure returns (uint104) {
if (value > type(uint104).max) {
revert SafeCastOverflowedUintDowncast(104, value);
}
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
*/
function toUint96(uint256 value) internal pure returns (uint96) {
if (value > type(uint96).max) {
revert SafeCastOverflowedUintDowncast(96, value);
}
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
*/
function toUint88(uint256 value) internal pure returns (uint88) {
if (value > type(uint88).max) {
revert SafeCastOverflowedUintDowncast(88, value);
}
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
*/
function toUint80(uint256 value) internal pure returns (uint80) {
if (value > type(uint80).max) {
revert SafeCastOverflowedUintDowncast(80, value);
}
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
*/
function toUint72(uint256 value) internal pure returns (uint72) {
if (value > type(uint72).max) {
revert SafeCastOverflowedUintDowncast(72, value);
}
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
*/
function toUint64(uint256 value) internal pure returns (uint64) {
if (value > type(uint64).max) {
revert SafeCastOverflowedUintDowncast(64, value);
}
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
*/
function toUint56(uint256 value) internal pure returns (uint56) {
if (value > type(uint56).max) {
revert SafeCastOverflowedUintDowncast(56, value);
}
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
*/
function toUint48(uint256 value) internal pure returns (uint48) {
if (value > type(uint48).max) {
revert SafeCastOverflowedUintDowncast(48, value);
}
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
*/
function toUint40(uint256 value) internal pure returns (uint40) {
if (value > type(uint40).max) {
revert SafeCastOverflowedUintDowncast(40, value);
}
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
*/
function toUint32(uint256 value) internal pure returns (uint32) {
if (value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, value);
}
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
*/
function toUint24(uint256 value) internal pure returns (uint24) {
if (value > type(uint24).max) {
revert SafeCastOverflowedUintDowncast(24, value);
}
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
*/
function toUint16(uint256 value) internal pure returns (uint16) {
if (value > type(uint16).max) {
revert SafeCastOverflowedUintDowncast(16, value);
}
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
*/
function toUint8(uint256 value) internal pure returns (uint8) {
if (value > type(uint8).max) {
revert SafeCastOverflowedUintDowncast(8, value);
}
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
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
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(248, value);
}
}
/**
* @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
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(240, value);
}
}
/**
* @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
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(232, value);
}
}
/**
* @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
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(224, value);
}
}
/**
* @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
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(216, value);
}
}
/**
* @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
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(208, value);
}
}
/**
* @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
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(200, value);
}
}
/**
* @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
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(192, value);
}
}
/**
* @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
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(184, value);
}
}
/**
* @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
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(176, value);
}
}
/**
* @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
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(168, value);
}
}
/**
* @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
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(160, value);
}
}
/**
* @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
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(152, value);
}
}
/**
* @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
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(144, value);
}
}
/**
* @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
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(136, value);
}
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(128, value);
}
}
/**
* @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
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(120, value);
}
}
/**
* @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
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(112, value);
}
}
/**
* @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
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(104, value);
}
}
/**
* @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
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(96, value);
}
}
/**
* @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
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(88, value);
}
}
/**
* @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
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(80, value);
}
}
/**
* @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
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(72, value);
}
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(64, value);
}
}
/**
* @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
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(56, value);
}
}
/**
* @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
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(48, value);
}
}
/**
* @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
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(40, value);
}
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(32, value);
}
}
/**
* @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
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(24, value);
}
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(16, value);
}
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(8, value);
}
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
if (value > uint256(type(int256).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int256(value);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.
// Taken from https://raw.githubusercontent.com/Amxx/openzeppelin-contracts/ce497cb05ca05bb9aa2b86ec1d99e6454e7ab2e9/contracts/utils/SlotDerivation.sol
pragma solidity ^0.8.20;
/**
* @notice Library for computing storage (and transient storage) locations from namespaces and deriving slots
* corresponding to standard patterns.
* @dev The derivation method for array and mapping matches the storage layout used by the solidity language/compiler.
*
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
*
* Example usage:
* ```solidity
* contract Example {
* // Add the library methods
* using StorageSlot for bytes32;
* using SlotDerivation for bytes32;
*
* // Declare a namespace
* string private constant _NAMESPACE = "<namespace>" // eg. OpenZeppelin.Slot
*
* function setValueInNamespace(uint256 key, address newValue) internal {
* _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
* }
*
* function getValueInNamespace(uint256 key) internal view returns (address) {
* return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
* }
* }
* ```
*
* TIP: Consider using this library along with {StorageSlot}.
*
* NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
* upgrade safety will ignore the slots accessed through this library.
*/
library SlotDerivation {
/// @dev Derive an ERC-7201 slot from a string (namespace).
function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
slot := and(keccak256(0x00, 0x20), not(0xff))
}
}
/// @dev Add an offset to a slot to get the n-th element of a structure or an array.
function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
unchecked {
return bytes32(uint256(slot) + pos);
}
}
/// @dev Derive the location of the first element in an array from the slot where the length is stored.
function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, slot)
result := keccak256(0x00, 0x20)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, key)
mstore(0x20, slot)
result := keccak256(0x00, 0x40)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
/// @dev Derive the location of a mapping element from the key.
function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let length := mload(key)
let begin := add(key, 0x20)
let end := add(begin, length)
let cache := mload(end)
mstore(end, slot)
result := keccak256(begin, add(length, 0x20))
mstore(end, cache)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @notice Library for reading and writing primitive types to specific storage slots. Based on OpenZeppelin; just adding support for int256.
* @dev TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlotExtension {
struct Int256Slot {
int256 value;
}
/// @dev Returns an `Int256Slot` with member `value` located at `slot`.
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/// @dev Custom type that represents a slot holding an address.
type AddressSlotType is bytes32;
/// @dev Cast an arbitrary slot to a AddressSlotType.
function asAddress(bytes32 slot) internal pure returns (AddressSlotType) {
return AddressSlotType.wrap(slot);
}
/// @dev Custom type that represents a slot holding a boolean.
type BooleanSlotType is bytes32;
/// @dev Cast an arbitrary slot to a BooleanSlotType.
function asBoolean(bytes32 slot) internal pure returns (BooleanSlotType) {
return BooleanSlotType.wrap(slot);
}
/// @dev Custom type that represents a slot holding a bytes32.
type Bytes32SlotType is bytes32;
/// @dev Cast an arbitrary slot to a Bytes32SlotType.
function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) {
return Bytes32SlotType.wrap(slot);
}
/// @dev Custom type that represents a slot holding a uint256.
type Uint256SlotType is bytes32;
/// @dev Cast an arbitrary slot to a Uint256SlotType.
function asUint256(bytes32 slot) internal pure returns (Uint256SlotType) {
return Uint256SlotType.wrap(slot);
}
/// @dev Custom type that represents a slot holding an int256.
type Int256SlotType is bytes32;
/// @dev Cast an arbitrary slot to an Int256SlotType.
function asInt256(bytes32 slot) internal pure returns (Int256SlotType) {
return Int256SlotType.wrap(slot);
}
/// @dev Load the value held at location `slot` in transient storage.
function tload(AddressSlotType slot) internal view returns (address value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}
/// @dev Store `value` at location `slot` in transient storage.
function tstore(AddressSlotType slot, address value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
/// @dev Load the value held at location `slot` in transient storage.
function tload(BooleanSlotType slot) internal view returns (bool value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}
/// @dev Store `value` at location `slot` in transient storage.
function tstore(BooleanSlotType slot, bool value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
/// @dev Load the value held at location `slot` in transient storage.
function tload(Bytes32SlotType slot) internal view returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}
/// @dev Store `value` at location `slot` in transient storage.
function tstore(Bytes32SlotType slot, bytes32 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
/// @dev Load the value held at location `slot` in transient storage.
function tload(Uint256SlotType slot) internal view returns (uint256 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}
/// @dev Store `value` at location `slot` in transient storage.
function tstore(Uint256SlotType slot, uint256 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
/// @dev Load the value held at location `slot` in transient storage.
function tload(Int256SlotType slot) internal view returns (int256 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}
/// @dev Store `value` at location `slot` in transient storage.
function tstore(Int256SlotType slot, int256 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { StorageSlotExtension } from "./StorageSlotExtension.sol";
import {
AddressArraySlotType,
AddressToUintMappingSlot,
TransientStorageHelpers
} from "../helpers/TransientStorageHelpers.sol";
/**
* @notice Library for managing sets of primitive types.
* @dev See https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive types.
*
* Based on the EnumerableSet library from OpenZeppelin Contracts, altered to remove the base private functions that
* work on bytes32, replacing them with a native implementation for address values, to reduce bytecode size and
* runtime costs. It also uses transient storage.
*
* The `unchecked_at` function was also added, which allows for more gas efficient data reads in some scenarios.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using TransientEnumerableSet for TransientEnumerableSet.AddressSet;
*
* // Declare a set state variable
* TransientEnumerableSet.AddressSet private mySet;
* }
* ```
*/
library TransientEnumerableSet {
using TransientStorageHelpers for *;
using StorageSlotExtension for StorageSlotExtension.Uint256SlotType;
// The original OpenZeppelin implementation uses a generic Set type with bytes32 values: this was replaced with
// AddressSet, which uses address keys natively, resulting in more dense bytecode.
// solhint-disable func-name-mixedcase
struct AddressSet {
// Storage of set values.
address[] __values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(address addressKey => uint256 indexValue) __indexes;
}
/// @notice An index is beyond the current bounds of the set.
error IndexOutOfBounds();
/// @notice An element that is not present in the set.
error ElementNotFound();
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, if it was not already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
if (!contains(set, value)) {
_values(set).tPush(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value.
_indexes(set).tSet(value, _values(set).tLength());
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set; i.e., if it was present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot.
uint256 valueIndex = _indexes(set).tGet(value);
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex;
uint256 lastIndex;
unchecked {
toDeleteIndex = valueIndex - 1;
lastIndex = _values(set).tLength() - 1;
}
// The swap is only necessary if we're not removing the last element.
if (toDeleteIndex != lastIndex) {
address lastValue = _values(set).tAt(lastIndex);
// Move the last entry to the index of the entry to delete.
_values(set).tSet(toDeleteIndex, lastValue);
// Update the index for the moved value.
_indexes(set).tSet(lastValue, valueIndex); // = toDeleteIndex + 1; all indices are 1-based
}
// Delete the slot where the moved value was stored.
_values(set).tPop();
// We need to delete the index for the deleted slot with transient storage because another operation in the
// same transaction may want to add the same element to the array again.
_indexes(set).tSet(value, 0);
return true;
} else {
return false;
}
}
/// @dev Returns true if the value is in the set. O(1).
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _indexes(set).tGet(value) != 0;
}
/// @dev Returns the number of values on the set. O(1).
function length(AddressSet storage set) internal view returns (uint256) {
return _values(set).tLength();
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
if (index >= _values(set).tLength()) {
revert IndexOutOfBounds();
}
return unchecked_at(set, index);
}
/**
* @dev Same as {at}, except this doesn't revert if `index` it outside of the set (i.e. if it is equal or larger
* than {length}). O(1).
*
* This function performs one less storage read than {at}, but should only be used when `index` is known to be
* within bounds.
*/
function unchecked_at(AddressSet storage set, uint256 index) internal view returns (address) {
return _values(set).tUncheckedAt(index);
}
/// @dev Return the index of an element in the set, or revert if not found.
function indexOf(AddressSet storage set, address value) internal view returns (uint256) {
uint256 rawIndex = _indexes(set).tGet(value);
if (rawIndex == 0) {
revert ElementNotFound();
}
unchecked {
return rawIndex - 1;
}
}
/**
* @dev Same as {indexOf}, except this doesn't revert if the element isn't present in the set.
* In this case, it returns 0.
*
* This function performs one less storage read than {indexOf}, but should only be used when `index` is known to be
* within bounds.
*/
function unchecked_indexOf(AddressSet storage set, address value) internal view returns (uint256) {
uint256 rawIndex = _indexes(set).tGet(value);
unchecked {
return rawIndex == 0 ? 0 : rawIndex - 1;
}
}
// Transient storage functions
/// @dev Return the raw contents of the underlying address array.
function values(AddressSet storage set) internal view returns (address[] memory memValues) {
uint256 len = _values(set).tLength();
memValues = new address[](len);
for (uint256 i = 0; i < len; ++i) {
memValues[i] = _values(set).tUncheckedAt(i);
}
}
// solhint-disable no-inline-assembly
function _values(AddressSet storage set) private view returns (AddressArraySlotType slot) {
address[] storage structValues = set.__values;
assembly ("memory-safe") {
slot := structValues.slot
}
}
function _indexes(AddressSet storage set) private view returns (AddressToUintMappingSlot slot) {
mapping(address addressKey => uint256 indexValue) storage indexes = set.__indexes;
assembly ("memory-safe") {
slot := indexes.slot
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { StorageSlotExtension } from "../openzeppelin/StorageSlotExtension.sol";
import { SlotDerivation } from "../openzeppelin/SlotDerivation.sol";
type TokenDeltaMappingSlotType is bytes32;
type AddressToUintMappingSlot is bytes32;
type UintToAddressToBooleanMappingSlot is bytes32;
type AddressArraySlotType is bytes32;
/**
* @notice Helper functions to read and write values from transient storage, including support for arrays and mappings.
* @dev This is temporary, based on Open Zeppelin's partially released library. When the final version is published, we
* should be able to remove our copies and import directly from OZ. When Solidity catches up and puts direct support
* for transient storage in the language, we should be able to get rid of this altogether.
*
* This only works on networks where EIP-1153 is supported.
*/
library TransientStorageHelpers {
using SlotDerivation for *;
using StorageSlotExtension for *;
/// @notice An index is out of bounds on an array operation (e.g., at).
error TransientIndexOutOfBounds();
// Calculate the slot for a transient storage variable.
function calculateSlot(string memory domain, string memory varName) internal pure returns (bytes32) {
return
keccak256(
abi.encode(uint256(keccak256(abi.encodePacked("balancer-labs.v3.storage.", domain, ".", varName))) - 1)
) & ~bytes32(uint256(0xff));
}
/***************************************************************************
Mappings
***************************************************************************/
function tGet(TokenDeltaMappingSlotType slot, IERC20 k1) internal view returns (int256) {
return TokenDeltaMappingSlotType.unwrap(slot).deriveMapping(address(k1)).asInt256().tload();
}
function tSet(TokenDeltaMappingSlotType slot, IERC20 k1, int256 value) internal {
TokenDeltaMappingSlotType.unwrap(slot).deriveMapping(address(k1)).asInt256().tstore(value);
}
function tGet(AddressToUintMappingSlot slot, address key) internal view returns (uint256) {
return AddressToUintMappingSlot.unwrap(slot).deriveMapping(key).asUint256().tload();
}
function tSet(AddressToUintMappingSlot slot, address key, uint256 value) internal {
AddressToUintMappingSlot.unwrap(slot).deriveMapping(key).asUint256().tstore(value);
}
function tGet(
UintToAddressToBooleanMappingSlot slot,
uint256 uintKey,
address addressKey
) internal view returns (bool) {
return
UintToAddressToBooleanMappingSlot
.unwrap(slot)
.deriveMapping(uintKey)
.deriveMapping(addressKey)
.asBoolean()
.tload();
}
function tSet(UintToAddressToBooleanMappingSlot slot, uint256 uintKey, address addressKey, bool value) internal {
UintToAddressToBooleanMappingSlot
.unwrap(slot)
.deriveMapping(uintKey)
.deriveMapping(addressKey)
.asBoolean()
.tstore(value);
}
// Implement the common "+=" operation: map[key] += value.
function tAdd(AddressToUintMappingSlot slot, address key, uint256 value) internal {
AddressToUintMappingSlot.unwrap(slot).deriveMapping(key).asUint256().tstore(tGet(slot, key) + value);
}
function tSub(AddressToUintMappingSlot slot, address key, uint256 value) internal {
AddressToUintMappingSlot.unwrap(slot).deriveMapping(key).asUint256().tstore(tGet(slot, key) - value);
}
/***************************************************************************
Arrays
***************************************************************************/
function tLength(AddressArraySlotType slot) internal view returns (uint256) {
return AddressArraySlotType.unwrap(slot).asUint256().tload();
}
function tAt(AddressArraySlotType slot, uint256 index) internal view returns (address) {
_ensureIndexWithinBounds(slot, index);
return AddressArraySlotType.unwrap(slot).deriveArray().offset(index).asAddress().tload();
}
function tSet(AddressArraySlotType slot, uint256 index, address value) internal {
_ensureIndexWithinBounds(slot, index);
AddressArraySlotType.unwrap(slot).deriveArray().offset(index).asAddress().tstore(value);
}
function _ensureIndexWithinBounds(AddressArraySlotType slot, uint256 index) private view {
uint256 length = AddressArraySlotType.unwrap(slot).asUint256().tload();
if (index >= length) {
revert TransientIndexOutOfBounds();
}
}
function tUncheckedAt(AddressArraySlotType slot, uint256 index) internal view returns (address) {
return AddressArraySlotType.unwrap(slot).deriveArray().offset(index).asAddress().tload();
}
function tUncheckedSet(AddressArraySlotType slot, uint256 index, address value) internal {
AddressArraySlotType.unwrap(slot).deriveArray().offset(index).asAddress().tstore(value);
}
function tPush(AddressArraySlotType slot, address value) internal {
// Store the value at offset corresponding to the current length.
uint256 length = AddressArraySlotType.unwrap(slot).asUint256().tload();
AddressArraySlotType.unwrap(slot).deriveArray().offset(length).asAddress().tstore(value);
// Update current length to consider the new value.
AddressArraySlotType.unwrap(slot).asUint256().tstore(length + 1);
}
function tPop(AddressArraySlotType slot) internal returns (address value) {
uint256 lastElementIndex = AddressArraySlotType.unwrap(slot).asUint256().tload() - 1;
// Update length to last element. When the index is 0, the slot that holds the length is cleared out.
AddressArraySlotType.unwrap(slot).asUint256().tstore(lastElementIndex);
StorageSlotExtension.AddressSlotType lastElementSlot = AddressArraySlotType
.unwrap(slot)
.deriveArray()
.offset(lastElementIndex)
.asAddress();
// Return last element.
value = lastElementSlot.tload();
// Clear value in temporary storage.
lastElementSlot.tstore(address(0));
}
/***************************************************************************
Uint256 Values
***************************************************************************/
function tIncrement(StorageSlotExtension.Uint256SlotType slot) internal {
slot.tstore(slot.tload() + 1);
}
function tDecrement(StorageSlotExtension.Uint256SlotType slot) internal {
slot.tstore(slot.tload() - 1);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
/// @notice Contract that shares the modifier `onlyVault`.
contract VaultGuard {
IVault internal immutable _vault;
constructor(IVault vault) {
_vault = vault;
}
modifier onlyVault() {
_ensureOnlyVault();
_;
}
function _ensureOnlyVault() private view {
if (msg.sender != address(_vault)) {
revert IVaultErrors.SenderIsNotVault(msg.sender);
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IRateProvider } from "../solidity-utils/helpers/IRateProvider.sol";
/**
* @notice Represents a pool's liquidity management configuration.
* @param disableUnbalancedLiquidity If set, liquidity can only be added or removed proportionally
* @param enableAddLiquidityCustom If set, the pool has implemented `onAddLiquidityCustom`
* @param enableRemoveLiquidityCustom If set, the pool has implemented `onRemoveLiquidityCustom`
* @param enableDonation If set, the pool will not revert if liquidity is added with AddLiquidityKind.DONATION
*/
struct LiquidityManagement {
bool disableUnbalancedLiquidity;
bool enableAddLiquidityCustom;
bool enableRemoveLiquidityCustom;
bool enableDonation;
}
// @notice Custom type to store the entire configuration of the pool.
type PoolConfigBits is bytes32;
/**
* @notice Represents a pool's configuration (hooks configuration are separated in another struct).
* @param liquidityManagement Flags related to adding/removing liquidity
* @param staticSwapFeePercentage The pool's native swap fee
* @param aggregateSwapFeePercentage The total swap fee charged, including protocol and pool creator components
* @param aggregateYieldFeePercentage The total swap fee charged, including protocol and pool creator components
* @param tokenDecimalDiffs Compressed storage of the token decimals of each pool token
* @param pauseWindowEndTime Timestamp after which the pool cannot be paused
* @param isPoolRegistered If true, the pool has been registered with the Vault
* @param isPoolInitialized If true, the pool has been initialized with liquidity, and is available for trading
* @param isPoolPaused If true, the pool has been paused (by governance or the pauseManager)
* @param isPoolInRecoveryMode If true, the pool has been placed in recovery mode, enabling recovery mode withdrawals
*/
struct PoolConfig {
LiquidityManagement liquidityManagement;
uint256 staticSwapFeePercentage;
uint256 aggregateSwapFeePercentage;
uint256 aggregateYieldFeePercentage;
uint40 tokenDecimalDiffs;
uint32 pauseWindowEndTime;
bool isPoolRegistered;
bool isPoolInitialized;
bool isPoolPaused;
bool isPoolInRecoveryMode;
}
/**
* @notice The flag portion of the `HooksConfig`.
* @dev `enableHookAdjustedAmounts` must be true for all contracts that modify the `amountCalculated`
* in after hooks. Otherwise, the Vault will ignore any "hookAdjusted" amounts. Setting any "shouldCall"
* flags to true will cause the Vault to call the corresponding hook during operations.
*/
struct HookFlags {
bool enableHookAdjustedAmounts;
bool shouldCallBeforeInitialize;
bool shouldCallAfterInitialize;
bool shouldCallComputeDynamicSwapFee;
bool shouldCallBeforeSwap;
bool shouldCallAfterSwap;
bool shouldCallBeforeAddLiquidity;
bool shouldCallAfterAddLiquidity;
bool shouldCallBeforeRemoveLiquidity;
bool shouldCallAfterRemoveLiquidity;
}
/// @notice Represents a hook contract configuration for a pool (HookFlags + hooksContract address).
struct HooksConfig {
bool enableHookAdjustedAmounts;
bool shouldCallBeforeInitialize;
bool shouldCallAfterInitialize;
bool shouldCallComputeDynamicSwapFee;
bool shouldCallBeforeSwap;
bool shouldCallAfterSwap;
bool shouldCallBeforeAddLiquidity;
bool shouldCallAfterAddLiquidity;
bool shouldCallBeforeRemoveLiquidity;
bool shouldCallAfterRemoveLiquidity;
address hooksContract;
}
/**
* @notice Represents temporary state used during a swap operation.
* @param indexIn The zero-based index of tokenIn
* @param indexOut The zero-based index of tokenOut
* @param amountGivenScaled18 The amountGiven (i.e., tokenIn for ExactIn), adjusted for token decimals
* @param swapFeePercentage The swap fee to be applied (might be static or dynamic)
*/
struct SwapState {
uint256 indexIn;
uint256 indexOut;
uint256 amountGivenScaled18;
uint256 swapFeePercentage;
}
/**
* @notice Represents the Vault's configuration.
* @param isQueryDisabled If set to true, disables query functionality of the Vault. Can be modified by governance
* @param isVaultPaused If set to true, swaps and add/remove liquidity operations are halted
* @param areBuffersPaused If set to true, the Vault wrap/unwrap primitives associated with buffers will be disabled
*/
struct VaultState {
bool isQueryDisabled;
bool isVaultPaused;
bool areBuffersPaused;
}
/**
* @notice Represents the accounts holding certain roles for a given pool. This is passed in on pool registration.
* @param pauseManager Account empowered to pause/unpause the pool (note that governance can always pause a pool)
* @param swapFeeManager Account empowered to set static swap fees for a pool (or 0 to delegate to governance)
* @param poolCreator Account empowered to set the pool creator fee (or 0 if all fees go to the protocol and LPs)
*/
struct PoolRoleAccounts {
address pauseManager;
address swapFeeManager;
address poolCreator;
}
/*******************************************************************************
Tokens
*******************************************************************************/
// Note that the following tokens are unsupported by the Vault. This list is not meant to be exhaustive, but covers
// many common types of tokens that will not work with the Vault architecture. (See https://github.com/d-xo/weird-erc20
// for examples of token features that are problematic for many protocols.)
//
// * Rebasing tokens (e.g., aDAI). The Vault keeps track of token balances in its internal accounting; any token whose
// balance changes asynchronously (i.e., outside a swap or liquidity operation), would get out-of-sync with this
// internal accounting. This category would also include "airdrop" tokens, whose balances can change unexpectedly.
//
// * Double entrypoint (e.g., old Synthetix tokens, now fixed). These could likewise bypass internal accounting by
// registering the token under one address, then accessing it through another. This is especially troublesome
// in v3, with the introduction of ERC4626 buffers.
//
// * Fee on transfer (e.g., PAXG). The Vault issues credits and debits according to given and calculated token amounts,
// and settlement assumes that the send/receive transfer functions transfer exactly the given number of tokens.
// If this is not the case, transactions will not settle. Unlike with the other types, which are fundamentally
// incompatible, it would be possible to design a Router to handle this - but we didn't try it. In any case, it's
// not supported in the current Routers.
//
// * Tokens with more than 18 decimals (e.g., YAM-V2). The Vault handles token scaling: i.e., handling I/O for
// amounts in native token decimals, but doing calculations with full 18-decimal precision. This requires reading
// and storing the decimals for each token. Since virtually all tokens are 18 or fewer decimals, and we have limited
// storage space, 18 was a reasonable maximum. Unlike the other types, this is enforceable by the Vault. Attempting
// to register such tokens will revert with `InvalidTokenDecimals`. Of course, we must also be able to read the token
// decimals, so the Vault only supports tokens that implement `IERC20Metadata.decimals`, and return a value less than
// or equal to 18.
//
// * Token decimals are checked and stored only once, on registration. Valid tokens store their decimals as immutable
// variables or constants. Malicious tokens that don't respect this basic property would not work anywhere in DeFi.
//
// These types of tokens are supported but discouraged, as they don't tend to play well with AMMs generally.
//
// * Very low-decimal tokens (e.g., GUSD). The Vault has been extensively tested with 6-decimal tokens (e.g., USDC),
// but going much below that may lead to unanticipated effects due to precision loss, especially with smaller trade
// values.
//
// * Revert on zero value approval/transfer. The Vault has been tested against these, but peripheral contracts, such
// as hooks, might not have been designed with this in mind.
//
// * Other types from "weird-erc20," such as upgradeable, pausable, or tokens with blocklists. We have seen cases
// where a token upgrade fails, "bricking" the token - and many operations on pools containing that token. Any
// sort of "permissioned" token that can make transfers fail can cause operations on pools containing them to
// revert. Even Recovery Mode cannot help then, as it does a proportional withdrawal of all tokens. If one of
// them is bricked, the whole operation will revert. Since v3 does not have "internal balances" like v2, there
// is no recourse.
//
// Of course, many tokens in common use have some of these "features" (especially centralized stable coins), so
// we have to support them anyway. Working with common centralized tokens is a risk common to all of DeFi.
/**
* @notice Token types supported by the Vault.
* @dev In general, pools may contain any combination of these tokens.
*
* STANDARD tokens (e.g., BAL, WETH) have no rate provider.
* WITH_RATE tokens (e.g., wstETH) require a rate provider. These may be tokens like wstETH, which need to be wrapped
* because the underlying stETH token is rebasing, and such tokens are unsupported by the Vault. They may also be
* tokens like sEUR, which track an underlying asset, but are not yield-bearing. Finally, this encompasses
* yield-bearing ERC4626 tokens, which can be used to facilitate swaps without requiring wrapping or unwrapping
* in most cases. The `paysYieldFees` flag can be used to indicate whether a token is yield-bearing (e.g., waDAI),
* not yield-bearing (e.g., sEUR), or yield-bearing but exempt from fees (e.g., in certain nested pools, where
* yield fees are charged elsewhere).
*
* NB: STANDARD must always be the first enum element, so that newly initialized data structures default to Standard.
*/
enum TokenType {
STANDARD,
WITH_RATE
}
/**
* @notice Encapsulate the data required for the Vault to support a token of the given type.
* @dev For STANDARD tokens, the rate provider address must be 0, and paysYieldFees must be false. All WITH_RATE tokens
* need a rate provider, and may or may not be yield-bearing.
*
* At registration time, it is useful to include the token address along with the token parameters in the structure
* passed to `registerPool`, as the alternative would be parallel arrays, which would be error prone and require
* validation checks. `TokenConfig` is only used for registration, and is never put into storage (see `TokenInfo`).
*
* @param token The token address
* @param tokenType The token type (see the enum for supported types)
* @param rateProvider The rate provider for a token (see further documentation above)
* @param paysYieldFees Flag indicating whether yield fees should be charged on this token
*/
struct TokenConfig {
IERC20 token;
TokenType tokenType;
IRateProvider rateProvider;
bool paysYieldFees;
}
/**
* @notice This data structure is stored in `_poolTokenInfo`, a nested mapping from pool -> (token -> TokenInfo).
* @dev Since the token is already the key of the nested mapping, it would be redundant (and an extra SLOAD) to store
* it again in the struct. When we construct PoolData, the tokens are separated into their own array.
*
* @param tokenType The token type (see the enum for supported types)
* @param rateProvider The rate provider for a token (see further documentation above)
* @param paysYieldFees Flag indicating whether yield fees should be charged on this token
*/
struct TokenInfo {
TokenType tokenType;
IRateProvider rateProvider;
bool paysYieldFees;
}
/**
* @notice Data structure used to represent the current pool state in memory
* @param poolConfigBits Custom type to store the entire configuration of the pool.
* @param tokens Pool tokens, sorted in token registration order
* @param tokenInfo Configuration data for each token, sorted in token registration order
* @param balancesRaw Token balances in native decimals
* @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates
* @param tokenRates 18-decimal FP values for rate tokens (e.g., yield-bearing), or FP(1) for standard tokens
* @param decimalScalingFactors Conversion factor used to adjust for token decimals for uniform precision in
* calculations. It is 1e18 (FP 1) for 18-decimal tokens
*/
struct PoolData {
PoolConfigBits poolConfigBits;
IERC20[] tokens;
TokenInfo[] tokenInfo;
uint256[] balancesRaw;
uint256[] balancesLiveScaled18;
uint256[] tokenRates;
uint256[] decimalScalingFactors;
}
enum Rounding {
ROUND_UP,
ROUND_DOWN
}
/*******************************************************************************
Swaps
*******************************************************************************/
enum SwapKind {
EXACT_IN,
EXACT_OUT
}
// There are two "SwapParams" structs defined below. `VaultSwapParams` corresponds to the external swap API defined
// in the Router contracts, which uses explicit token addresses, the amount given and limit on the calculated amount
// expressed in native token decimals, and optional user data passed in from the caller.
//
// `PoolSwapParams` passes some of this information through (kind, userData), but "translates" the parameters to fit
// the internal swap API used by `IBasePool`. It scales amounts to full 18-decimal precision, adds the token balances,
// converts the raw token addresses to indices, and adds the address of the Router originating the request. It does
// not need the limit, since this is checked at the Router level.
/**
* @notice Data passed into primary Vault `swap` operations.
* @param kind Type of swap (Exact In or Exact Out)
* @param pool The pool with the tokens being swapped
* @param tokenIn The token entering the Vault (balance increases)
* @param tokenOut The token leaving the Vault (balance decreases)
* @param amountGivenRaw Amount specified for tokenIn or tokenOut (depending on the type of swap)
* @param limitRaw Minimum or maximum value of the calculated amount (depending on the type of swap)
* @param userData Additional (optional) user data
*/
struct VaultSwapParams {
SwapKind kind;
address pool;
IERC20 tokenIn;
IERC20 tokenOut;
uint256 amountGivenRaw;
uint256 limitRaw;
bytes userData;
}
/**
* @notice Data for a swap operation, used by contracts implementing `IBasePool`.
* @param kind Type of swap (exact in or exact out)
* @param amountGivenScaled18 Amount given based on kind of the swap (e.g., tokenIn for EXACT_IN)
* @param balancesScaled18 Current pool balances
* @param indexIn Index of tokenIn
* @param indexOut Index of tokenOut
* @param router The address (usually a router contract) that initiated a swap operation on the Vault
* @param userData Additional (optional) data required for the swap
*/
struct PoolSwapParams {
SwapKind kind;
uint256 amountGivenScaled18;
uint256[] balancesScaled18;
uint256 indexIn;
uint256 indexOut;
address router;
bytes userData;
}
/**
* @notice Data for the hook after a swap operation.
* @param kind Type of swap (exact in or exact out)
* @param tokenIn Token to be swapped from
* @param tokenOut Token to be swapped to
* @param amountInScaled18 Amount of tokenIn (entering the Vault)
* @param amountOutScaled18 Amount of tokenOut (leaving the Vault)
* @param tokenInBalanceScaled18 Updated (after swap) balance of tokenIn
* @param tokenOutBalanceScaled18 Updated (after swap) balance of tokenOut
* @param amountCalculatedScaled18 Token amount calculated by the swap
* @param amountCalculatedRaw Token amount calculated by the swap
* @param router The address (usually a router contract) that initiated a swap operation on the Vault
* @param pool Pool address
* @param userData Additional (optional) data required for the swap
*/
struct AfterSwapParams {
SwapKind kind;
IERC20 tokenIn;
IERC20 tokenOut;
uint256 amountInScaled18;
uint256 amountOutScaled18;
uint256 tokenInBalanceScaled18;
uint256 tokenOutBalanceScaled18;
uint256 amountCalculatedScaled18;
uint256 amountCalculatedRaw;
address router;
address pool;
bytes userData;
}
/*******************************************************************************
Add liquidity
*******************************************************************************/
enum AddLiquidityKind {
PROPORTIONAL,
UNBALANCED,
SINGLE_TOKEN_EXACT_OUT,
DONATION,
CUSTOM
}
/**
* @notice Data for an add liquidity operation.
* @param pool Address of the pool
* @param to Address of user to mint to
* @param maxAmountsIn Maximum amounts of input tokens
* @param minBptAmountOut Minimum amount of output pool tokens
* @param kind Add liquidity kind
* @param userData Optional user data
*/
struct AddLiquidityParams {
address pool;
address to;
uint256[] maxAmountsIn;
uint256 minBptAmountOut;
AddLiquidityKind kind;
bytes userData;
}
/*******************************************************************************
Remove liquidity
*******************************************************************************/
enum RemoveLiquidityKind {
PROPORTIONAL,
SINGLE_TOKEN_EXACT_IN,
SINGLE_TOKEN_EXACT_OUT,
CUSTOM
}
/**
* @notice Data for an remove liquidity operation.
* @param pool Address of the pool
* @param from Address of user to burn from
* @param maxBptAmountIn Maximum amount of input pool tokens
* @param minAmountsOut Minimum amounts of output tokens
* @param kind Remove liquidity kind
* @param userData Optional user data
*/
struct RemoveLiquidityParams {
address pool;
address from;
uint256 maxBptAmountIn;
uint256[] minAmountsOut;
RemoveLiquidityKind kind;
bytes userData;
}
/*******************************************************************************
Remove liquidity
*******************************************************************************/
enum WrappingDirection {
WRAP,
UNWRAP
}
/**
* @notice Data for a wrap/unwrap operation.
* @param kind Type of swap (Exact In or Exact Out)
* @param direction Direction of the wrapping operation (Wrap or Unwrap)
* @param wrappedToken Wrapped token, compatible with interface ERC4626
* @param amountGivenRaw Amount specified for tokenIn or tokenOut (depends on the type of swap and wrapping direction)
* @param limitRaw Minimum or maximum amount specified for the other token (depends on the type of swap and wrapping
* direction)
*/
struct BufferWrapOrUnwrapParams {
SwapKind kind;
WrappingDirection direction;
IERC4626 wrappedToken;
uint256 amountGivenRaw;
uint256 limitRaw;
}
// Protocol Fees are 24-bit values. We transform them by multiplying by 1e11, so that they can be set to any value
// between 0% and 100% (step 0.00001%). Protocol and pool creator fees are set in the `ProtocolFeeController`, and
// ensure both constituent and aggregate fees do not exceed this precision.
uint256 constant FEE_BITLENGTH = 24;
uint256 constant FEE_SCALING_FACTOR = 1e11;
// Used to ensure the safety of fee-related math (e.g., pools or hooks don't set it greater than 100%).
// This value should work for practical purposes and is well within the max precision requirements.
uint256 constant MAX_FEE_PERCENTAGE = 99.9999e16; // 99.9999%
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { IVersion } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IVersion.sol";
/**
* @notice Retrieves a contract's version from storage.
* @dev The version is set at deployment time and cannot be changed. It would be immutable, but immutable strings
* are not yet supported.
*
* Contracts like factories and pools should have versions. These typically take the form of JSON strings containing
* detailed information about the deployment. For instance:
*
* `{name: 'ChildChainGaugeFactory', version: 2, deployment: '20230316-child-chain-gauge-factory-v2'}`
*/
contract Version is IVersion {
string private _version;
constructor(string memory version_) {
_setVersion(version_);
}
/**
* @notice Getter for the version.
* @return version The stored contract version
*/
function version() external view returns (string memory) {
return _version;
}
/// @dev Internal setter that allows this contract to be used in proxies.
function _setVersion(string memory newVersion) internal {
_version = newVersion;
}
}
{
"compilationTarget": {
"contracts/BatchRouter.sol": "BatchRouter"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"details": {
"constantOptimizer": true,
"cse": true,
"deduplicate": true,
"inliner": true,
"jumpdestRemover": true,
"orderLiterals": true,
"peephole": true,
"simpleCounterForLoopUncheckedIncrement": true,
"yul": true,
"yulDetails": {
"optimizerSteps": "dhfoDgvulfnTUtnIf [ xa[r]EscLM cCTUtTOntnfDIul Lcul Vcul [j] Tpeul xa[rul] xa[r]cL gvif CTUca[r]LSsTFOtfDnca[r]Iulc ] jmul[jul] VcTOcul jmul : fDnTOcmu",
"stackAllocation": true
}
},
"runs": 9999
},
"remappings": [],
"viaIR": true
}
[{"inputs":[{"internalType":"contract IVault","name":"vault","type":"address"},{"internalType":"contract IWETH","name":"weth","type":"address"},{"internalType":"contract IPermit2","name":"permit2","type":"address"},{"internalType":"string","name":"routerVersion","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"ErrorSelectorNotFound","type":"error"},{"inputs":[],"name":"EthTransfer","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InputLengthMismatch","type":"error"},{"inputs":[],"name":"InsufficientEth","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"uint8","name":"bits","type":"uint8"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeCastOverflowedUintDowncast","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"SenderIsNotVault","type":"error"},{"inputs":[],"name":"SwapDeadline","type":"error"},{"inputs":[],"name":"TransientIndexOutOfBounds","type":"error"},{"inputs":[],"name":"getSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct IRouterCommon.PermitApproval[]","name":"permitBatch","type":"tuple[]"},{"internalType":"bytes[]","name":"permitSignatures","type":"bytes[]"},{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint160","name":"amount","type":"uint160"},{"internalType":"uint48","name":"expiration","type":"uint48"},{"internalType":"uint48","name":"nonce","type":"uint48"}],"internalType":"struct IAllowanceTransfer.PermitDetails[]","name":"details","type":"tuple[]"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"sigDeadline","type":"uint256"}],"internalType":"struct IAllowanceTransfer.PermitBatch","name":"permit2Batch","type":"tuple"},{"internalType":"bytes","name":"permit2Signature","type":"bytes"},{"internalType":"bytes[]","name":"multicallData","type":"bytes[]"}],"name":"permitBatchAndCall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"exactAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountIn[]","name":"paths","type":"tuple[]"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"userData","type":"bytes"}],"name":"querySwapExactIn","outputs":[{"internalType":"uint256[]","name":"pathAmountsOut","type":"uint256[]"},{"internalType":"address[]","name":"tokensOut","type":"address[]"},{"internalType":"uint256[]","name":"amountsOut","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"exactAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountIn[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"wethIsEth","type":"bool"},{"internalType":"bytes","name":"userData","type":"bytes"}],"internalType":"struct IBatchRouter.SwapExactInHookParams","name":"params","type":"tuple"}],"name":"querySwapExactInHook","outputs":[{"internalType":"uint256[]","name":"pathAmountsOut","type":"uint256[]"},{"internalType":"address[]","name":"tokensOut","type":"address[]"},{"internalType":"uint256[]","name":"amountsOut","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"},{"internalType":"uint256","name":"exactAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountOut[]","name":"paths","type":"tuple[]"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"userData","type":"bytes"}],"name":"querySwapExactOut","outputs":[{"internalType":"uint256[]","name":"pathAmountsIn","type":"uint256[]"},{"internalType":"address[]","name":"tokensIn","type":"address[]"},{"internalType":"uint256[]","name":"amountsIn","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"},{"internalType":"uint256","name":"exactAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountOut[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"wethIsEth","type":"bool"},{"internalType":"bytes","name":"userData","type":"bytes"}],"internalType":"struct IBatchRouter.SwapExactOutHookParams","name":"params","type":"tuple"}],"name":"querySwapExactOutHook","outputs":[{"internalType":"uint256[]","name":"pathAmountsIn","type":"uint256[]"},{"internalType":"address[]","name":"tokensIn","type":"address[]"},{"internalType":"uint256[]","name":"amountsIn","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"exactAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountIn[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"wethIsEth","type":"bool"},{"internalType":"bytes","name":"userData","type":"bytes"}],"name":"swapExactIn","outputs":[{"internalType":"uint256[]","name":"pathAmountsOut","type":"uint256[]"},{"internalType":"address[]","name":"tokensOut","type":"address[]"},{"internalType":"uint256[]","name":"amountsOut","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"exactAmountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountIn[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"wethIsEth","type":"bool"},{"internalType":"bytes","name":"userData","type":"bytes"}],"internalType":"struct IBatchRouter.SwapExactInHookParams","name":"params","type":"tuple"}],"name":"swapExactInHook","outputs":[{"internalType":"uint256[]","name":"pathAmountsOut","type":"uint256[]"},{"internalType":"address[]","name":"tokensOut","type":"address[]"},{"internalType":"uint256[]","name":"amountsOut","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"},{"internalType":"uint256","name":"exactAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountOut[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"wethIsEth","type":"bool"},{"internalType":"bytes","name":"userData","type":"bytes"}],"name":"swapExactOut","outputs":[{"internalType":"uint256[]","name":"pathAmountsIn","type":"uint256[]"},{"internalType":"address[]","name":"tokensIn","type":"address[]"},{"internalType":"uint256[]","name":"amountsIn","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"bool","name":"isBuffer","type":"bool"}],"internalType":"struct IBatchRouter.SwapPathStep[]","name":"steps","type":"tuple[]"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"},{"internalType":"uint256","name":"exactAmountOut","type":"uint256"}],"internalType":"struct IBatchRouter.SwapPathExactAmountOut[]","name":"paths","type":"tuple[]"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"wethIsEth","type":"bool"},{"internalType":"bytes","name":"userData","type":"bytes"}],"internalType":"struct IBatchRouter.SwapExactOutHookParams","name":"params","type":"tuple"}],"name":"swapExactOutHook","outputs":[{"internalType":"uint256[]","name":"pathAmountsIn","type":"uint256[]"},{"internalType":"address[]","name":"tokensIn","type":"address[]"},{"internalType":"uint256[]","name":"amountsIn","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]