# Balancer V3 & Gyroscope: Integration Reference **Companion to**: `BALANCER_V3_GYROSCOPE_RESEARCH.md` **Purpose**: Concrete contract addresses, code snippets, and deployment examples --- ## Part 1: Balancer V3 Contract Addresses ### Ethereum Mainnet ``` Vault (Core): 0xBA12222222228d8Ba445958a75a0704d566BF2C8 WeightedPoolFactory: 0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc StablePoolFactory: 0xf3de90B4dDD9b3ba9d2ab9BbBe6EfFcf52F1a1fB Router (EntryPoint): 0xBA12222222228d8Ba445958a75a0704d566BF2C8 ProtocolFeeController: 0xF4c873f02aF4aE79d01aF27Cfd8F25FCC22d9D47 ``` **Key Insight**: The vault address is the same for all operations. Always target `0xBA12...` for swaps/joins/exits. ### Arbitrum ``` Vault: 0xBA12222222228d8Ba445958a75a0704d566BF2C8 WeightedPoolFactory: 0x86F5453991fBBFF1eDF663108f302a1eB4FdDf10 StablePoolFactory: 0x6B7b84d4bffad1c5C1eC8b3d8f1F5c81c5f1D2a4 GyroECLPFactory: 0x65755d39C36... (check Balancer UI for current) ``` ### Base ``` Vault: 0xBA12222222228d8Ba445958a75a0704d566BF2C8 WeightedPoolFactory: 0xf3de90B4dDD9b3ba9d2ab9BbBe6EfFcf52F1a1fB StablePoolFactory: 0x6B7b84d4bffad1c5C1eC8b3d8f1F5c81c5f1D2a4 USDC/sUSDS Boosted Pool: 0x... (look up on Balancer UI) ``` **Note**: Factory addresses change across deployments. **Always verify on [Balancer Deployments](https://github.com/balancer/balancer-deployments) repo** or Balancer UI for current addresses. --- ## Part 2: Python → Solidity Translation Guide ### E-CLP Parameters **Python** (`elliptical_clp.py:33-43`): ```python @dataclass class ECLPParams: alpha: float # Lower price bound beta: float # Upper price bound c: float # cos(-φ) rotation s: float # sin(-φ) rotation lam: float # λ ≥ 1, stretching factor ``` **Solidity** (FixedPoint 18 decimals): ```solidity struct ECLPParams { uint256 alpha; // e.g., 0.95e18 (95% price) uint256 beta; // e.g., 1.05e18 (105% price) uint256 c; // e.g., 0.707107e18 (cos 45°) uint256 s; // e.g., 0.707107e18 (sin 45°) uint256 lam; // e.g., 2.0e18 (2x stretch) } // Validation require(alpha < beta, "α must be < β"); require(lam >= 1e18, "λ must be ≥ 1"); uint256 norm = sqrt(c*c + s*s); require(abs(norm - 1e18) < 1e8, "(c,s) must be unit"); ``` ### Invariant Computation **Python** (`elliptical_clp.py:106-150`): ```python def compute_invariant(x: float, y: float, p: ECLPParams, dp: ECLPDerivedParams) -> float: A = make_eclp_A(p.c, p.s, p.lam) AChi_x = dp.w / p.lam + dp.z AChi_y = p.lam * dp.u + dp.v AChi = np.array([AChi_x, AChi_y]) AChi_sq = AChi @ AChi t = np.array([x, y]) At = A @ t At_sq = At @ At AtAChi = At @ AChi denom = AChi_sq - 1.0 discriminant = AtAChi**2 - denom * At_sq r = (AtAChi + np.sqrt(discriminant)) / denom return r ``` **Solidity** (PRBMath fixed-point): ```solidity import { PRBMath } from "@prb/math/PRBMath.sol"; function computeInvariant( uint256 x, uint256 y, ECLPParams memory params, ECLPDerivedParams memory derived ) internal pure returns (uint256 invariant) { // Compute A-matrix (uint256 a00, uint256 a01, uint256 a10, uint256 a11) = _computeAMatrix( params.c, params.s, params.lam ); // AChi components uint256 AChi_x = PRBMath.mulDiv(derived.w, 1e18, params.lam) + derived.z; uint256 AChi_y = PRBMath.mulDiv(params.lam, derived.u, 1e18) + derived.v; uint256 AChi_sq = PRBMath.mulDiv(AChi_x, AChi_x, 1e18) + PRBMath.mulDiv(AChi_y, AChi_y, 1e18); // At = A @ (x, y) uint256 At_x = PRBMath.mulDiv(a00, x, 1e18) + PRBMath.mulDiv(a01, y, 1e18); uint256 At_y = PRBMath.mulDiv(a10, x, 1e18) + PRBMath.mulDiv(a11, y, 1e18); uint256 At_sq = PRBMath.mulDiv(At_x, At_x, 1e18) + PRBMath.mulDiv(At_y, At_y, 1e18); // AtAChi = At · AChi uint256 AtAChi = PRBMath.mulDiv(At_x, AChi_x, 1e18) + PRBMath.mulDiv(At_y, AChi_y, 1e18); // Quadratic: denom * r^2 - 2*AtAChi*r + At_sq = 0 int256 denom = int256(AChi_sq) - int256(1e18); if (denom == 0) { // Degenerate: circle case λ=1 return _sqrt(At_sq); } // discriminant = AtAChi^2 - denom * At_sq uint256 disc = PRBMath.mulDiv(AtAChi, AtAChi, 1e18) - PRBMath.mulDiv(uint256(abs(denom)), At_sq, 1e18); // r = (AtAChi + sqrt(disc)) / denom uint256 sqrtDisc = _sqrt(disc); invariant = PRBMath.mulDiv(AtAChi + sqrtDisc, 1e18, uint256(abs(denom))); return invariant; } function _computeAMatrix( uint256 c, uint256 s, uint256 lam ) internal pure returns (uint256 a00, uint256 a01, uint256 a10, uint256 a11) { // A = [[c²/λ + s², (1-c²)(1-1/λ²)], // [(1-c²)(1-1/λ²), s²/λ + c²]] // Simplified from GyroECLPMath.sol uint256 lam_sq = PRBMath.mulDiv(lam, lam, 1e18); uint256 lam_inv = PRBMath.mulDiv(1e18, 1e18, lam); // 1/λ uint256 c_sq = PRBMath.mulDiv(c, c, 1e18); uint256 s_sq = PRBMath.mulDiv(s, s, 1e18); a00 = PRBMath.mulDiv(c_sq, lam_inv, 1e18) + s_sq; a11 = PRBMath.mulDiv(s_sq, lam_inv, 1e18) + c_sq; uint256 term = (1e18 - c_sq) * (1e18 - lam_inv) / 1e18; a01 = term; a10 = term; } function _sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; return PRBMath.sqrt(x); } ``` ### Swap Out Given In **Python** (`elliptical_clp.py:172-189`): ```python def calc_out_given_in( x: float, y: float, p: ECLPParams, dp: ECLPDerivedParams, amount_in: float, token_in: int = 0, ) -> float: r = compute_invariant(x, y, p, dp) if token_in == 0: new_x = x + amount_in new_y = _calc_y_given_x(new_x, r, p, dp) return y - new_y else: new_y = y + amount_in new_x = _calc_x_given_y(new_y, r, p, dp) return x - new_x ``` **Solidity** (for `IBasePool.onSwap`): ```solidity function onSwap(PoolSwapParams calldata params) external view override returns (uint256 amountCalculated) { uint256[] memory balances = params.balances; uint256 x = balances[0]; // Token 0 balance uint256 y = balances[1]; // Token 1 balance uint256 invariant = computeInvariant(x, y, eclpParams, eclpDerived); if (params.kind == SwapKind.EXACT_IN) { if (params.tokenInIndex == 0) { uint256 new_x = x + params.amountIn; uint256 new_y = _solveYGivenX(new_x, invariant); amountCalculated = y > new_y ? y - new_y : 0; } else { uint256 new_y = y + params.amountIn; uint256 new_x = _solveXGivenY(new_y, invariant); amountCalculated = x > new_x ? x - new_x : 0; } } else { // EXACT_OUT: solve for input required if (params.tokenOutIndex == 0) { uint256 new_x = x - params.amountOut; uint256 new_y = _solveYGivenX(new_x, invariant); amountCalculated = new_y > y ? new_y - y : 0; } else { uint256 new_y = y - params.amountOut; uint256 new_x = _solveXGivenY(new_y, invariant); amountCalculated = new_x > x ? new_x - x : 0; } } } ``` --- ## Part 3: P-AMM (Redemption Curve) Hook Example ### Simplified Hook Implementation ```solidity pragma solidity ^0.8.24; import { IHooks } from "@balancer-labs/v3-vault/contracts/interfaces/IHooks.sol"; contract SimpleRedemptionHook is IHooks { struct PAMMParams { uint256 alpha_bar; // Curvature: 10.0e18 uint256 xu_bar; // No-discount threshold: 0.8e18 uint256 theta_bar; // Floor rate: 0.5e18 uint256 outflow_memory; // Decay: 0.999e18 } struct PAMMState { uint256 reserve_value; // Total USD in reserve uint256 myco_supply; // Total MYCO issued uint256 cumulative_redeemed; uint256 last_redemption_time; } PAMMParams public params; PAMMState public state; address public bondedPool; address public governance; event RedemptionRateUpdated(uint256 newRate, uint256 timestamp); modifier onlyGovernance() { require(msg.sender == governance, "Not governance"); _; } function initialize( uint256 reserve, uint256 supply, address pool, address gov ) external { state = PAMMState(reserve, supply, 0, block.timestamp); params = PAMMParams(10.0e18, 0.8e18, 0.5e18, 0.999e18); bondedPool = pool; governance = gov; } function computeRedemptionRate(uint256 redemption_amount) public view returns (uint256) { if (state.myco_supply == 0) return 1e18; uint256 ba = (state.reserve_value * 1e18) / state.myco_supply; // Fully backed if (ba >= 1e18) { return 1e18; } // No backing if (ba == 0) { return params.theta_bar; } // Compute alpha (curvature) uint256 delta = 1e18 - ba; uint256 alpha = (params.alpha_bar * 1e18) / state.myco_supply; if (delta > 0) { uint256 min_alpha = 2 * delta; alpha = alpha > min_alpha ? alpha : min_alpha; } // Compute xu (no-discount zone) uint256 xu = params.xu_bar; if (alpha > 0 && delta > 0) { // xu_from_delta = 1.0 - sqrt(2*delta/alpha) uint256 ratio = (2 * delta * 1e18) / alpha; uint256 sqrt_ratio = _sqrt(ratio); xu = (1e18 - sqrt_ratio) < params.xu_bar ? (1e18 - sqrt_ratio) : params.xu_bar; } // Current redemption level uint256 x = ((state.cumulative_redeemed + redemption_amount / 2) * 1e18) / state.myco_supply; // Determine zone if (x <= xu) { // No discount return ba; } // Parabolic discount (simplified) // rate = ba - x + alpha * (x - xu)^2 / 2 uint256 discount = (alpha * (x - xu) * (x - xu)) / (2e18); uint256 rate = ba > x ? ba - x : 0; rate = rate + discount / 1e18; rate = rate < params.theta_bar ? params.theta_bar : rate; return rate > 1e18 ? 1e18 : rate; } // Hook interface implementations function onRegister( address pool, address factory, bytes memory userData ) external returns (bytes4) { require(pool == bondedPool, "Unknown pool"); return IHooks.onRegister.selector; } function beforeSwap(BeforeSwapParams calldata params) external override returns (bytes memory hookData) { // If swapping OUT of MYCO (redemption), apply discount uint256 redemptionRate = computeRedemptionRate(params.amount); return abi.encode(redemptionRate); } function afterSwap(AfterSwapParams calldata params) external override { // Update state: flow tracking, backing ratio uint256 time_diff = block.timestamp - state.last_redemption_time; uint256 decay = (params.outflowMemory ** time_diff) / 1e18; state.cumulative_redeemed = (state.cumulative_redeemed * decay) / 1e18; state.last_redemption_time = block.timestamp; emit RedemptionRateUpdated( computeRedemptionRate(params.amountOut), block.timestamp ); } function beforeAddLiquidity(BeforeAddLiquidityParams calldata) external pure override returns (bytes memory) { return ""; } function afterAddLiquidity(AfterAddLiquidityParams calldata) external pure override {} function beforeRemoveLiquidity(BeforeRemoveLiquidityParams calldata) external pure override returns (bytes memory) { return ""; } function afterRemoveLiquidity(AfterRemoveLiquidityParams calldata) external pure override {} // Governance functions function updateReserve(uint256 newValue) external onlyGovernance { state.reserve_value = newValue; } function updateParams( uint256 alpha, uint256 xu, uint256 theta ) external onlyGovernance { params.alpha_bar = alpha; params.xu_bar = xu; params.theta_bar = theta; } // Helper function _sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; uint256 z = (x + 1) / 2; uint256 y = x; while (z < y) { y = z; z = (x / z + z) / 2; } return y; } } ``` --- ## Part 4: Pool Factory Example ```solidity pragma solidity ^0.8.24; import { BasePoolFactory } from "@balancer-labs/v3-pool-utils/contracts/BasePoolFactory.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; import "./ECLPBondingCurvePool.sol"; contract ECLPBondingCurveFactory is BasePoolFactory { event PoolCreated( address indexed pool, string name, address[] tokens, uint256[] params ); constructor(IVault vault) BasePoolFactory(vault) {} function create( string memory name, string memory symbol, address[] calldata tokens, uint256 swapFeePercentage, address hooksContract, bytes calldata eclpParamsEncoded ) external returns (address) { require(tokens.length == 2, "Must be 2-token pool"); // Decode E-CLP params (uint256 alpha, uint256 beta, uint256 c, uint256 s, uint256 lam) = abi.decode( eclpParamsEncoded, (uint256, uint256, uint256, uint256, uint256) ); bytes memory creationCode = type(ECLPBondingCurvePool).creationCode; bytes memory constructorArgs = abi.encode( address(_vault), name, symbol, tokens, swapFeePercentage, hooksContract, alpha, beta, c, s, lam ); address pool = _create(creationCode, constructorArgs); emit PoolCreated( pool, name, tokens, abi.decode(eclpParamsEncoded, (uint256[])) ); return pool; } } ``` --- ## Part 5: Testing (Foundry) ### Test E-CLP Invariant Preservation ```solidity pragma solidity ^0.8.24; import "forge-std/Test.sol"; import "../src/ECLPBondingCurvePool.sol"; contract ECLPBondingCurvePoolTest is Test { ECLPBondingCurvePool pool; function setUp() public { // Deploy pool with test params // α = 0.95, β = 1.05, (c,s) = (0.707, 0.707), λ = 1.5 } function testInvariantPreservationOnSwap() public { uint256 x = 1000e18; uint256 y = 1000e18; uint256 invariant1 = pool.computeInvariant(x, y); uint256 amountIn = 100e18; uint256 amountOut = pool.onSwap( PoolSwapParams({ kind: SwapKind.EXACT_IN, tokenInIndex: 0, tokenOutIndex: 1, amount: amountIn, balances: [x + amountIn, y - amountOut], // ... other params }) ); uint256 newX = x + amountIn; uint256 newY = y - amountOut; uint256 invariant2 = pool.computeInvariant(newX, newY); // Invariant should be approximately preserved (allow small rounding error) assertApproxEqRel(invariant1, invariant2, 1e15); // 0.0001% tolerance } function testSpotPriceMonotonic() public { // Increasing X should increase Y price (spot price of Y in X) uint256 price1 = pool.spotPrice(1000e18, 1000e18); uint256 price2 = pool.spotPrice(2000e18, 500e18); // With asymmetric ellipse, price moves smoothly assertTrue(price2 < price1); // Y becomes cheaper as X increases } } ``` --- ## Part 6: Deployment Checklist ### Pre-Launch (Testnet) ``` [ ] Deploy factory on testnet [ ] Create sample pool via factory [ ] Register pool with vault [ ] Link hooks contract [ ] Run full test suite (Foundry) [ ] Compare gas to reference pools (WeightedPool, StablePool, GyroECLP) [ ] Run fuzz tests (Echidna or Foundry) [ ] Verify against Python reference implementation [ ] Integration test with Balancer UI (swaps, joins, exits) ``` ### Pre-Launch (Mainnet) ``` [ ] Security audit (Trail of Bits, OpenZeppelin, etc.) [ ] Bug bounty program ($50K+ coverage) [ ] Community review period (1+ week) [ ] Governance vote (if DAO-controlled) [ ] Reserve initialization (fund vaults) [ ] Set conservative swap fee (e.g., 0.5%) [ ] Low TVL cap (e.g., $100K initial) ``` ### Launch Day ``` [ ] Deploy factory + pools [ ] Mint initial liquidity [ ] Announce on Balancer Discord/Forums [ ] Enable on Balancer UI [ ] Monitor TVL, gas, price stability [ ] Be ready to pause if needed (governance power) ``` --- ## Part 7: Example Deployments ### Community Treasury (Weighted Pool) ```javascript // Using Balancer SDK const MYCO = "0x..."; // Your token const USDC = "0x..."; // Stablecoin const factory = IWeightedPoolFactory("0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc"); const pool = factory.create( "MYCO Treasury LP", "MYCO-LP", [MYCO, USDC], ["600000000000000000", "400000000000000000"], // 60%, 40% "5000000000000000", // 0.5% swap fee "0x0000000000000000000000000000000000000000" // no hooks ); // Mint initial liquidity await vault.joinPool(pool, MYCO, USDC, amounts); ``` ### Token Launch (E-CLP + P-AMM Hook) ```javascript const LAUNCH_TOKEN = "0x..."; const STABLECOIN = "0x..."; // Deploy hook first const pammHook = new SimpleRedemptionHook.deploy( /* reserve, supply, pool, governance */ ); // Deploy E-CLP via factory const eclpFactory = ECLPBondingCurveFactory("0x..."); const params = { alpha: "950000000000000000", // 0.95 beta: "1050000000000000000", // 1.05 c: "707107000000000000", // cos(45°) s: "707107000000000000", // sin(45°) lam: "2000000000000000000" // 2.0 (asymmetric) }; const pool = eclpFactory.create( "LAUNCH E-CLP", "LAUNCH-LP", [LAUNCH_TOKEN, STABLECOIN], "10000000000000000", // 1% launch fee (higher for illiquid) pammHook.address, abi.encode(params) ); // Seed initial liquidity // Ratio biased toward stablecoin (due to λ=2 stretch) ``` --- ## References & Links - [Balancer V3 Docs - Custom Pools](https://docs.balancer.fi/build/build-an-amm/create-custom-amm-with-novel-invariant.html) - [Balancer V3 GitHub - Pool Examples](https://github.com/balancer/balancer-v3-monorepo/tree/main/pkg/pool-stable/contracts) - [GyroECLPPool.sol Source](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/GyroECLPPool.sol) - [Hooks Balancer Directory](https://hooks.balancer.fi/) - [PRBMath Library](https://github.com/paulrberg/prb-math)