Degen Code

Degen Code

Share this post

Degen Code
Degen Code
Balancer — V2 Vault Batch Swaps
Copy link
Facebook
Email
Notes
More

Balancer — V2 Vault Batch Swaps

Part IV: Two Swaps At The Same Time, Man

Dec 20, 2024
∙ Paid
1

Share this post

Degen Code
Degen Code
Balancer — V2 Vault Batch Swaps
Copy link
Facebook
Email
Notes
More
1
Share

Continuing our exploration of the Balancer ecosystem, let’s turn our attention to the batch swap functionality of the Vault contract.

Part II covered the Weighted Balancer pool variant, and Part III covered its interaction with the Vault.

Balancer — V2 Pool Contract

Balancer — V2 Pool Contract

BowTiedDevil
·
December 11, 2024
Read full story
Balancer — V2 Vault Contract

Balancer — V2 Vault Contract

BowTiedDevil
·
December 16, 2024
Read full story

This lesson will review the Vault’s batch swap feature, which allows the Vault to perform multiple swaps in a single transaction. Uniswap V3 offers a similar feature via the Multicall interface to the V3 Router and Universal Router contracts.

UniswapV3 — Multicall

UniswapV3 — Multicall

BowTiedDevil
·
October 17, 2022
Read full story

Batch Swaps

The batch swap function appears in Swaps.sol, which the Vault contract inherits:

function batchSwap(
    SwapKind kind,
    BatchSwapStep[] memory swaps,
    IAsset[] memory assets,
    FundManagement memory funds,
    int256[] memory limits,
    uint256 deadline
)
    external
    payable
    override
    nonReentrant
    whenNotPaused
    authenticateFor(funds.sender)
    returns (int256[] memory assetDeltas)
{
    // The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy.
    // solhint-disable-next-line not-rely-on-time
    _require(block.timestamp <= deadline, Errors.SWAP_DEADLINE);

    InputHelpers.ensureInputLengthMatch(assets.length, limits.length);

    // Perform the swaps, updating the Pool token balances and computing the net Vault asset deltas.
    assetDeltas = _swapWithPools(swaps, assets, funds, kind);

    // Process asset deltas, by either transferring assets from the sender (for positive deltas) or to the recipient
    // (for negative deltas).
    uint256 wrappedEth = 0;
    for (uint256 i = 0; i < assets.length; ++i) {
        IAsset asset = assets[i];
        int256 delta = assetDeltas[i];
        _require(delta <= limits[i], Errors.SWAP_LIMIT);

        if (delta > 0) {
            uint256 toReceive = uint256(delta);
            _receiveAsset(asset, toReceive, funds.sender, funds.fromInternalBalance);

            if (_isETH(asset)) {
                wrappedEth = wrappedEth.add(toReceive);
            }
        } else if (delta < 0) {
            uint256 toSend = uint256(-delta);
            _sendAsset(asset, toSend, funds.recipient, funds.toInternalBalance);
        }
    }

    // Handle any used and remaining ETH.
    _handleRemainingEth(wrappedEth);
}

It is a simple function, focused mainly on input validation and token amount bookkeeping.

Let’s consider the function inputs:

  • SwapKind kind

  • BatchSwapStep[] memory swaps

  • IAsset[] memory assets

  • FundManagement memory funds

  • int256[] memory limits

  • uint256 deadline

We studied the SwapKind and FundManagement structs in Part III. IAsset, limit, and deadline are simple types covered in Part II.

So the only new type we need to inspect is BatchSwapStep, which is defined in IVault.sol:

struct BatchSwapStep {
    bytes32 poolId;
    uint256 assetInIndex;
    uint256 assetOutIndex;
    uint256 amount;
    bytes userData;
}

The poolId, amount, and userData elements are straightforward and familiar. However assetInIndex and assetOutIndex are new.

The comments above batchSwap() in IVault.sol clarify that the entries in swaps specify the input and output tokens by their index in the assets array. So we should be aware that multi-pool swaps need to perform some pre-transaction token indexing and provide the appropriate index at each pool that matches the order we’ve specified.

Swaps At Each Pool

From the perspective of the swap through each pool, it operates similarly to the single swap function.

To keep the function small, the logic for performing the chained swaps is offloaded to a _swapWithPools() function:

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 BowTiedDevil
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More