smart-contracts/backlog/docs/INTEGRATION_REFERENCE.md

678 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)