Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/.vuepress/public/images/BPTOracles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions docs/concepts/core-concepts/balancer-pool-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ By doing this, the BalancerPoolToken contract ensures that Balancer Pool Tokens
## Composability

As BPTs adhere to the ERC20 standard, they can seamlessly integrate as pool tokens in other pools. For instance, the BPT of an ERC4626 pool comprising wrapped versions of DAI, USDC, and USDT can be paired with tokens from new projects. This composability ensures the maintenance of deep and capital-efficient stable liquidity, while simultaneously creating efficient swap paths for the project token.

## Oracles

If Chainlink price feeds are available for all tokens, Weighted and Stable Pool BPTs can be priced in USD terms using the corresponding LP Oracle contracts: [WeightedLPOracle](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/standalone-utils/contracts/WeightedLPOracle.sol) and [StableLPOracle](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/standalone-utils/contracts/StableLPOracle.sol), which implement [this interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/standalone-utils/ILPOracleBase.sol) as well as the `AggregatorV3Interface` Chainlink price feed interface. This allows users to call `latestRoundData` on the oracle contract to get an accurate, non-manipulable BPT price, just as if it were a Chainlink oracle itself.

Note that it would be possible to generalize these contracts to support other kinds of price feeds; the only function of the price oracle is to fetch the current market prices. The pricing algorithm and all logic is contained in the LP oracle code (and mostly all in the base contracts).

The most common use for these contracts is enabling Balancer BPT to be used as collateral on lending platforms. See the [BPT as Collateral](./bpt-oracles/bpt-oracles.md) docs for more details.
40 changes: 40 additions & 0 deletions docs/concepts/core-concepts/bpt-oracles/bpt-oracles-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
order: 4
title: BPT Oracle contracts
---
# Overview

We have implemented oracles for both Weighted and Stable Balancer pools. (ReCLAMM pools should also work, as they are fundamentally Weighted pools, just incorporating virtual balances.) These are in the standalone-utils package: `WeightedLPOracle` and `StableLPOracle`, and associated factories.

![Inheritance Diagram](/images/BPTOracles.png)

## Oracle Factories

Oracles are created from the corresponding pool factories using the create function:

`function create(IBasePool pool, AggregatorV3Interface[] memory feeds) external returns (ILPOracleBase oracle);`

