Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add rateProvider specifics #1

Merged
merged 38 commits into from Jun 26, 2023
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
44b22fd
docs: add rateProvider specifics
May 17, 2023
9502b41
add yield fee calculation.
May 25, 2023
07a2f73
change rate provider applicability
May 25, 2023
5b45c71
docs: spelling for rate Provider
May 30, 2023
9b3d4f1
docs: added clarity
May 30, 2023
7a14925
docs: use correct balances values
May 30, 2023
f33f06d
docs: spelling of tokens
May 30, 2023
b9cb11e
Merge branch 'balancer:main' into rate-provider-additions
mkflow27 May 30, 2023
a844d19
docs: address some comments
mkflow27 Jun 1, 2023
4987734
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
9690b8e
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
2ef6a0f
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
965a881
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
4920080
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
a2d1d81
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
17eff16
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
d01c0f6
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
91325fa
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 1, 2023
6dcd723
docs: remove unnecessary line
Jun 5, 2023
6edec5a
docs: spelling
Jun 5, 2023
25dc967
docs: display as math expression
Jun 7, 2023
310bbd3
docs: formatting change
Jun 7, 2023
add96c6
docs: clearer wording of differences
Jun 7, 2023
e60065f
docs: remove unnecessary text
Jun 7, 2023
fdf3be7
docs: reposition note on scaling
Jun 7, 2023
b05ee55
docs: update to yield fee section
Jun 7, 2023
0088176
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 9, 2023
221032d
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 9, 2023
7469f76
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 9, 2023
d70b490
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 9, 2023
a22a987
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 9, 2023
7add4c8
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 9, 2023
9cb4b0d
docs: format code snippets
Jun 9, 2023
1ea9ab0
Merge branch 'balancer:main' into rate-provider-additions
mkflow27 Jun 12, 2023
c2c1f19
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 26, 2023
5d4105a
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 26, 2023
0bda0ff
Update docs/reference/contracts/rate-providers.md
mkflow27 Jun 26, 2023
c528c5d
docs: make note more concise
Jun 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
170 changes: 170 additions & 0 deletions docs/reference/contracts/rate-providers.md
Expand Up @@ -39,3 +39,173 @@ This contract makes use of Chainlink's registry contract so it can handle if Cha
#### [`ChainlinkRateProvider`](https://github.com/balancer/metastable-rate-providers/blob/master/contracts/ChainlinkRateProvider.sol)

If you're running on a network for which Chainlink doesn't have a registry and you think the risk of a deprecated price feed is low enough, then you can use the rateProvider that directly queries a given Chainlink oracle.

## Application of Rate Providers By Pool Type

Different types of pools utilize rate providers in different contexts.

| PoolType | Yield Fee | Pricing Equations|
| ----------- | ----------- | ----------- |
| Composable Stable Pool | ✅ | ✅ |
| Meta Stable (EOL) | ✅ | ✅ |
| Weighted Pool | ✅ | ❌ |
| Managed Pool | ✅ | ❌ |
| Liquity Bootstrapping Pool | ❌ | ❌ |

::: note RateProvider Usage
While both Stable and Weighted Pools can have RateProviders, only Stable Pools use them to determine token prices.
- Stable: raw token balances are scaled by the RateProvider values before being fed into StableMath
- Weighted: token rates are used only to determine growth to calculate yield protocol fees
:::

Composable Stable Pools and Meta Stable Pools have different implementations for the scaling operations but the outcome is the same.


### Composable Stable Pool Implementation
Scaling Example:

```
function _scalingFactors() internal view virtual override returns (uint256[] memory) {
// There is no need to check the arrays length since both are based on `_getTotalTokens`
uint256 totalTokens = _getTotalTokens();
uint256[] memory scalingFactors = new uint256[](totalTokens);

for (uint256 i = 0; i < totalTokens; ++i) {
scalingFactors[i] = _getScalingFactor(i).mulDown(_getTokenRate(i));
}

return scalingFactors;
}
```

### Composable Stable Pool Swap Example

