Degen Code

Degen Code

Share this post

Degen Code
Degen Code
Balancer — V2 Pool Contract
Copy link
Facebook
Email
Notes
More

Balancer — V2 Pool Contract

Part II: Weighted Pool, Two Tokens, Vault Swap

Dec 11, 2024
∙ Paid
3

Share this post

Degen Code
Degen Code
Balancer — V2 Pool Contract
Copy link
Facebook
Email
Notes
More
1
Share

The Balancer V2 codebase is contained in a monorepo on Github which holds several discrete packages. The packages are organized under the pkg directory, and the README contains links to several of the most important ones.

In this lesson we will review a Balancer pool contract and its associated Vault, then load the contracts on a local fork to experiment with a direct token swap performed without a front end.

I viewed the list of pools on the Balancer app to select an appropriate pool. One of the mainnet pools is an 80-20 weighted WETH/BAL pool. If you’re unfamiliar with the concept of pool weightings, please review my Balancer Introduction:

Balancer — Overview

Balancer — Overview

BowTiedDevil
·
December 7, 2024
Read full story

This pool has high volume and liquidity and appears to be quite active, so it’s a good choice for exploration.

The pool address is 0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56. Inspecting the Etherscan page reveals that the pool is built on the WeightedPool2Tokens.sol contract. Curiously, the Balance repo does not contain a contract by this name. Inspecting the deployment transaction for the pool, we see that it was created by a factory at address 0xA5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0. The contract name for that factory is WeightedPool2TokensFactory.sol, which is also not available in the monorepo!

Searching on Github, I found a fork of the Balancer repo as of 3 years ago. It contains a version of WeightedPool2TokensFactory.sol which is very similar to the verified source for that factory contract, but does not match exactly.

Since it’s clear that the development of V2 in the monorepo was active after this deployment, I’ll prioritize the verified source on Etherscan instead. However don’t forget that the monorepo contains a test suite that I can use to inform our unit tests. I used the Uniswap V3 tests to verify the behavior of many libraries in degenbot, so this is a familiar approach.

The source code of WeightedPool2Tokens.sol is contained within the factory, so we will use it from here. I recommend opening the Etherscan verified WeightedPool2Tokens.sol contract in another tab and referring to it as we move through the contract review here.

WeightedPool2Tokens

Contract Definition

The pool contract inherits from several others. Follow the links to their source if interested:

  • IMinimalSwapInfoPool

  • IPriceOracle

  • BasePoolAuthorization

  • BalancerPoolToken

  • TemporarilyPausable

  • PoolPriceOracle

  • WeightedMath

  • WeightedOracleMath

It uses several libraries for manipulating Solidity types:

  • FixedPoint for uint256

  • WeightedPoolUserDataHelpers for bytes

  • WeightedPool2TokensMiscData for bytes32

The contract defines several private and internal values:

  • uint256 private constant _MINIMUM_BPT = 1e6

  • uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 1e12 // 0.0001%

  • uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 1e17 // 10%

  • bytes32 internal _miscData

  • uint256 private _lastInvariant

  • IVault private immutable _vault

  • bytes32 private immutable _poolId

  • IERC20 internal immutable _token0

  • IERC20 internal immutable _token1

  • uint256 private immutable _normalizedWeight0

  • uint256 private immutable _normalizedWeight1

  • uint256 private immutable _maxWeightTokenIndex

  • uint256 internal immutable _scalingFactor0

  • uint256 internal immutable _scalingFactor1

Events:

  • event OracleEnabledChanged(bool enabled)

  • event SwapFeePercentageChanged(uint256 swapFeePercentage);

A struct of pool parameters:

  • struct NewPoolParams {

    IVault vault;

    string name;

    string symbol;

    IERC20 token0;

    IERC20 token1;

    uint256 normalizedWeight0;

    uint256 normalizedWeight1;

    uint256 swapFeePercentage;

    uint256 pauseWindowDuration;

    uint256 bufferPeriodDuration;

    bool oracleEnabled;

    address owner;

    }

And an important modifier that is applied to some functions:

  • modifier onlyVault(bytes32 poolId) {

    _require(msg.sender == address(getVault()), Errors.CALLER_NOT_VAULT);

    _require(poolId == getPoolId(), Errors.INVALID_POOL_ID);

    _;

    }

Functions

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