--- tags: LitReview --- # The Vault Deep Dive ## Components Summary (General) ### Authorizor - The balancer governance contract which is set for the vault - Any changing of the authorizor requires approval from the current authorizor ### Relayer - Relayers call functions on behalf of users - Have to be approved to be able to act as a relayer ### Internal Balance - Users deposit into the vault for their internal balanace which are later either withdrawn or transferred - Use cases can be as the source of funds when joining a pool, the destination of funds when exiting a pool, or either the source or destination when doing a swap - Internal balances greatly reduces gas costs - Batching is a feature of this so multiple different transactions can be called at once ### User Balance Operations - There are four types of operations that can happen: An internal deposit, an internal withdraw, an internal transfer and an external transfer #### Internal Deposit - Increases the Internal Balance of the `recipient` account by transferring tokens from the corresponding `sender`. #### Internal Withdraw - Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient` #### Internal Transfer - Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient` #### External Transfer - Transfers tokens from `sender` to `recipient`, using the Vault's ERC20 allowance ### Pools - Pools for holding funds - Three specializations of pools: General, Miniminal Swap Info, Two Token - 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. - Multiple pools may share the same contract - Pools cannot be deregistered - Pools can not have the specialization changed #### General - Suitable for all pools - Highest swap cost - Passes balance of all tokens in the pool - Costs increase with number of registered tokens #### Minimal Swap Info - Only passes the balance of the two tokens involved in the swap - Suitable for some pricing algorithms #### Two Token - Only allows for two tokens to be registered - The lowest costs of the three #### Pool + Token Information - Found through calling getPoolTokenInfo - `cash`: number of tokens the Vault currently holds for the Pool - `managed`: number of tokens withdrawn and held outside the Vault by the Pool's token Asset Manager - `token` = `cash` + `managed` - `lastChangeBlock`: latest block number in which `token`'s total balance was modified - `assetManager`: Pool's token Asset Manager ### Joins - joinPool takes care of joins - Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance - This triggers custom Pool behavior, typically granting pool shares - If the caller is not `sender`, it must be an authorized relayer for them - `assets` and `maxAmountsIn` decide for each asset the amount to put in at maximum - Amounts to send are decided by the Pool and not the Vault, Vault just enforces maximums - This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement their own custom logic ### Exits - 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 - The amounts to send are decided by the Pool and not the Vault: it just enforces these minimums. - This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract ### Swaps - Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions - The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence. - Two types of swaps: - given in: 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: 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) - possible to chain swaps using a placeholder input amount, which the Vault replaces with the calculated output of the previous swap for multihop swaps - Tokens do not leave the vault until all swaps involved are completed (saves on gas) - Pure abritrage is possible with this (no need to put down any tokens at all) - a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after this point in time ### Flash Loans - The flashLoan function 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. - Flash loans allow for a transaction that can only take place within the same block ### Pool Balance - There are three types of changes to the pool balance: WITHDRAW, DEPOSIT, UPDATE - WITHDRAW: decrease the Pool's cash, but increase its managed balance, leaving the total balance unchanged - DEPOSIT: increase the Pool's cash, but decrease its managed balance, leaving the total balance unchanged - UPDATE: 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). ### Protocol Fees - Some operations cause the Vault to collect tokens in the form of protocol fees, which can then be withdrawn by permissioned accounts. - 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. ## IVault Interface (Detailed) ### getAuthorizer **Code**: function getAuthorizer() external view returns (IAuthorizer) **Description**: Returns the authorizer of the contract ### setAuthorizer **Code**: function setAuthorizer(IAuthorizer newAuthorizer) external **Description**: Sets a new Authorizer for the Vault - The caller must be allowed by the current Authorizer to do this - Emits an `AuthorizerChanged` event ### AuthorizerChanged **Code**: event AuthorizerChanged(IAuthorizer indexed newAuthorizer) **Description**: Emitted when a new authorizer is set by `setAuthorizer` ### hasApprovedRelayer **Code**: function hasApprovedRelayer(address user, address relayer) external view returns (bool) **Description**: Returns true if a user has approved a relayer to act as a relayer for them ### setRelayerApproval **Code**: function setRelayerApproval(address sender, address relayer, bool approved) external **Description**: Allows `relayer` to act as a relayer for `sender` if `approved` is true, and disallows it otherwise. - Emits a `RelayerApprovalChanged` event. ### RelayerApprovalChanged **Code**: event RelayerApprovalChanged(address indexed relayer, address indexed sender, bool approved) **Description**: Emitted every time a relayer is approved or disapproved by `setRelayerApproval`. ### getInternalBalance **Code**: function getInternalBalance(address user, IERC20[] memory tokens) external view returns (uint256[] memory) **Description**: Returns `user`'s Internal Balance for a set of tokens. ### manageUserBalance **Code**: function manageUserBalance(UserBalanceOp[] memory ops) external payable; **Description**: Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer) and plain ERC20 transfers using the Vault's allowance - For each operation, if the caller is not `sender`, it must be an authorized relayer for them. - Struct for UserBalanceOp: struct UserBalanceOp { UserBalanceOpKind kind; IAsset asset; uint256 amount; address sender; address payable recipient; } - UserBalanceOpKind options: DEPOSIT_INTERNAL, WITHDRAW_INTERNAL, TRANSFER_INTERNAL, TRANSFER_EXTERNAL #### Internal Deposit - 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()` - Emits an `InternalBalanceChanged` event. #### Internal Withdraw - Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient` - Emits an `InternalBalanceChanged` event. #### Internal Transfer - Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient` - Emits an `InternalBalanceChanged` event #### External Transfer - 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. - Emits an `ExternalBalanceTransfer` event. ### InternalBalanceChanged **Code**: event InternalBalanceChanged(address indexed user, IERC20 indexed token, int256 delta) **Description**: This event is emitted when pools or a user balance operation changes the internal balance. - Because Internal Balance works exclusively with ERC20 tokens, ETH deposits and withdrawals will use the WETH address. ### ExternalBalanceTransfer **Code**: event ExternalBalanceTransfer(IERC20 indexed token, address indexed sender, address recipient, uint256 amount) **Description**: Emitted when a user's Vault ERC20 allowance is used by the Vault to transfer tokens to an external account. ### registerPool **Code**: function registerPool(PoolSpecialization specialization) external returns (bytes32) **Description**: Registers the caller account as a Pool with a given specialization setting. - The options come from the PoolSpecialization enum which has { GENERAL, MINIMAL_SWAP_INFO, TWO_TOKEN } - Returns the id of the pool - Pools cannot be deregistered - Pools can not have the specialization 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. - Multiple pools may share the same contract - Emits a `PoolRegistered` event. ### PoolRegistered **Code**: event PoolRegistered(bytes32 indexed poolId, address indexed poolAddress, PoolSpecialization specialization) **Description**: Emitted when a Pool is registered by calling `registerPool`. ### getPool **Code**: function getPool(bytes32 poolId) external view returns (address, PoolSpecialization) **Description**: Returns a Pool's contract address and specialization setting. ### registerTokens **Code**: function registerTokens(bytes32 poolId, IERC20[] memory tokens, address[] memory assetManagers) external **Description**: 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 - 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 - Asset Managers have to be very secure contracts because they can set balances to arbitrary amounts - 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. ### TokensRegistered **Code**: event TokensRegistered(bytes32 indexed poolId, IERC20[] tokens, address[] assetManagers) **Description**: Emitted when a Pool registers tokens by calling `registerTokens`. ### deregisterTokens **Code**: function deregisterTokens(bytes32 poolId, IERC20[] memory tokens) external **Description**: Deregisters `tokens` for the `poolId` Pool - Must be called by the Pool's contract - Only registered tokens (via `registerTokens`) can be deregistered - Must have 0 total balance - A deregistered token can be re-registered later on, possibly with a different Asset Manager - Emits a `TokensDeregistered` event ### TokensDeregistered **Code**: event TokensDeregistered(bytes32 indexed poolId, IERC20[] tokens) **Description**: Emitted when a Pool deregisters tokens by calling `deregisterTokens` ### getPoolTokenInfo **Code**: function getPoolTokenInfo(bytes32 poolId, IERC20 token) external view returns (uint256 cash, uint256 managed, uint256 lastChangeBlock, address assetManager) **Description**: Returns detailed information for a Pool's registered token - `cash`: number of tokens the Vault currently holds for the Pool - `managed`: number of tokens withdrawn and held outside the Vault by the Pool's token Asset Manager - `token` = `cash` + `managed` - `lastChangeBlock`: latest block number in which `token`'s total balance was modified - `assetManager`: Pool's token Asset Manager ### getPoolTokens **Code**: function getPoolTokens(bytes32 poolId) external view returns (IERC20[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock) **Description**: Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of the tokens' `balances` changed. - Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. ### joinPool **Code**: function joinPool(bytes32 poolId, address sender, address recipient, JoinPoolRequest memory request) external payable; **Description**: Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance - This triggers custom Pool behavior, typically granting pool shares - If the caller is not `sender`, it must be an authorized relayer for them - `assets` and `maxAmountsIn` decide for each asset the amount to put in at maximum - Amounts to send are decided by the Pool and not the Vault, Vault just enforces maximums - If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping - 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). - This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement their own custom logic - Extra information for the hook can be passed `userData` argument, which is ignored by the Vault and passed directly to the Pool's contract, as is `recipient` - Emits a `PoolBalanceChanged` event. - JoinPoolRequest Struct: struct JoinPoolRequest { IAsset[] assets; uint256[] maxAmountsIn; bytes userData; bool fromInternalBalance; } ### exitPool **Code**: function exitPool(bytes32 poolId, address sender, address payable recipient, ExitPoolRequest memory request) external; **Description**: 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 - If the caller is not `sender`, it must be an authorized relayer for them. - 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. - If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. - This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract - This typically requires additional information from the user (such as the expected number of Pool shares to return); userData can once again be used - Emits a `PoolBalanceChanged` event. - ExitPoolRequest struct: struct ExitPoolRequest { IAsset[] assets; uint256[] minAmountsOut; bytes userData; bool toInternalBalance; } ### PoolBalanceChanged **Code**: event PoolBalanceChanged(bytes32 indexed poolId, address indexed liquidityProvider, IERC20[] tokens, int256[] deltas, uint256[] protocolFeeAmounts) **Description**: Emitted when a user joins or exits a Pool by calling `joinPool` or `exitPool`, respectively. - PoolBalanceChangeKind is an enum of { JOIN, EXIT } ### swap **Code**: function swap(SingleSwap memory singleSwap, FundManagement memory funds, uint256 limit, uint256 deadline) external payable returns (uint256); **Description**: 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. - The SwapKind enum is { GIVEN_IN, GIVEN_OUT } - SingleSwap Struct: struct SingleSwap { bytes32 poolId; SwapKind kind; IAsset assetIn; IAsset assetOut; uint256 amount; bytes userData; } - `amount`: either `amountIn` or `amountOut` depending on the `kind` value. - `assetIn`/`assetOut`: either token addresses, or the IAsset sentinel value for ETH (the zero address) - `userData`: Data forwarded to `onSwap` hook ### batchSwap **Code**: function batchSwap(SwapKind kind, BatchSwapStep[] memory swaps, IAsset[] memory assets, FundManagement memory funds, int256[] memory limits, uint256 deadline) external payable returns (int256[] memory); **Description**: 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. - Swaps are executed sequentially, in the order specified by the `swaps` array. Each element describes the specifics for the swap - Emits `Swap` events. - BatchSwapStep Struct: struct BatchSwapStep { bytes32 poolId; uint256 assetInIndex; uint256 assetOutIndex; uint256 amount; bytes userData; } - asset in/out fields: indexes into the `assets` array - The other fields are the same as the single swap struct ### Swap **Code**: event Swap(bytes32 indexed poolId, IERC20 indexed tokenIn, IERC20 indexed tokenOut, uint256 amountIn, uint256 amountOut); **Description**: Emitted for each individual swap performed by `swap` or `batchSwap`. ### queryBatchSwap **Code**: function queryBatchSwap(SwapKind kind, BatchSwapStep[] memory swaps, IAsset[] memory assets, FundManagement memory funds) external returns (int256[] memory assetDeltas) **Description**: Simulates a call to `batchSwap`, returning an array of Vault asset deltas FundManagement Struct: struct FundManagement { address sender; bool fromInternalBalance; address payable recipient; bool toInternalBalance; } ### flashLoan **Code**: function flashLoan(IFlashLoanRecipient recipient, IERC20[] memory tokens, uint256[] memory amounts, bytes memory userData) external; **Description**: 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` indicate the loan amounts for each token contract. - The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the `receiveFlashLoan` call. - Emits `FlashLoan` events. ### FlashLoan **Code**: event FlashLoan(IFlashLoanRecipient indexed recipient, IERC20 indexed token, uint256 amount, uint256 feeAmount) **Description**: Emitted for each individual flash loan performed by `flashLoan` ### managePoolBalance **Code**: function managePoolBalance(PoolBalanceOp[] memory ops) external **Description**: 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`. - Struct for PoolBalanceOp: struct PoolBalanceOp { PoolBalanceOpKind kind; bytes32 poolId; IERC20 token; uint256 amount; } - The enum PoolBalanceOpKind has values { WITHDRAW, DEPOSIT, UPDATE } - WITHDRAW: decrease the Pool's cash, but increase its managed balance, leaving the total balance unchanged - DEPOSIT: increase the Pool's cash, but decrease its managed balance, leaving the total balance unchanged - UPDATE: 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). ### PoolBalanceManaged **Code**: event PoolBalanceManaged(bytes32 indexed poolId, address indexed assetManager, IERC20 indexed token, int256 cashDelta, int256 managedDelta) **Description**: Emitted when a Pool's token Asset Manager alters its balance via `managePoolBalance`. ### getProtocolFeesCollector **Code**: function getProtocolFeesCollector() external view returns (IProtocolFeesCollector) **Description**: Returns the current protocol fee module. ### setPaused **Code**: function getProtocolFeesCollector() external view returns (IProtocolFeesCollector) **Description**: 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. - Disables the following features: - 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. ### WETH **Code**: function WETH() external view returns (IWETH) **Description**: Returns the Vault's WETH instance. ## References Github Repo: https://github.com/balancer-labs/balancer-v2-monorepo/tree/master/pkg/vault Github IVault Interface Repo Page: https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/vault/contracts/interfaces/IVault.sol