Looking at a swap of [WETH to sfrxETH](https://etherscan.io/tx/0x72d756d0fcd663343ca1b2adcfc7e114e8598bc0be28386752f16222384a29b3) the pool balances are upscaled.
| Token | balances | rate | scaled balance |
| ----------- | ----------- | ----------- | ----------- |
| BPT - 0x5aee1e99fe86960377de9f88689616916d5dcabe | 2596148429265848431954582359320590 | 100000000000000000 | 2596148429265848431954582359320590 |
| wstETH - 0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 | 6998331684674570187580 | 1123265399574462175 | 7860983836140600107855 |
| sfrxETH - 0xac3e018457b222d93114458476f3e3416abbe38f | 7388958961745977404526 | 1038371994655823641 | 7672488055538194248508 |
| rETH - 0xae78736cd615f374d3085123a210448e74fc6393 | 5950507951882438548950 | 1069881935087994199 | 6366340962316480428138 |




The upscaled token balances are are fed into `_calcOutGivenIn`.
```
ComposableStablePool._calcOutGivenIn
(
amplificationParameter = 200000,
balances = [7860983836140600107855,7672488055538194248508,6366340962316480428138],
tokenIndexIn = 1,
tokenIndexOut = 0,
tokenAmountIn = 163517835854389679,
invariant = 21899336949210774987256
)
=> (163536626614409153)
```

More details on this specific transaction can be found [here.](https://dashboard.tenderly.co/tx/mainnet/0x72d756d0fcd663343ca1b2adcfc7e114e8598bc0be28386752f16222384a29b3?trace=0.4.2.2.19.11.0.3.0.5)

### Meta Stable Pool Implementation
Scaling Example:

```
function _scalingFactor(IERC20 token) internal view virtual override returns (uint256) {
uint256 baseScalingFactor = super._scalingFactor(token);
uint256 priceRate = _priceRate(token);
// Given there is no generic direction for this rounding, it simply follows the same strategy as the BasePool.
return baseScalingFactor.mulDown(priceRate);
}
```

### Meta Stable Pool Swap Example

Looking at a swap of 50 ETH to 46.68 rETH the pool balances are upscaled.



| Token | balances | rate | scaled balance |
| ----------- | ----------- | ----------- | ----------- |
| rEth 0xae78736cd615f374d3085123a210448e74fc6393 | 20040415915824227571764 | 1070121751154609309 | 21445684973708525874136 |
| WETH 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 | 21953505292747563228232 | 1000000000000000000 | 21953505292747563228232 |

The upscaled token balances are fed into `_calcOutGivenIn`

```
MetaStablePool._calcOutGivenIn
(
amplificationParameter = 50000,
balances = [21445684973708525874136,21953505292747563228232],
tokenIndexIn = 1,
tokenIndexOut = 0,
tokenAmountIn = 49980000000000000000
)
=> (49954807369169689518)

```

More details on this specific transaction can be found [here.](https://etherscan.io/tx/0x67f477517acf6e0c91ec7997e665ca25d2806da060af30272876742584f0aa21)

::: note Scaling
Bear in mind that the tokens used for demonstration in these examples all have 18 decimals and Balancer natively uses 18 decimals for internal accounting. If tokens have different decimals, the scaled balances scale with the tokens decimals as well.

:::

## Yield Fee

Rate providers play a crucial role in determining whether yield fees are charged during pool joins and exits. The pool's `_athRateProduct` and its dynamically calculated `rateProduct` are key factors that determine this.

`rateProduct` is calculated as the weighted product of all current rates:
### Yield Fees for WeightedPools
| Token | Weight | rate |
| ----------- | ----------- | ----------- |
| A (yield bearing) | 0.3 | 1.01 |
| B (non yield bearing) | 0.5 | 1.00 |
| C (yield bearing) | 0.2 | 1.05 |


`rateProduct` = $(0.3 * 1.01) * (0.5 * 1) * (0.2 * 1.05) = 1.013$

As part of the calculation of the `rateProduct`, the `rateProvider` of the pool tokens are queried for their rates.

```
/**
mkflow27 marked this conversation as resolved.
Show resolved Hide resolved
* @notice Returns the contribution to the total rate product from a token with the given weight and rate Provider.
*/
function _getRateFactor(uint256 normalizedWeight, IRateProvider provider) internal view returns (uint256) {
return provider == IRateProvider(0) ? FixedPoint.ONE : provider.getRate().powDown(normalizedWeight);
}
```
More details on the implementation can be found [here.](https://github.com/balancer/balancer-v2-monorepo/blob/cbce7d63479dafb4f4ea9ad8cb2dbdbb26edae50/pkg/pool-weighted/contracts/WeightedPoolProtocolFees.sol#L304)

There are several scenarios in which no yield fees are paid during a pools join or exit operation. Below are a couple of examples:

- `rateProduct` remain unchanged: If multiple joins or exits occur without any factors contributing to a `newATHRateProduct`, no yield fees are minted.
- Insignificant `rateProduct` fluctuations between tokens: In some cases, the rate of one token may increase while the rate of another token decreases. If, on a normalized basis, the rate increase of Token A is less than the rate decrease of Token B, the calculated `rateProduct` would not reach the ceiling of `ATHRateProduct`. As a result, no yield fees would be paid during the pools join or exit.


Yield fees being minted indicated by positive return values of
```
WeightedPool._getYieldProtocolFeesPoolPercentage
(
normalizedWeights = ["800000000000000000","200000000000000000"]
)
=>
(1074881576209740, 978863663373687295)
```
More details on this specific transaction be found [here.](https://dashboard.tenderly.co/mkflow/project/tx/mainnet/0x9e1d45013f4b65f444bb9b2ef823c0d4fd0a53e2b2bad85ba85a8e26c0bed45d?trace=0.2.7.3.7.1.2.2)

No yield fees being minted indicated by zero return values of
```
WeightedPool.getYieldProtocolFeesPoolPercentage
(
normalizedWeights = ["800000000000000000","200000000000000000"]
)
=>
(0, 0)
```
Whereas in this [transaction](https://dashboard.tenderly.co/mkflow/project/tx/mainnet/0xe2d8d7f705d23c18e7d25e68bf01ac2544cc73806c8e4572135d9bc16790b4f5), the `ATHRateProduct` did not increase
and `(0,0)` is [returned](https://github.com/balancer/balancer-v2-monorepo/blob/cbce7d63479dafb4f4ea9ad8cb2dbdbb26edae50/pkg/pool-weighted/contracts/WeightedPoolProtocolFees.sol#L191) since `rateProduct <= athRateProduct`. This indicates that no yield fees were paid in this transaction due to the insufficient increase in the rateProduct.