In recent times, the staggering growth in the Defi ecosystem has given rise to several revolutionary projects that have taken the world by storm. Traditional finance concepts are being recreated in the Defi space in a more efficient, trustless, and secure manner. Among these projects, the idea of automated market-making (AMM) has gained considerable traction. Uniswap, Balancer, Curve.fi are some of the projects based on AMMs.
This article will dissect the core functionalities and technical implementation of Balancer Finance, one of the top AMM’s in the DeFi space which also acts as a portfolio manager and a price sensor. We will try to explain each of these functions in much detail as we progress with the dissertation.
Before we delve into the complexities of the protocol, let's try and understand the unique proposition that Balancer offers to its users.
Like any other decentralized exchange, Balancer allows liquidity providers to deposit their assets in liquidity pools and earn trading fees as people use their assets. However, Balancer also provides an additional utility to LPs in the form of the composition of its asset pools. Unlike Uniswap, a Balancer pool can contain up to 8 (16 in Balancer V2) different ERC-20 tokens, each with a unique distribution. The fact that these liquidity pools do not have to maintain a 50-50% ratio between tokens gives investors an option to diversify their liquidity across several assets. For instance, a 3-token Balancer pool may comprise 25%ETH, 25%DAI, and 50%USDC; users can independently add liquidity in any of these assets.
Apart from offering diversification by creating pools with multiple tokens and different ratios, Balancer can also act as an automated portfolio manager for liquidity providers. The Balancer is currently among the top 10 decentralized exchanges operating on the Ethereum blockchain in market capitalization1. The initial idea of the protocol stems from the concept of index funds. In traditional finance, index funds represent a portfolio of assets designed to imitate the performance of a financial market index2. It guarantees investors a controlled exposure to their asset portfolios by hedging risk and achieving diversification. The fund manager usually buys or sells assets based on their performance to keep the total value of the asset’s share in the portfolio constant.
However, investors have to incorporate custodial risk and pay fees to these portfolio managers for managing, holding, and rebalancing their assets. Balancer Finance provides a one-stop decentralized solution to such investors; instead of paying fees for rebalancing their portfolios, they can earn fees from traders who rebalance the portfolio following arbitrage opportunities. As the prices of assets change, traders sense the opportunity to make an easy profit. As a result, they carry out trades until the asset’s price reflects its actual market price.
Three main categories of traders can benefit from the protocol, retail traders- who look for low slippage costs and favorable rates to exchange their tokens, arbitrageurs- who trade to eliminate the market inefficiencies between centralized and decentralized exchanges and Ethereum smart contracts- who seek liquidity for a variety of reasons such as trading on behalf of users, liquidating positions on other protocols operating on the blockchain, etc3.
Since each pool contains a diversified set of tokens with different dynamics and use cases, users are provided with unique investment opportunities to earn a high yield on their assets. The specific layout of tokens, weights, and fees designed by Balancer makes investment opportunities more attractive for traders.
The exchange function of Balancer shares a similar approach to the one introduced by Uniswap V2. It is based on an N-dimensional surface and defined by constraining a value function “V” to a constant. N-dimensions represent the number of tokens in the pool. Each n is a liquidity curve that is based on the constant value V. Since this function represents all the weights and balances of tokens in a liquidity pool and is pegged to an invariant, it doesn’t allow the share value of a particular asset in the pool to change, regardless of the nature of trades. As a result, the value of all the tokens in the pool is preserved.
The value function is mathematically represented as:
-The mathematical notation large Pi or (“𝚷”) represents the product of the given function over a set of terms;
-“t” represents a range of tokens in the pool;
-“Bt” indicates the balance of the token in each liquidity pool;
-“Wt” is the normalized weight of the token or its share in the pool. The sum of all normalized weights is 1 or 100%.
In a theoretical context, the spot price is defined as the price in the marketplace pertinent to exchanging a particular pair of tokens at a given time and place. In simple terms, it is the ratio between a pair of tokens.
The above illustration, however, represents the spot price as calculated in Uniswap V2. In Balancer, the spot price of each pair of tokens is not only defined by the balances of the tokens but also their respective weights in the liquidity pool. The unique distribution of weights of tokens in Balancer adds a slight extension to the spot price calculation. The spot price between any two tokens, SPio, in Balancer, is calculated as:
Since the weights of tokens in a liquidity pool are kept constant by the protocol, the spot price can only be affected through changing token balances. There are only two ways token balances in a pool can be affected; they can either change if the liquidity provider adds or removes liquidity or when a trade is carried out through those tokens. In the case of a trade, the constant surface causes the price of tokens being bought by the trader (token “o”) to increase and the price of tokens being sold by the trader (token “i”) to decrease.
As explained earlier, as soon as the token prices deviate from external market prices, arbitrageurs come into play. Trades are carried out until the token price is again reflective of the market price or, in other words, when all the profit opportunities have been eliminated. These arbitrageurs make sure that the token prices offered by Balancer are in accordance with the rational market prices.
Notice that the calculation is similar to Uniswap V2, with just the weights of two tokens being taken into account. If we were to use this formula in Uniswap, a 50-50 distribution of assets would not have impacted the calculation. Both the weights would eventually cancel out, leaving a simple ratio of token balances. However, due to the unique distribution of tokens in a Balancer pool, it is important to incorporate the normalized weights of tokens in the spot price calculation.
To know more about spot price proof, refer to Balancer’s whitepaper.
Whenever a trader occurs in a liquidity pool, a small fee is charged to the trader, which is called Swap Fee. This fee is in the form of a certain percentage of input token amount and ranges between 0.0001% to 10%. In the case of Balancer, the amount of fees earned by the pool belongs to the liquidity providers; the exchange does not charge any additional fees at the protocol layer. However, there is a placeholder in the code for ‘exit fee,’ which is currently set to zero.
The fee which is going to be charged for the trade is incorporated in the spot price; this strategy is referred to as charging fees “on the way in”. Also, since the swap fee is a percentage of the input tokens, it acts as a multiplier and increases the spot price. The new spot price formula is now given as:
Where, “𝜙” represents the swap fee within a percentage range 0.0001% to 10%, depending on the configurations of the liquidity pool.
Effective price represents a change in the spot price at the time of trade due to a change in the amount of tokens. One might think the effective price is the same as spot price, but there is a slight difference between the two. Spot price acts as a theoretical price for infinitesimal trades- very small trades with minimal impact on price. However, in reality, trades that do not affect token balances by much are very rare. Most of the trades in a liquidity pool would affect token balances and cause a price change. Therefore effective price calculation serves as an appropriate tool to gauge this price change. Assume “Ai” is the amount of token ‘i’ being sold by the trader and “Ao” is the amount of token “o” being bought by the trader, then the formula for calculating effective price is given as:
Since effective price reflects the change in trading amounts, the effective price becomes equal to the spot price as they approach closer to zero. The equation shows:
Note that the concept of effective price is just included to establish a better framework for the changing token prices, it has no practical implementation in the protocol.
As mentioned earlier, Balancer pools maintain a constant share of value across all tokens, and these shares are equivalent to the weights of each token. To prove this claim, the protocol first determines the total pool value in terms of a single token and then divides the value of each token by the total pool value to obtain the share of each token. This sounds quite tedious, let’s try and illustrate the point through an example.
Consider a Balancer liquidity pool; for simplicity, assume there are only two assets in the pool, i.e., ETH and DAI, with 20% and 80% shares, respectively. Further, assume that the pool has 1000 tokens, 200 ETH, and 800 DAI according to the given weights. The example would consider DAI as token ‘t’ and ETH as token ‘n’.
To determine the total pool value using a single token “t”, the protocol first calculates how many tokens “t” are all other remaining tokens’ n’ in the pool worth. The general formula for this calculation is given as:
Where, ‘Wn’ and ‘Wt’ represent the respective weights of the tokens and ‘Bt’ denotes the balance of token ‘t’. Now let’s get back to the example; suppose we want to find out how much DAI is the other token in the pool, i.e., ETH, worth. We can calculate this by dividing the weights of ETH and DAI and then multiplying it by the total balance of DAI; the result will be 200ETH. In this way, we can use a single token to determine the balances of all other tokens in the pool.
Similarly, we can calculate the total pool value using a single token ‘t’, since it is the sum of values of each token in terms of ‘t’. The general formula is given as:
Here, ‘t’ represents the token “DAI”. If we substitute the values from the example under consideration in the above formula, we can obtain the total pool value, i.e., 1000 tokens.
Now, to find the share of each token ‘n’, the protocol simply divides the value of each token ‘n’ by the total pool value. The result is the share of that particular token in the liquidity pool.
Following the illustration, substitute the value of ETH in the pool as 200 and divide it by the total pool value, i.e., 1000 tokens. The result would be the share of ETH, i.e., 0.2 or 20% in the given ETH/DAI liquidity pool. This proves that the share of each token in the pool remains constant and that it is equal to the weight of that token.
Note: To know more about the mathematics of the formulas defined above, refer to the whitepaper.
In Balancer, the unique distribution of assets in each pool calculates trade outcomes a little complex. Before a trade takes place, the trader must know both, i.e., the amount of tokens the user is sending in “Ai” and the amount of tokens he will be receiving “Ao”. As a result, there are two separate calculations whenever an exchange takes place.
Before making a trade, it is useful for the trader to know the amount he will be receiving on giving up a particular amount of tokens. The formula to calculate the amount of tokens received “Ao” is given as:
The formula shown above is derived on the assumption that the value function remains constant before and after the trade. However, this is not the case; in reality, each trade in the pool incorporates a swap fee, which increases the value of the constant function. To take this fee into account, a different formula is used where “Ai” is replaced by “Ai . (1- swapFee)”, it is given as:
Similarly, the protocol also calculates the amount of tokens that the users needs to send in order to receive their desired tokens. The formula without swap fee is given as:
Again to incorporate the swap fee replace “Ai” with “Ai . (1- swapFee)” and since the calculation is for “Ai”, move the multiple (1- swapFee) to the other side of the equation. The new formula is:
The role of arbitrageurs is well-defined by the protocol, due to which it is successfully able to keep track of the market prices. To incentivize arbitrageurs so that they perform their role, the protocol provides a calculation before the trade to show the amount of tokens that arbitrageurs must send to enforce a spot price change from the current spot price SPio to a desired one SPi’o . This helps the trader determine the amount of tokens they have to give to reflect a spot price change that would bring the contract price closer to the external market price. Profit opportunities are eliminated as soon as the contract price is equal to the market price. This core functionality also makes Balancer a reliable on-chain price sensor.
The amount of tokens ‘i’ that the trader needs to trade against tokens ‘o’ to achieve a desired level of spot price ‘SPi’o’ is given by the equation:
Also, read Flash Ecosystem: An In-Depth Explanation.
Whenever liquidity providers deposit any liquidity in a balancer pool, they are given pool tokens or LP tokens which represent the share of their assets in the pool. The supply of these LP tokens is directly proportional to the value function of that particular pool. For instance, if an asset deposit increases the value function by 10%, the supply of LP tokens will also increase by 10%. This happens because those 10% newly minted pool tokens are issued to the liquidity provider in return for the deposit.
Balancer provides an added utility to its liquidity providers by offering single asset deposit/ withdrawals apart from the normal weighted asset deposit/withdrawals. An explanation of both of these ways to add or remove liquidity is provided in this section.
All-asset deposit/withdrawal is based on the existing distribution of assets in the pool. If a user wants to add liquidity, he needs to deposit a proportional amount of each of the assets in the pool. So, for each asset ‘k’ the user has to deposit ‘Dk’ tokens in order to receive ‘Pissued’ LP tokens from an existing supply of ‘Psupply’ is given as:
Where “Bk” is the token balance of ‘k’ before the deposit.
The reverse operation is applied if the users want to redeem their proportional share of assets in the pool. So, for each asset ‘k’ the amount of tokens that the user gets ‘Ak’ by redeeming their pool tokens “Predeemed” is given by:
Here “Bk” is the token balance of ‘k’ before the withdrawal. Note that the redeemed amount of tokens are subtracted from the total supply because upon withdrawal the user receives pool tokens alongwith their initial shares of deposited liquidity.
Considering the unique distribution of assets in Balancer pools, there is a chance that the user might not have all the assets in the required proportions to carry out the weighted asset deposit procedure. To cater to these investors, Balancer allows liquidity providers to earn pool tokens by depositing their liquidity in a single asset in the pool, provided that the pool contains that asset. How are the proportions kept constant if only a single asset is being deposited or withdrawn, Balancer takes care of this concern by performing trades at the back-end until the pool is rebalanced. For instance, if a particular asset, say the liquidity provider, is depositing asset A, the protocol would sell more asset A to get back all other tokens in the prior proportions.
The amount of pool tokens issued to the user ‘Pissued’ in return for his deposit of ‘At’ tokens of a single token ‘t’ in the pool is given by the equation:
The above formula is derived on the assumption that there are no swap fees. This is because depositing only a single asset in the pool could serve as a swap for the protocol: depositing token A and immediately withdrawing some other token B. Also, liquidity providers receive a share of the entire pool of assets after depositing only a single asset, it could work as a trade since depositing a certain amount of tokens gives them a share of all other tokens in the pool. Due to these reasons, a swap fee is charged.
The fee is applied to the remaining portion of the weights in the pool i.e., At . (1-Wt), this is because ‘Wt’ represents the weight of the single token deposited by the liquidity provider and is already accounted for. The formula to find the amount of pool tokens received on depositing a single token ‘t’ then becomes:
Similarly, to find the amount of single token ‘t’ to be deposited for a defined amount of pool tokens can be calculated by:
After taking the swap fees into account we have:
Similarly, the withdrawal formula without swap fees is just the reverse of the deposit formula. It is given as:
Here ‘At’ is the amount of token one receives when redeeming “Predeemed” tokens. With swap fees, the formula changes to:
The implementation is similar to the one described for single asset deposits.
Balancer also allows liquidity providers to withdraw a desired token ‘t’ from the pool and calculate the amount of pool tokens required to carry out the redemption trade. The formulas with and without swap fees are given below:
Balancer offers a lot of flexibility to both liquidity providers and traders in terms of its asset pools. This section will talk about the variety of liquidity pools that the protocol has to offer.
Public pools, also called finalized pools, have fixed parameters, including asset types, weights, and fees. These are the pools that you would typically find on a regular decentralized exchange. Public pools allow users to add liquidity and trade tokens freely. Also, their implementation is a lot easier and cost-efficient than other pools.
Private pools, also called controlled pools, are designed to cater to investment portfolios. Users can form a different combination of assets, assign different weights, and set fees as per their own peculiarities. The protocol also offers the flexibility to change these configurations; the owner (person who has created the pool) can change fee structures, asset proportions, etc. However, since private pools hand over the control to their owner, they would not be categorized as trustless. They would demand additional trust requirements from the perspective of traders.
A smart pool is a hybrid version of public and private pools as they are both flexible and trustless. Users can add liquidity in them, and the parameters can be made fixed or dynamic depending on the functioning of the smart contract.
Governance tokens are usually introduced to create alignment between asset holders and protocol stakeholders. To cater to the Balancer community’s participation in the protocol and to establish strong governance, Balancer has also introduced its governance token called ‘BAL’. The native token does not serve as an investment but rather a reward for interacting with the protocol and contributing to its future development.
The total supply of BAL tokens is capped at 100 million. The distribution depends on the protocol's governance; it can be stopped before the end cap is reached. The total supply of BAL tokens available for liquidity providers is 65M. Every week, 145,000 BAL tokens are distributed to liquidity providers, which corresponds to approximately 7.5M a year. At this rate, it would take 8.66 years for the token to reach its supply limit. The current distribution of BAL tokens, excluding liquidity providers, is carried out in the following way:
This section explores the codebase except for self-explanatory functions such as getters and setters.
The newBPool function is called to create a new pool. Along with creating the pool, the function sets the controller of that pool, which would be the creator, i.e. msg.sender.
This function invokes the BPool contract. See section 9.2
The factory contract is owned by a pre-specified address, denoted by _blabs variable. The _blabs address can set the variable’s value to a new address by using setBLabs function.
When the BPool contract is invoked, the constructor sets the default value for parameters of the pool. These parameters include the owner, factory address, swap fee amount, publicSwap, and finalized.
The _publicSwap variable, which has a bool data type, refers to whether the function swap is allowed to be called or not. In contrast, the variable _finalized refers to whether the public can swap or join the pool. Furthermore, these pools have fixed pool asset types, weights, and fees.
By public, it means anyone and everyone apart from the controller, who already has all these rights.
The controller, i.e., owner address, can set these values.
The controller address then binds the tokens with the pool, meaning initializing the token address in that pool, enabling the users and pool joiners to deposit tokens in their pool.
The bind function calls the rebind function, which implements two modifiers.
First, the log modifier, which logs the signature of the message, i.e., msg.sign, address of message sender, i.e., msg.sender, and the data if there is any, i.e., msg.data.
The second, the lock modifier, which prevents reentrancy.
Then the rebind function adjusts the denormalized weight and total weights. Notice that denormalized weights are denoted by denorm, and they denote the portion of the pool allocated to tokens.
Now, while the whitepaper regards weights in terms of percentage. In most cases, if one weight% changes all others have to change too. Since that would cost a lot more in terms of gas to update, Balancer used arbitrary values. What it means is that the weights of the tokens are initialized at the time of binding, and after that, if the balance is changed in the pool, it has no effect on the denorm weight. The maximum total denormalized weight of the pool is 50e18. The value of denorm is 0 when the pool is created. However, the controller can use this function to reinitialize the denorms as well with respect to each token.
Along with allocated denorms, the controller also gives balance. If the balance is more than the old balance that means the controller is increasing the balance share of that specific token, and if it is less then, the controller is withdrawing the balance. The bind function deals with this parameter accordingly. Then, transfers the token balance either to/from the message sender or in case of exit fee, to the factory address.
Just as the controller binds the tokens, the controller can unbind the tokens as well. The function swaps the token to be unbound with the last token then deletes the last token via pop, and finally transfers its balance.
The contract has the functionality to absorb all tokens sent to the address of the pool contract as well. To do so the gulp function is invoked. If the received tokens are bound with the pool, it will simply gulp the tokens. This is to ensure that the balance of the pool matches the reserves of the token in the pool.
LPs can join an initialized pool and provide liquidity to that pool for the desired amount of pool tokens. One thing to notice is that LPs can provide liquidity in finalized pools only. The function joinPool is used for this purpose. With respect to ratio, the provided input tokens are pulled from the msg.sender and added to the respective token’s balance.
Finally, pool tokens are minted, representing the LP’s pool share, and transferred to LPs address. The standard of these LP tokens is ERC20. Keep in mind that LPs provide all ‘n’ tokens of the pool in this function.
Just as LPs join a pool and provide liquidity, they may exit a pool and withdraw the provided liquidity, provided the poolAmountIn i.e. amount of input pool tokens. The function exitPool is utilized for this. It is pretty similar to the joinPool function except tokens were added to the balance of the pool and then transferred from LPs address to the pool’s address. Here, the reverse happens, tokens are subtracted from the balance of the pool and then transferred from the pool address to LPs address. Just like joinPool function this function also moves around all n tokens of the pool.
What is really different here is the exit fee. The exit fee is calculated and sent to the factory address. The EXIT_FEE variable denotes the protocol fee, which however is 0, but can be changed.
The pool contract also has the swapExactAmountIn function which enables traders to provide input tokens and get output tokens of equivalent value. Users can swap less than 50% of the current balance of tokens in the pool. Exceeding this amount will revert the transaction. The output token amount is calculated via the calcOutGivenIn function. Then input and output amounts are transferred respectively.
Similarly, if the trader wants to get a specific amount of output tokens, they may call the swapExactAmountOut function. This function uses the calcInGivenOut function to calculate the input amount of tokens. Users can’t swap out more than 1/3rd of the balance of the tokens. Then tokens are transferred accordingly.
Balancer allows LPs to provide liquidity to a pool with one single asset. Given a single input asset, the function joinswapExternAmountIn calculates the amount of pool tokens with the function calcPoolOutGivenSingleIn. This amount is transferred to the LP after LPs add liquidity to the pool. Arbitrageurs readjust the balances, balancers do not auto-adjust the balances.
Similarly, for a specified amount of pool tokens, LPs can provide liquidity with a single asset, for which the function joinswapPoolAmountOut is used. The function calculates the input amount of tokens via the calcSingleInGivenPoolOut function. It then mints the pool tokens. Then transfers the input tokens and pool tokens to their respective destinations.
Just as LPs join the pools and provide liquidity with one single asset, they may do the same to exit the pool, meaning they can receive single output assets while exiting the pool through the exitswapPoolAmountIn function. For a given amount of pool tokens, the LPs will receive the desired single output asset. The amount of this output asset is calculated via the calcSingleOutGivenPoolIn function. Then LPs provide the pool tokens which are burned for output tokens.
Similarly for a given amount of output tokens, the function exitSwapExternAmoutOut calculates the pool token amount to be received. Then makes the necessary transfers.
Balancer protocol has limitations as well as capped values, all of which are declared in this contract. The contract is simple and easy to understand. The limitations of the protocol are described well here.
Also, read the detailed study on Chainlink.