Developing Weighted Pool (Balancer-Style)
Balancer V2 rethought AMM: instead of rigid 50/50 ratio came pools with arbitrary asset weights—80/20, 60/20/20, 33/33/34. This opened new class of problems: on-chain portfolio management with automatic rebalancing via arbitrage. But implementing weighted pool from scratch—means encountering invariant mathematics, precision issues with large exponents and price manipulation vulnerabilities.
Weighted Pool Mathematics and Where It Breaks
Balancer Invariant and Fixed-Point Computations
Weighted pool rests on invariant:
V = ∏(Bᵢ / Wᵢ)^Wᵢ
where Bᵢ—token i balance, Wᵢ—its normalized weight. During swap, system solves equation for output balance. Problem—x^y at non-integer exponents requires LogExpMath, fixed-point library for natural logarithm and exponent calculation in 18-decimal Solidity.
Balancer uses LogExpMath.sol with bounds: x must be in range [0.000001e18, 2^255], exponent not exceed 130e18. Out of bounds—revert. This isn't just technical detail: adding liquidity with extreme ratios (e.g., 99% of one asset to 80/20 pool) may hit library limits. addLiquidity reverts on legitimate operations.
Spot Price and Flash Loan Manipulation
Spot price in weighted pool defined as:
SP = (Bᵢ / Wᵢ) / (Bⱼ / Wⱼ)
This is instantaneous price before swap fee. If protocol uses getSpotPrice() as oracle—vulnerable to flash loan manipulation. Attacker takes huge loan, swaps moving spot price 10x, calls vulnerable function, returns loan. All in one transaction.
Solution—don't use spot price as price oracle. For on-chain prices need Chainlink or Uniswap V3 TWAP (IUniswapV3Pool.observe()). Inside pool, spot price used only for swap calculation—correct because swap itself changes balances and shifts price back via swap fee.
Join Problem with Unequal Weights and Impermanent Loss
Unlike Uniswap V2, weighted pool allows entering with arbitrary token set or single token. Single-asset join goes through internal virtual swap, charged with swap fee. Must explicitly explain to users: large single-asset join—like swapping half amount. At 80/20 ETH/USDC weights, join via USDC only—user implicitly buys ETH.
Impermanent loss in weighted pool less than 50/50 pool at same price moves. For 80/20 pool at 5x asset A price IL ~4.4% vs 25.5% at 50/50. Mathematically provable, we add IL graphs for specific weights to documentation.
How We Build Weighted Pool
Architecture Based on Balancer V2 Vault
Balancer V2 separated token storage and pool logic. All tokens in single Vault contract, pools just calculation logic. Gives:
- Flash loans from any Vault token without separate contract
- Batch swaps through multiple pools in one transaction
- Single authorization point via
IAuthorizer
Developing custom weighted pool, we implement IBasePool interface and register in Vault. Key methods: onSwap(), onJoinPool(), onExitPool(). Invariant logic lives in WeightedMath.sol—we use proven Balancer implementation, not write own.
Managed Weights (Managed Pool)
For on-chain index funds need ability to change weights without flash loan vulnerability. Balancer solves via gradual weight update: weights linearly interpolate between start and end values by blocks.
function _getNormalizedWeight(IERC20 token) internal view returns (uint256) {
uint256 pctProgress = _calculateWeightChangeProgress();
return _interpolateWeight(_startWeight[token], _endWeight[token], pctProgress);
}
Abrupt weight changes let arbitrageurs extract value from LP. Gradual changes give arbitrageurs market-price trading opportunity, minimizing losses.
Mainnet Fork Testing
Before deploy run fork-tests via Foundry against real Balancer Vault on Ethereum:
forge test --fork-url $MAINNET_RPC --match-contract WeightedPoolTest -vvv
Check: round-trip swap doesn't lose more than swap fee + 1 wei (invariant preserved), single-asset join/exit correctly calculates BPT (Balancer Pool Tokens), LogExpMath edge cases don't revert on real volumes.
Stack and Integrations
| Component | Technology |
|---|---|
| Math | LogExpMath.sol, FixedPoint.sol (Balancer) |
| Testing | Foundry fork-tests, Echidna property tests |
| Price Oracles | Chainlink Data Feeds, Uniswap V3 TWAP |
| Management | Gnosis Safe + Timelock for weight changes |
| Frontend | wagmi v2, viem, Balancer SDK |
| Indexing | The Graph (subgraph for pool events) |
Development Process
Analytics (2-3 days). Determine pool composition, weights, swap fee, if weight management needed. Calculate expected volumes and IL for LP.
Design (3-5 days). Choose between Balancer V2 fork and custom implementation based on interfaces. For most tasks—fork with minimal changes, don't invent own math.
Development (1-2 weeks). Pool contracts + administrative functions + Vault integration. Fuzz-test invariant via Echidna: property V_after >= V_before after any operation (except swaps where V changes correctly).
Audit and Deployment. Slither + manual math audit. Deploy via forge script with verification. For pools with TVL >$500K—external audit mandatory.
Timeline Expectations
Weighted pool based on Balancer V2 fork with custom weights—2 to 4 weeks. Managed Pool with gradual weight changes and governance—4 to 6 weeks. Fully custom math with new invariant—6+ weeks, plus mandatory external audit.







