Balancer V2 — Contract Reference

Operational details for Balancer V2 weighted/stable pools: which canonical contracts the Rust engine implements (the Python companions are thin shells over it), how each contract generation is detected on-chain, and the deployment lineage used as parity ground truth.

Sources scraped for this reference (2026-09):

  • balancer-v2-monorepo @ f8b6f44f21afaf3c802536ed478277f945e7f256 (2026-05-28, “Bump hardhat minor version (#2652)”) — also the vendored reference for the Tier-3 oracle.

  • balancer-labs/balancer-deployments @ HEAD (addresses/base.json, addresses/mainnet.json).

Canonical math pin (Tier-3 oracle)

tier3-oracle/src-balancer/BalancerSwapOracleHarness.sol compiles the canonical library sources fetched at the pinned commit into tier3-oracle/lib/balancer-src/:

Vendored source

Canonical package

Version at pin

FixedPoint.sol, LogExpMath.sol, Math.sol

pkg/solidity-utils

4.0.0

WeightedMath.sol

pkg/pool-weighted

2.0.1

StableMath.sol

pkg/pool-stable

0.1.0

(vault interfaces)

pkg/vault

3.0.1

The harness reproduces the fee/scaling/direction sequence of the Rust engine (degenbot-pools::simulate_balancer_weighted_swap / simulate_balancer_stable_swap), which is the sole owner of Balancer swap math; the Python companions are thin shells over it. Byte-exact parity is asserted in rust/crates/foundation/degenbot-pools/tests/tier3_balancer_swap_vs_revm.rs (just test-tier3 balancer); artifact integrity is enforced by tier3_harness_artifacts.rs + verify-tier3-artifacts.sh.

Contract-generation matrix

Contract

Deployment lineage → factory

Swap math

Rust engine

Python companion

WeightedPool2Tokens (2021)

mainnet 20210418-weighted-pool → WeightedPool2TokensFactory 0xA5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0, WeightedPoolFactory 0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9 (DEPRECATED)

pow V1 (FixedPoint without the TWO/FOUR fast paths)

YES — PowVersion.V1

shell over Rust core

WeightedPool (current, post-2022)

Base 20230320-weighted-pool-v4 (ACTIVE) → WeightedPoolFactory 0x4C32a8a8fDa4E24139B51b456B42290f51d6A1c4; mainnet 20230206-weighted-pool-v3 / 20230320-weighted-pool-v4

pow V2 (LogExpMath fast paths for y == ONE/TWO/FOUR)

YES — PowVersion.V2

shell over Rust core

StablePool (plain, 2021-06)

mainnet 20210624-stable-pool

StableMath._calculateInvariant(amp, balances, roundUp=true) (P_D accumulation — the deployed inline revision shared with MetaStable)

YES — InvariantVersion.V2

shell over Rust core

MetaStablePool

mainnet 20210727-meta-stable-pool → MetaStablePoolFactory 0x67d27634E44793fE63c467035E31ea8635117cd4 (DEPRECATED)

StableMath._calculateInvariant(amp, balances, roundUp=true) (P_D round up)

YES — InvariantVersion.V2

shell over Rust core

ComposableStablePool (incl. MetaStable-era V2/V3 and V5/V6 factories)

mainnet 20220906–20240223; Base 20230711-composable-stable-pool-v5 / 20240223-composable-stable-pool-v6 (Base factories listed DEPRECATED — pools live in the V2 vault registry regardless)

INVARIANT_V1 (round-down D_P) + BPT-in-balances

YES — InvariantVersion.V1 + bpt_idx skip

shell over Rust core

Balancer V3 (the 2024-12 vault + 2026 factories)

Base/mainnet 20241204-v3-vault + v3-weighted-pool* / v3-stable-pool* tasks

hook-based, new vault

NOT in scope (V2-vault pools only)

—

Shared across chains: the V2 Vault itself (0xBA12222222228d8Ba445958a75a0704d566BF2C8).

Version detection at construction

  • Weighted pow version — detect_pow_version() (balancer/pools.py) scans the deployed bytecode for the TWO constant (0x1bc16d674ec80000) used by the y == TWO fast path, absent from 2021-era contracts → PowVersion.V1.

  • Stable invariant version — resolve_invariant_version() (builders/balancer_builder_base.py) maps the Vault pool specialization: General = 0 → ComposableStablePool → INVARIANT_V1; MinimalSwapInfo = 1 → MetaStablePool → INVARIANT_V2. Plain 2021 StablePools are General (3+ tokens) or TwoToken (2 tokens) with no BPT and also use INVARIANT_V2; the heuristic has no branch for them, so builds must pass the BuildPoolRequest.invariant_version override (see gaps).

  • BPT-in-balances — detect_bpt_index() probes the token list; None marks MetaStable (no BPT token).

Known deployed pools used as parity ground truth

Golden-oracle parity (mainnet, block 24,407,242 — tests/golden/data/tests/balancer/):

Pool

Notes

0x5c6Ee304…DB8F56 (80 BAL / 20 WETH)

2021-era weighted (pow V1)

0x3e5FA951…CAA32 (USDT / WETH)

weighted pair

0xff083f57…1FBe88 (GTC / WETH)

weighted pair

0x0b09deA16…2a35

two-token weighted

0x9D0D36cC…c30ed

multi-token weighted (N > 2)

0x32296969…130230

stable (boosted/composable lineage)

Gaps / follow-ups

  1. Plain StablePool (2021) — covered, override still required. The committed 20210624-stable-pool build-info (solc 0.7.1) carries the deployed StableMath._calculateInvariant(amp, balances, roundUp) P_D revision with swaps on roundUp=true — exactly the engine’s InvariantVersion.V2 (calculate_invariant_deployed(..., true)). The Tier-3 companion gate exercises a 3-token plain-2021 configuration (INVARIANT_V2, no BPT) through both companion methods and both directions, and the Rust Tier-3 oracle pins that multi-token path byte-exact to the harness’s verbatim deployed invariant. The builder’s resolve_invariant_version heuristic still maps only specializations 0 and 1 (plain 2021 pools are 0 or 2), so production builds of a plain 2021 pool must pass the BuildPoolRequest.invariant_version override until the heuristic learns the no-BPT/no-rate-provider case.

  2. BalancerQueries on Base — resolved. The two addresses are per-chain deployments of the same 20220721-balancer-queries artifact: 0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5 has code on Ethereum mainnet and none on Base, while 0x300Ab2038EAc391f26D9F895dc61F8F66a548833 has code on Base (its vault() is the canonical V2 Vault) and none on mainnet. The driver constant (BALANCERQUERIES_CONTRACT_ADDRESS in balancer/deployments.py) is intentionally mainnet-scoped for the mainnet parity tests; the deployments registry’s 0x300Ab is the Base counterpart. No Base-scoped constant is added because no consumer queries BalancerQueries on Base.