Rate Providers

Overview

Rate Providers are contracts that provide an exchange rate between two assets. These exchange rates can come from any on-chain source, whether that may be an oracle, a ratio of queryable balances, or another calculation.

Rate Providers implement a getRate() function that returns an exchange rate.

Use Cases

You can use rateProviders for all, some, or none of the assets in your pool. If you are not using a rateProvider for an asset, you must pass the zero address (0x0000000000000000000000000000000000000000), which will result in a rate of 1.

All Assets

You will want to use rateProviders for all assets in your pool when each asset has its own price that is independent of all the other assets' prices. If we have tokens A, B, and C and only have price feeds with respect to USD, then we would want all assets to have price feeds. When internally calculating relative prices, the USD would cancel out, giving us prices for A:B, A:C, B:C, and their inverses.

Some Assets

You will want to use rateProviders for some assets in your pool when you have rates that directly convert between the assets. If we have tokens A and B and a rate provider that gives the price of A with respect to B, then the rateProvider corresponding to token A would get the A:B price feed, and the rateProvider corresponding to token B would be the zero address.

None of the Assets

You will have no rateProviders in your pool when your tokens are price-pegged to each other. For example, a pool with USDC, USDT, and DAI would have all rateProviders set to the zero address since the exchange rate between those tokens is 1.

Examples

Direct Balance Query

Wrapping rebasing tokens, such as stETH, makes them compatible with Balancer, but knowing the exchange rate between the underlying rebasing token and the wrapped token is necessary to facilitate StableSwap swaps. As such, the wstETH rateProvider has a getRate() function that calls wstETH's own stEthPerToken() function. See the contract hereopen in new window.

Oracles

Using oracles for price feeds is a simple way to determine an exchange rate. There are two example contracts for how to use Chainlink as a price source: ChainlinkRegistryRateProvideropen in new window and ChainlinkRateProvideropen in new window.

ChainlinkRegistryRateProvideropen in new window

This contract makes use of Chainlink's registry contract so it can handle Chainlink migrating to a new price feed for a given asset pair. Though there are increased gas costs for this, it's a tradeoff for ensuring the pool doesn't get stuck on an abandoned price feed. While this is an unlikely scenario, it doesn't hurt to be careful.

ChainlinkRateProvideropen in new window

If you're running on a network where 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.

Implementation

The Balancer Vault stores each token's rate in the PoolData struct.

struct PoolData {
    PoolConfigBits poolConfigBits;
    IERC20[] tokens;
    TokenInfo[] tokenInfo;
    uint256[] balancesRaw;
    uint256[] balancesLiveScaled18;
    uint256[] tokenRates;
    uint256[] decimalScalingFactors;
}

Each time a swap, addLiquidity or removeLiquidity operation is performed, the token's rate is read to guarantee accurate results. The tokenRates from PoolData are internally updated through the reloadBalancesAndRates function, which is called as needed by all swap, addLiquidity, and removeLiquidity operations. As mentioned in token scaling, data from the external Rate Provider contract is read and stored.