`AggregatorV3Interface` is a Chainlink price feed. Note that this assumes the feed array is parallel to the pool tokens; i.e., the feed at each position is the correct one for the corresponding pool token. There is no way for the code to check this, so responsibility falls on the caller to ensure that this is the case. See the [Usage with Rate Providers](./bpt-oracles.md#usage-with-rate-providers) section for further considerations. Note that there are no restrictions in the factories that limit oracle creation, so any mistakes can be remedied by simply calling create again with correct values.

This first computes an "OracleID" as the hash of the pool and price feed addresses. Since the tokens must be ordered, we know the price feed addresses will also be in the same order, so a given combination of the pool and price feeds is guaranteed unique. It then calls an internal `_create` (implemented by derived contracts) to deploy the oracle with the given configuration, and registers it with the factory.

The base factory contract [interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/standalone-utils/ILPOracleFactoryBase.sol) defines functions to find an oracle given pool and feed addresses, and check whether a given oracle was deployed by the official factory:

```
function getOracle(
IBasePool pool,
AggregatorV3Interface[] memory feeds
) external view returns (ILPOracleBase oracle);

function isOracleFromFactory(ILPOracleBase oracle) external view returns (bool success);
```

## Oracle contracts

The common factory contract [interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/standalone-utils/ILPOracleBase.sol) defines:

`function calculateTVL(int256[] memory prices) external view returns (uint256 tvl);`

This function computes the total value of the pool, solving the pool and price constraints simultaneously as described in the [main article](./bpt-oracles.md#derivation).

Just as the price feeds implement the Chainlink interface `AggregatorV3Interface` to fetch individual token prices, the common factory [contract](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/standalone-utils/contracts/LPOracleBase.sol) also implements this interface, and calling `latestRoundData` invokes the virtual `calculateTVL` function to get the total value, then divides by the total supply to get the BPT price.
112 changes: 112 additions & 0 deletions docs/concepts/core-concepts/bpt-oracles/bpt-oracles-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
order: 4
title: BPT Oracle Numerical Example
---
# Numerical example
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page is simply awesome!


Consider a Stable Pool containing USDC and USDT, where USDT has slightly de-pegged (say, to 0.98).

p₁ = $1.00 (USDC), p₂ = $0.98 (USDT)<br>
A = 100, D = 1000 (invariant)

Step 1: Calculate the parameters (amplification coefficient, and "helper" constants derived from the Amplification parameter)

a = A × n^(2n) = 100 × 2^4 = 1600<br>
b = a - n^n = 1600 - 2^2 = 1596<br>
c = b/a = 1596/1600 = 0.9975

Step 2: Calculate the r values (scaled prices)

r₁ = p₁/a = 1.00/1600 = 0.000625<br>
r₂ = p₂/a = 0.98/1600 = 0.0006125

Step 3: Find the root, using Newton's method:

Choose the starting point: k₀ = (1 + 1/(1+b)) × ρ

k₀ = (1 + 1/(1+1596)) × 1633 = (1 + 1/1597) × 1633 ≈ 1634.02

Newton's method:<br>
kₙ₊₁ = kₙ − G(kₙ) / G′(kₙ); where

G(k) = T(k)³ × P(k) - α<br>
G'(k) = T²(k) × P(k) × [(3T'(k) × P(k) + T(k) × P'(k))/P(k)]

And:
T'(k) = -r₁/(kr₁-1)² - r₂/(kr₂-1)²<br>
P'(k) = P(k) × [r₁/(kr₁-1) + r₂/(kr₂-1)]

First iteration (k₀ = 1634.02)

Calculate T(1634.02):

T = 1/(1634.02×0.000625-1) + 1/(1634.02×0.0006125-1) - 1<br>
T = 1/(1.0213-1) + 1/(1.0008-1) - 1<br>
T = 1/0.0213 + 1/0.0008 - 1 = 46.95 + 1250 - 1 ≈ 1295.95

Calculate P(1634.02):

P = (1634.02×0.000625-1) × (1634.02×0.0006125-1)<br>
P = 0.0213 × 0.0008 ≈ 0.000017

Calculate G(1634.02): the "error"

G = (1295.95)³ × 0.000017 - 1588.03<br>
G = 2.17×10⁹ × 0.000017 - 1588.03 ≈ 36,890 - 1588 ≈ 35,302

Calculate derivatives and find k₁:

T' = -0.000625/(0.0213)² - 0.0006125/(0.0008)² ≈ -1380 - 956 ≈ -2336<br>
G' ≈ ... (complex calculation) ≈ 50.8

k₁ = 1634.02 - 35,302/50.8 ≈ 1634.02 - 694.9 ≈ 939.1

Continue iterations:
| Step | k̃ₙ | G(k̃ₙ) | k̃ₙ₊₁ |
| ---- | ------- | ------ | ------- |
| 0 | 1634.02 | 35,302 | 939.1 |
| 1 | 939.1. | -285.4 | 1425.7 |
| 2 | 1425.7 | 892.1 | 1580.3 |
| 3 | 1580.3 | 124.7 | 1635.8 |
| 4 | 1635.8 | 8.2 | 1640.1 |
| 5 | 1640.1 | 0.1 | 1641.0 |
| 6 | 1641.0 | ~0 | 1641.0 |

Graphical representation of the T curve:

![Find the root](/images/BPTOracleIllustration.png)

Step 4: Calculate T (using our found k̃ = 1641)

Recall that T = 1/(k̃r₁ - 1) + 1/(k̃r₂ - 1) - 1

T = 1/(1641 × 0.000625 - 1) + 1/(1641 × 0.0006125 - 1) - 1<br>
T = 1/(1.025625 - 1) + 1/(1.0051125 - 1) - 1<br>
T = 1/0.025625 + 1/0.0051125 - 1<br>
T = 39.02 + 195.60 - 1 = 233.62

Step 5: Calculate effective balances

Recall that x₁ = (cD)/(k̃r₁ - 1) × T⁻¹

x₁ = (0.9975 × 1000)/(1641 × 0.000625 - 1) × (1/233.62)<br>
x₁ = 997.5/0.025625 × (1/233.62) = 166.69

x₂ = (cD)/(k̃r₂ - 1) × T⁻¹<br>
x₂ = (0.9975 × 1000)/(1641 × 0.0006125 - 1) × (1/233.62)<br>
x₂ = 997.5/0.0051125 × (1/233.62) = 833.31

Step 6: Calculate total pool value

Recall that Pool Value = p₁ × x₁ + p₂ × x₂

Pool Value = $1.00 × 166.69 + $0.98 × 833.31<br>
Pool Value = $166.69 + $816.64 = $983.33

Step 7: Calculate BPT price

Recall that BPT Price = Pool Value / Total BPT Supply

(If total BPT supply = 1000 tokens, since D = 1000, and starting prices were nominal at $1)

BPT Price = $983.33 / 1000 = $0.983 per BPT
Loading