Greetings dear readers! We continue our series of instructive posts with some special recommendations for developers using Balancer V1 integration.
We believe there is no one who doubts that the basis of any secure integration is a special approach to writing code. Consequently, this article will be focused only on those aspects that can be really useful for making your code safe and secure!
Check out our previous article:
First things first, we would like to express our heartfelt gratitude to the Balancer designers, everyone who supports it, the authors of all resource materials, and, of course, our in-team auditors who have assisted us by providing much-needed information and breaking the veil of secrecy!
In it, users can ultimately create pools using two or more crypto assets of their choice. Much like other notable AMMs, Balancer routes its trades through any liquidity pools needed to secure the best rates for users.
In other words, it allows users to instantly swap tokens and earn fees when they provide liquidity to different pools; various Web3 teams utilize it to integrate it to their own projects, and if done incorrectly, it can lead to attacks and losses of user’s funds, which should never be allowed!
In this article, we present tips that we have acquired from almost two years of auditing Balancer V1 integrations into DeFi protocols, you will also find a list of tools and research for self-study, we strongly recommend that you read it separately for better understanding.
Let’s get started! We hope you find today’s article informative and helpful!
Following the tips below can significantly improve the security of your integration:
The pool contract stores the fees percentage in a variable. This value should not be cached in order to save gas because the pool owner can alter it at any time!
However, if pool.getOwner() returns address(0), the pool commission is static.
It is highly inefficient if the integrating protocol only employs single swaps (Vault.swap). When integrating, the protocol should use batch swaps because they are generally cheaper than single swaps and open up more potential pairs — there is sometimes just no pool with two selected tokens.
For pools with TWAP functionality (Weighted2Tokens and MetaStablePool) you should not use pool.getLatest() function, because it relies on current reserves that are easily manipulated. Use pool.getTimeWeightedAverage() instead!
Vault Queries (queryBatchSwap, queryJoin, queryExit) cannot be used to calculate limits (maximum or minimum values) in the same transaction where swap (pool entry or exit) is executed, otherwise that transaction will be vulnerable to sandwich attack and query will return values based on the attacker transaction.
Furthermore, swap transactions from other users can influence the price (slippage) significantly, and querying in the same transaction with a swap will not protect against this!
When interacting with pools via Vault, bytes32 pool_id is passed — and it’s important to keep in mind that this is not the pool address, converted into bytes32. The on-chain id must be obtained via pool.getPoolId().
The weights of the tokens in a pool are scaled by 10¹⁸. As follows, a value of 0.8*10¹⁸ will be stored for a 80% weight. If there are any calculations with weights, you should not forget to divide by modifier 10¹⁸, if it is not reduced when dividing two weights.
When calculating the output value at a swap, the input value must include the pool fees. We would also suggest in this point to think about how to specify minReturn when calling the function (from which token the fee is taken, at what point, etc.)
We strongly advise you to review this list of fantastic tools and resources separately for a better understanding:
We hope that this article was informative and useful for you! Thank you for reading!
What instruments should we review? What would you be interested in reading about?
Check out our previous article:
Please leave your comments, we will be happy to answer them, and the best answers and questions may be included in the next article!