773 lines
38 KiB
Solidity
773 lines
38 KiB
Solidity
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import "../solidity-utils/openzeppelin/IERC20.sol";
|
|
import "../solidity-utils/helpers/IAuthentication.sol";
|
|
import "../solidity-utils/helpers/ISignaturesValidator.sol";
|
|
import "../solidity-utils/helpers/ITemporarilyPausable.sol";
|
|
import "../solidity-utils/misc/IWETH.sol";
|
|
|
|
import "./IAsset.sol";
|
|
import "./IAuthorizer.sol";
|
|
import "./IFlashLoanRecipient.sol";
|
|
import "./IProtocolFeesCollector.sol";
|
|
|
|
pragma solidity >=0.7.0 <0.9.0;
|
|
|
|
/**
|
|
* @dev Full external interface for the Vault core contract - no external or public methods exist in the contract that
|
|
* don't override one of these declarations.
|
|
*/
|
|
interface IVault is ISignaturesValidator, ITemporarilyPausable, IAuthentication {
|
|
// Generalities about the Vault:
|
|
//
|
|
// - Whenever documentation refers to 'tokens', it strictly refers to ERC20-compliant token contracts. Tokens are
|
|
// transferred out of the Vault by calling the `IERC20.transfer` function, and transferred in by calling
|
|
// `IERC20.transferFrom`. In these cases, the sender must have previously allowed the Vault to use their tokens by
|
|
// calling `IERC20.approve`. The only deviation from the ERC20 standard that is supported is functions not returning
|
|
// a boolean value: in these scenarios, a non-reverting call is assumed to be successful.
|
|
//
|
|
// - All non-view functions in the Vault are non-reentrant: calling them while another one is mid-execution (e.g.
|
|
// while execution control is transferred to a token contract during a swap) will result in a revert. View
|
|
// functions can be called in a re-reentrant way, but doing so might cause them to return inconsistent results.
|
|
// Contracts calling view functions in the Vault must make sure the Vault has not already been entered.
|
|
//
|
|
// - View functions revert if referring to either unregistered Pools, or unregistered tokens for registered Pools.
|
|
|
|
// Authorizer
|
|
//
|
|
// Some system actions are permissioned, like setting and collecting protocol fees. This permissioning system exists
|
|
// outside of the Vault in the Authorizer contract: the Vault simply calls the Authorizer to check if the caller
|
|
// can perform a given action.
|
|
|
|
/**
|
|
* @dev Returns the Vault's Authorizer.
|
|
*/
|
|
function getAuthorizer() external view returns (IAuthorizer);
|
|
|
|
/**
|
|
* @dev Sets a new Authorizer for the Vault. The caller must be allowed by the current Authorizer to do this.
|
|
*
|
|
* Emits an `AuthorizerChanged` event.
|
|
*/
|
|
function setAuthorizer(IAuthorizer newAuthorizer) external;
|
|
|
|
/**
|
|
* @dev Emitted when a new authorizer is set by `setAuthorizer`.
|
|
*/
|
|
event AuthorizerChanged(IAuthorizer indexed newAuthorizer);
|
|
|
|
// Relayers
|
|
//
|
|
// Additionally, it is possible for an account to perform certain actions on behalf of another one, using their
|
|
// Vault ERC20 allowance and Internal Balance. These accounts are said to be 'relayers' for these Vault functions,
|
|
// and are expected to be smart contracts with sound authentication mechanisms. For an account to be able to wield
|
|
// this power, two things must occur:
|
|
// - The Authorizer must grant the account the permission to be a relayer for the relevant Vault function. This
|
|
// means that Balancer governance must approve each individual contract to act as a relayer for the intended
|
|
// functions.
|
|
// - Each user must approve the relayer to act on their behalf.
|
|
// This double protection means users cannot be tricked into approving malicious relayers (because they will not
|
|
// have been allowed by the Authorizer via governance), nor can malicious relayers approved by a compromised
|
|
// Authorizer or governance drain user funds, since they would also need to be approved by each individual user.
|
|
|
|
/**
|
|
* @dev Returns true if `user` has approved `relayer` to act as a relayer for them.
|
|
*/
|
|
function hasApprovedRelayer(address user, address relayer) external view returns (bool);
|
|
|
|
/**
|
|
* @dev Allows `relayer` to act as a relayer for `sender` if `approved` is true, and disallows it otherwise.
|
|
*
|
|
* Emits a `RelayerApprovalChanged` event.
|
|
*/
|
|
function setRelayerApproval(
|
|
address sender,
|
|
address relayer,
|
|
bool approved
|
|
) external;
|
|
|
|
/**
|
|
* @dev Emitted every time a relayer is approved or disapproved by `setRelayerApproval`.
|
|
*/
|
|
event RelayerApprovalChanged(address indexed relayer, address indexed sender, bool approved);
|
|
|
|
// Internal Balance
|
|
//
|
|
// Users can deposit tokens into the Vault, where they are allocated to their Internal Balance, and later
|
|
// transferred or withdrawn. It can also be used as a source of tokens when joining Pools, as a destination
|
|
// when exiting them, and as either when performing swaps. This usage of Internal Balance results in greatly reduced
|
|
// gas costs when compared to relying on plain ERC20 transfers, leading to large savings for frequent users.
|
|
//
|
|
// Internal Balance management features batching, which means a single contract call can be used to perform multiple
|
|
// operations of different kinds, with different senders and recipients, at once.
|
|
|
|
/**
|
|
* @dev Returns `user`'s Internal Balance for a set of tokens.
|
|
*/
|
|
function getInternalBalance(address user, IERC20[] memory tokens) external view returns (uint256[] memory);
|
|
|
|
/**
|
|
* @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)
|
|
* and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as
|
|
* it lets integrators reuse a user's Vault allowance.
|
|
*
|
|
* For each operation, if the caller is not `sender`, it must be an authorized relayer for them.
|
|
*/
|
|
function manageUserBalance(UserBalanceOp[] memory ops) external payable;
|
|
|
|
/**
|
|
* @dev Data for `manageUserBalance` operations, which include the possibility for ETH to be sent and received
|
|
without manual WETH wrapping or unwrapping.
|
|
*/
|
|
struct UserBalanceOp {
|
|
UserBalanceOpKind kind;
|
|
IAsset asset;
|
|
uint256 amount;
|
|
address sender;
|
|
address payable recipient;
|
|
}
|
|
|
|
// There are four possible operations in `manageUserBalance`:
|
|
//
|
|
// - DEPOSIT_INTERNAL
|
|
// Increases the Internal Balance of the `recipient` account by transferring tokens from the corresponding
|
|
// `sender`. The sender must have allowed the Vault to use their tokens via `IERC20.approve()`.
|
|
//
|
|
// ETH can be used by passing the ETH sentinel value as the asset and forwarding ETH in the call: it will be wrapped
|
|
// and deposited as WETH. Any ETH amount remaining will be sent back to the caller (not the sender, which is
|
|
// relevant for relayers).
|
|
//
|
|
// Emits an `InternalBalanceChanged` event.
|
|
//
|
|
//
|
|
// - WITHDRAW_INTERNAL
|
|
// Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient`.
|
|
//
|
|
// ETH can be used by passing the ETH sentinel value as the asset. This will deduct WETH instead, unwrap it and send
|
|
// it to the recipient as ETH.
|
|
//
|
|
// Emits an `InternalBalanceChanged` event.
|
|
//
|
|
//
|
|
// - TRANSFER_INTERNAL
|
|
// Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient`.
|
|
//
|
|
// Reverts if the ETH sentinel value is passed.
|
|
//
|
|
// Emits an `InternalBalanceChanged` event.
|
|
//
|
|
//
|
|
// - TRANSFER_EXTERNAL
|
|
// Transfers tokens from `sender` to `recipient`, using the Vault's ERC20 allowance. This is typically used by
|
|
// relayers, as it lets them reuse a user's Vault allowance.
|
|
//
|
|
// Reverts if the ETH sentinel value is passed.
|
|
//
|
|
// Emits an `ExternalBalanceTransfer` event.
|
|
|
|
enum UserBalanceOpKind { DEPOSIT_INTERNAL, WITHDRAW_INTERNAL, TRANSFER_INTERNAL, TRANSFER_EXTERNAL }
|
|
|
|
/**
|
|
* @dev Emitted when a user's Internal Balance changes, either from calls to `manageUserBalance`, or through
|
|
* interacting with Pools using Internal Balance.
|
|
*
|
|
* Because Internal Balance works exclusively with ERC20 tokens, ETH deposits and withdrawals will use the WETH
|
|
* address.
|
|
*/
|
|
event InternalBalanceChanged(address indexed user, IERC20 indexed token, int256 delta);
|
|
|
|
/**
|
|
* @dev Emitted when a user's Vault ERC20 allowance is used by the Vault to transfer tokens to an external account.
|
|
*/
|
|
event ExternalBalanceTransfer(IERC20 indexed token, address indexed sender, address recipient, uint256 amount);
|
|
|
|
// Pools
|
|
//
|
|
// There are three specialization settings for Pools, which allow for cheaper swaps at the cost of reduced
|
|
// functionality:
|
|
//
|
|
// - General: no specialization, suited for all Pools. IGeneralPool is used for swap request callbacks, passing the
|
|
// balance of all tokens in the Pool. These Pools have the largest swap costs (because of the extra storage reads),
|
|
// which increase with the number of registered tokens.
|
|
//
|
|
// - Minimal Swap Info: IMinimalSwapInfoPool is used instead of IGeneralPool, which saves gas by only passing the
|
|
// balance of the two tokens involved in the swap. This is suitable for some pricing algorithms, like the weighted
|
|
// constant product one popularized by Balancer V1. Swap costs are smaller compared to general Pools, and are
|
|
// independent of the number of registered tokens.
|
|
//
|
|
// - Two Token: only allows two tokens to be registered. This achieves the lowest possible swap gas cost. Like
|
|
// minimal swap info Pools, these are called via IMinimalSwapInfoPool.
|
|
|
|
enum PoolSpecialization { GENERAL, MINIMAL_SWAP_INFO, TWO_TOKEN }
|
|
|
|
/**
|
|
* @dev Registers the caller account as a Pool with a given specialization setting. Returns the Pool's ID, which
|
|
* is used in all Pool-related functions. Pools cannot be deregistered, nor can the Pool's specialization be
|
|
* changed.
|
|
*
|
|
* The caller is expected to be a smart contract that implements either `IGeneralPool` or `IMinimalSwapInfoPool`,
|
|
* depending on the chosen specialization setting. This contract is known as the Pool's contract.
|
|
*
|
|
* Note that the same contract may register itself as multiple Pools with unique Pool IDs, or in other words,
|
|
* multiple Pools may share the same contract.
|
|
*
|
|
* Emits a `PoolRegistered` event.
|
|
*/
|
|
function registerPool(PoolSpecialization specialization) external returns (bytes32);
|
|
|
|
/**
|
|
* @dev Emitted when a Pool is registered by calling `registerPool`.
|
|
*/
|
|
event PoolRegistered(bytes32 indexed poolId, address indexed poolAddress, PoolSpecialization specialization);
|
|
|
|
/**
|
|
* @dev Returns a Pool's contract address and specialization setting.
|
|
*/
|
|
function getPool(bytes32 poolId) external view returns (address, PoolSpecialization);
|
|
|
|
/**
|
|
* @dev Registers `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
|
|
*
|
|
* Pools can only interact with tokens they have registered. Users join a Pool by transferring registered tokens,
|
|
* exit by receiving registered tokens, and can only swap registered tokens.
|
|
*
|
|
* Each token can only be registered once. For Pools with the Two Token specialization, `tokens` must have a length
|
|
* of two, that is, both tokens must be registered in the same `registerTokens` call, and they must be sorted in
|
|
* ascending order.
|
|
*
|
|
* The `tokens` and `assetManagers` arrays must have the same length, and each entry in these indicates the Asset
|
|
* Manager for the corresponding token. Asset Managers can manage a Pool's tokens via `managePoolBalance`,
|
|
* depositing and withdrawing them directly, and can even set their balance to arbitrary amounts. They are therefore
|
|
* expected to be highly secured smart contracts with sound design principles, and the decision to register an
|
|
* Asset Manager should not be made lightly.
|
|
*
|
|
* Pools can choose not to assign an Asset Manager to a given token by passing in the zero address. Once an Asset
|
|
* Manager is set, it cannot be changed except by deregistering the associated token and registering again with a
|
|
* different Asset Manager.
|
|
*
|
|
* Emits a `TokensRegistered` event.
|
|
*/
|
|
function registerTokens(
|
|
bytes32 poolId,
|
|
IERC20[] memory tokens,
|
|
address[] memory assetManagers
|
|
) external;
|
|
|
|
/**
|
|
* @dev Emitted when a Pool registers tokens by calling `registerTokens`.
|
|
*/
|
|
event TokensRegistered(bytes32 indexed poolId, IERC20[] tokens, address[] assetManagers);
|
|
|
|
/**
|
|
* @dev Deregisters `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
|
|
*
|
|
* Only registered tokens (via `registerTokens`) can be deregistered. Additionally, they must have zero total
|
|
* balance. For Pools with the Two Token specialization, `tokens` must have a length of two, that is, both tokens
|
|
* must be deregistered in the same `deregisterTokens` call.
|
|
*
|
|
* A deregistered token can be re-registered later on, possibly with a different Asset Manager.
|
|
*
|
|
* Emits a `TokensDeregistered` event.
|
|
*/
|
|
function deregisterTokens(bytes32 poolId, IERC20[] memory tokens) external;
|
|
|
|
/**
|
|
* @dev Emitted when a Pool deregisters tokens by calling `deregisterTokens`.
|
|
*/
|
|
event TokensDeregistered(bytes32 indexed poolId, IERC20[] tokens);
|
|
|
|
/**
|
|
* @dev Returns detailed information for a Pool's registered token.
|
|
*
|
|
* `cash` is the number of tokens the Vault currently holds for the Pool. `managed` is the number of tokens
|
|
* withdrawn and held outside the Vault by the Pool's token Asset Manager. The Pool's total balance for `token`
|
|
* equals the sum of `cash` and `managed`.
|
|
*
|
|
* Internally, `cash` and `managed` are stored using 112 bits. No action can ever cause a Pool's token `cash`,
|
|
* `managed` or `total` balance to be greater than 2^112 - 1.
|
|
*
|
|
* `lastChangeBlock` is the number of the block in which `token`'s total balance was last modified (via either a
|
|
* join, exit, swap, or Asset Manager update). This value is useful to avoid so-called 'sandwich attacks', for
|
|
* example when developing price oracles. A change of zero (e.g. caused by a swap with amount zero) is considered a
|
|
* change for this purpose, and will update `lastChangeBlock`.
|
|
*
|
|
* `assetManager` is the Pool's token Asset Manager.
|
|
*/
|
|
function getPoolTokenInfo(bytes32 poolId, IERC20 token)
|
|
external
|
|
view
|
|
returns (
|
|
uint256 cash,
|
|
uint256 managed,
|
|
uint256 lastChangeBlock,
|
|
address assetManager
|
|
);
|
|
|
|
/**
|
|
* @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of
|
|
* the tokens' `balances` changed.
|
|
*
|
|
* The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all
|
|
* Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.
|
|
*
|
|
* If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same
|
|
* order as passed to `registerTokens`.
|
|
*
|
|
* Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are
|
|
* the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`
|
|
* instead.
|
|
*/
|
|
function getPoolTokens(bytes32 poolId)
|
|
external
|
|
view
|
|
returns (
|
|
IERC20[] memory tokens,
|
|
uint256[] memory balances,
|
|
uint256 lastChangeBlock
|
|
);
|
|
|
|
/**
|
|
* @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will
|
|
* trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized
|
|
* Pool shares.
|
|
*
|
|
* If the caller is not `sender`, it must be an authorized relayer for them.
|
|
*
|
|
* The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount
|
|
* to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces
|
|
* these maximums.
|
|
*
|
|
* If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable
|
|
* this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the
|
|
* WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent
|
|
* back to the caller (not the sender, which is important for relayers).
|
|
*
|
|
* `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
|
|
* interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be
|
|
* sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final
|
|
* `assets` array might not be sorted. Pools with no registered tokens cannot be joined.
|
|
*
|
|
* If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only
|
|
* be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be
|
|
* withdrawn from Internal Balance: attempting to do so will trigger a revert.
|
|
*
|
|
* This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement
|
|
* their own custom logic. This typically requires additional information from the user (such as the expected number
|
|
* of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed
|
|
* directly to the Pool's contract, as is `recipient`.
|
|
*
|
|
* Emits a `PoolBalanceChanged` event.
|
|
*/
|
|
function joinPool(
|
|
bytes32 poolId,
|
|
address sender,
|
|
address recipient,
|
|
JoinPoolRequest memory request
|
|
) external payable;
|
|
|
|
struct JoinPoolRequest {
|
|
IAsset[] assets;
|
|
uint256[] maxAmountsIn;
|
|
bytes userData;
|
|
bool fromInternalBalance;
|
|
}
|
|
|
|
/**
|
|
* @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will
|
|
* trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized
|
|
* Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see
|
|
* `getPoolTokenInfo`).
|
|
*
|
|
* If the caller is not `sender`, it must be an authorized relayer for them.
|
|
*
|
|
* The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum
|
|
* token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:
|
|
* it just enforces these minimums.
|
|
*
|
|
* If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To
|
|
* enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead
|
|
* of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.
|
|
*
|
|
* `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
|
|
* interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must
|
|
* be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the
|
|
* final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.
|
|
*
|
|
* If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,
|
|
* an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to
|
|
* do so will trigger a revert.
|
|
*
|
|
* `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the
|
|
* `tokens` array. This array must match the Pool's registered tokens.
|
|
*
|
|
* This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement
|
|
* their own custom logic. This typically requires additional information from the user (such as the expected number
|
|
* of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and
|
|
* passed directly to the Pool's contract.
|
|
*
|
|
* Emits a `PoolBalanceChanged` event.
|
|
*/
|
|
function exitPool(
|
|
bytes32 poolId,
|
|
address sender,
|
|
address payable recipient,
|
|
ExitPoolRequest memory request
|
|
) external;
|
|
|
|
struct ExitPoolRequest {
|
|
IAsset[] assets;
|
|
uint256[] minAmountsOut;
|
|
bytes userData;
|
|
bool toInternalBalance;
|
|
}
|
|
|
|
/**
|
|
* @dev Emitted when a user joins or exits a Pool by calling `joinPool` or `exitPool`, respectively.
|
|
*/
|
|
event PoolBalanceChanged(
|
|
bytes32 indexed poolId,
|
|
address indexed liquidityProvider,
|
|
IERC20[] tokens,
|
|
int256[] deltas,
|
|
uint256[] protocolFeeAmounts
|
|
);
|
|
|
|
enum PoolBalanceChangeKind { JOIN, EXIT }
|
|
|
|
// Swaps
|
|
//
|
|
// Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,
|
|
// they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be
|
|
// aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.
|
|
//
|
|
// The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.
|
|
// In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),
|
|
// and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').
|
|
// More complex swaps, such as one token in to multiple tokens out can be achieved by batching together
|
|
// individual swaps.
|
|
//
|
|
// There are two swap kinds:
|
|
// - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the
|
|
// `onSwap` hook) the amount of tokens out (to send to the recipient).
|
|
// - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines
|
|
// (via the `onSwap` hook) the amount of tokens in (to receive from the sender).
|
|
//
|
|
// Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with
|
|
// the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated
|
|
// tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended
|
|
// swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at
|
|
// the final intended token.
|
|
//
|
|
// In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal
|
|
// Balance) after all individual swaps have been completed, and the net token balance change computed. This makes
|
|
// certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost
|
|
// much less gas than they would otherwise.
|
|
//
|
|
// It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple
|
|
// Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only
|
|
// updating the Pool's internal accounting).
|
|
//
|
|
// To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token
|
|
// involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the
|
|
// minimum amount of tokens to receive (by passing a negative value) is specified.
|
|
//
|
|
// Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after
|
|
// this point in time (e.g. if the transaction failed to be included in a block promptly).
|
|
//
|
|
// If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do
|
|
// the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be
|
|
// passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the
|
|
// same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).
|
|
//
|
|
// Finally, Internal Balance can be used when either sending or receiving tokens.
|
|
|
|
enum SwapKind { GIVEN_IN, GIVEN_OUT }
|
|
|
|
/**
|
|
* @dev Performs a swap with a single Pool.
|
|
*
|
|
* If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens
|
|
* taken from the Pool, which must be greater than or equal to `limit`.
|
|
*
|
|
* If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens
|
|
* sent to the Pool, which must be less than or equal to `limit`.
|
|
*
|
|
* Internal Balance usage and the recipient are determined by the `funds` struct.
|
|
*
|
|
* Emits a `Swap` event.
|
|
*/
|
|
function swap(
|
|
SingleSwap memory singleSwap,
|
|
FundManagement memory funds,
|
|
uint256 limit,
|
|
uint256 deadline
|
|
) external payable returns (uint256);
|
|
|
|
/**
|
|
* @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on
|
|
* the `kind` value.
|
|
*
|
|
* `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).
|
|
* Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.
|
|
*
|
|
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
|
|
* used to extend swap behavior.
|
|
*/
|
|
struct SingleSwap {
|
|
bytes32 poolId;
|
|
SwapKind kind;
|
|
IAsset assetIn;
|
|
IAsset assetOut;
|
|
uint256 amount;
|
|
bytes userData;
|
|
}
|
|
|
|
/**
|
|
* @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either
|
|
* the amount of tokens sent to or received from the Pool, depending on the `kind` value.
|
|
*
|
|
* Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the
|
|
* Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at
|
|
* the same index in the `assets` array.
|
|
*
|
|
* Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a
|
|
* Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or
|
|
* `amountOut` depending on the swap kind.
|
|
*
|
|
* Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out
|
|
* of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal
|
|
* the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.
|
|
*
|
|
* The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,
|
|
* or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and
|
|
* out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to
|
|
* or unwrapped from WETH by the Vault.
|
|
*
|
|
* Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies
|
|
* the minimum or maximum amount of each token the vault is allowed to transfer.
|
|
*
|
|
* `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the
|
|
* equivalent `swap` call.
|
|
*
|
|
* Emits `Swap` events.
|
|
*/
|
|
function batchSwap(
|
|
SwapKind kind,
|
|
BatchSwapStep[] memory swaps,
|
|
IAsset[] memory assets,
|
|
FundManagement memory funds,
|
|
int256[] memory limits,
|
|
uint256 deadline
|
|
) external payable returns (int256[] memory);
|
|
|
|
/**
|
|
* @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the
|
|
* `assets` array passed to that function, and ETH assets are converted to WETH.
|
|
*
|
|
* If `amount` is zero, the multihop mechanism is used to determine the actual amount based on the amount in/out
|
|
* from the previous swap, depending on the swap kind.
|
|
*
|
|
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
|
|
* used to extend swap behavior.
|
|
*/
|
|
struct BatchSwapStep {
|
|
bytes32 poolId;
|
|
uint256 assetInIndex;
|
|
uint256 assetOutIndex;
|
|
uint256 amount;
|
|
bytes userData;
|
|
}
|
|
|
|
/**
|
|
* @dev Emitted for each individual swap performed by `swap` or `batchSwap`.
|
|
*/
|
|
event Swap(
|
|
bytes32 indexed poolId,
|
|
IERC20 indexed tokenIn,
|
|
IERC20 indexed tokenOut,
|
|
uint256 amountIn,
|
|
uint256 amountOut
|
|
);
|
|
|
|
/**
|
|
* @dev All tokens in a swap are either sent from the `sender` account to the Vault, or from the Vault to the
|
|
* `recipient` account.
|
|
*
|
|
* If the caller is not `sender`, it must be an authorized relayer for them.
|
|
*
|
|
* If `fromInternalBalance` is true, the `sender`'s Internal Balance will be preferred, performing an ERC20
|
|
* transfer for the difference between the requested amount and the User's Internal Balance (if any). The `sender`
|
|
* must have allowed the Vault to use their tokens via `IERC20.approve()`. This matches the behavior of
|
|
* `joinPool`.
|
|
*
|
|
* If `toInternalBalance` is true, tokens will be deposited to `recipient`'s internal balance instead of
|
|
* transferred. This matches the behavior of `exitPool`.
|
|
*
|
|
* Note that ETH cannot be deposited to or withdrawn from Internal Balance: attempting to do so will trigger a
|
|
* revert.
|
|
*/
|
|
struct FundManagement {
|
|
address sender;
|
|
bool fromInternalBalance;
|
|
address payable recipient;
|
|
bool toInternalBalance;
|
|
}
|
|
|
|
/**
|
|
* @dev Simulates a call to `batchSwap`, returning an array of Vault asset deltas. Calls to `swap` cannot be
|
|
* simulated directly, but an equivalent `batchSwap` call can and will yield the exact same result.
|
|
*
|
|
* Each element in the array corresponds to the asset at the same index, and indicates the number of tokens (or ETH)
|
|
* the Vault would take from the sender (if positive) or send to the recipient (if negative). The arguments it
|
|
* receives are the same that an equivalent `batchSwap` call would receive.
|
|
*
|
|
* Unlike `batchSwap`, this function performs no checks on the sender or recipient field in the `funds` struct.
|
|
* This makes it suitable to be called by off-chain applications via eth_call without needing to hold tokens,
|
|
* approve them for the Vault, or even know a user's address.
|
|
*
|
|
* Note that this function is not 'view' (due to implementation details): the client code must explicitly execute
|
|
* eth_call instead of eth_sendTransaction.
|
|
*/
|
|
function queryBatchSwap(
|
|
SwapKind kind,
|
|
BatchSwapStep[] memory swaps,
|
|
IAsset[] memory assets,
|
|
FundManagement memory funds
|
|
) external returns (int256[] memory assetDeltas);
|
|
|
|
// Flash Loans
|
|
|
|
/**
|
|
* @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
|
|
* and then reverting unless the tokens plus a proportional protocol fee have been returned.
|
|
*
|
|
* The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
|
|
* for each token contract. `tokens` must be sorted in ascending order.
|
|
*
|
|
* The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
|
|
* `receiveFlashLoan` call.
|
|
*
|
|
* Emits `FlashLoan` events.
|
|
*/
|
|
function flashLoan(
|
|
IFlashLoanRecipient recipient,
|
|
IERC20[] memory tokens,
|
|
uint256[] memory amounts,
|
|
bytes memory userData
|
|
) external;
|
|
|
|
/**
|
|
* @dev Emitted for each individual flash loan performed by `flashLoan`.
|
|
*/
|
|
event FlashLoan(IFlashLoanRecipient indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount);
|
|
|
|
// Asset Management
|
|
//
|
|
// Each token registered for a Pool can be assigned an Asset Manager, which is able to freely withdraw the Pool's
|
|
// tokens from the Vault, deposit them, or assign arbitrary values to its `managed` balance (see
|
|
// `getPoolTokenInfo`). This makes them extremely powerful and dangerous. Even if an Asset Manager only directly
|
|
// controls one of the tokens in a Pool, a malicious manager could set that token's balance to manipulate the
|
|
// prices of the other tokens, and then drain the Pool with swaps. The risk of using Asset Managers is therefore
|
|
// not constrained to the tokens they are managing, but extends to the entire Pool's holdings.
|
|
//
|
|
// However, a properly designed Asset Manager smart contract can be safely used for the Pool's benefit,
|
|
// for example by lending unused tokens out for interest, or using them to participate in voting protocols.
|
|
//
|
|
// This concept is unrelated to the IAsset interface.
|
|
|
|
/**
|
|
* @dev Performs a set of Pool balance operations, which may be either withdrawals, deposits or updates.
|
|
*
|
|
* Pool Balance management features batching, which means a single contract call can be used to perform multiple
|
|
* operations of different kinds, with different Pools and tokens, at once.
|
|
*
|
|
* For each operation, the caller must be registered as the Asset Manager for `token` in `poolId`.
|
|
*/
|
|
function managePoolBalance(PoolBalanceOp[] memory ops) external;
|
|
|
|
struct PoolBalanceOp {
|
|
PoolBalanceOpKind kind;
|
|
bytes32 poolId;
|
|
IERC20 token;
|
|
uint256 amount;
|
|
}
|
|
|
|
/**
|
|
* Withdrawals decrease the Pool's cash, but increase its managed balance, leaving the total balance unchanged.
|
|
*
|
|
* Deposits increase the Pool's cash, but decrease its managed balance, leaving the total balance unchanged.
|
|
*
|
|
* Updates don't affect the Pool's cash balance, but because the managed balance changes, it does alter the total.
|
|
* The external amount can be either increased or decreased by this call (i.e., reporting a gain or a loss).
|
|
*/
|
|
enum PoolBalanceOpKind { WITHDRAW, DEPOSIT, UPDATE }
|
|
|
|
/**
|
|
* @dev Emitted when a Pool's token Asset Manager alters its balance via `managePoolBalance`.
|
|
*/
|
|
event PoolBalanceManaged(
|
|
bytes32 indexed poolId,
|
|
address indexed assetManager,
|
|
IERC20 indexed token,
|
|
int256 cashDelta,
|
|
int256 managedDelta
|
|
);
|
|
|
|
// Protocol Fees
|
|
//
|
|
// Some operations cause the Vault to collect tokens in the form of protocol fees, which can then be withdrawn by
|
|
// permissioned accounts.
|
|
//
|
|
// There are two kinds of protocol fees:
|
|
//
|
|
// - flash loan fees: charged on all flash loans, as a percentage of the amounts lent.
|
|
//
|
|
// - swap fees: a percentage of the fees charged by Pools when performing swaps. For a number of reasons, including
|
|
// swap gas costs and interface simplicity, protocol swap fees are not charged on each individual swap. Rather,
|
|
// Pools are expected to keep track of how much they have charged in swap fees, and pay any outstanding debts to the
|
|
// Vault when they are joined or exited. This prevents users from joining a Pool with unpaid debt, as well as
|
|
// exiting a Pool in debt without first paying their share.
|
|
|
|
/**
|
|
* @dev Returns the current protocol fee module.
|
|
*/
|
|
function getProtocolFeesCollector() external view returns (IProtocolFeesCollector);
|
|
|
|
/**
|
|
* @dev Safety mechanism to pause most Vault operations in the event of an emergency - typically detection of an
|
|
* error in some part of the system.
|
|
*
|
|
* The Vault can only be paused during an initial time period, after which pausing is forever disabled.
|
|
*
|
|
* While the contract is paused, the following features are disabled:
|
|
* - depositing and transferring internal balance
|
|
* - transferring external balance (using the Vault's allowance)
|
|
* - swaps
|
|
* - joining Pools
|
|
* - Asset Manager interactions
|
|
*
|
|
* Internal Balance can still be withdrawn, and Pools exited.
|
|
*/
|
|
function setPaused(bool paused) external;
|
|
|
|
/**
|
|
* @dev Returns the Vault's WETH instance.
|
|
*/
|
|
function WETH() external view returns (IWETH);
|
|
// solhint-disable-previous-line func-name-mixedcase
|
|
}
|