BasePoolcontract provides you with all the key mechanics to build out a pool with the high standards one would expect from a pool coming from Balancer Labs. If you see a pool based on this base contract, you know it’s no joke! To prove my claims, let’s jump right into it.
BasePoolcontract comes with the following base functionality.
BasePoolinherits from the
TemporarilyPausablecontract which provides an emergency pause feature within the first 30 days of factory deployment. Once the 30 days are over and a pool is still paused, it remains paused for an additional maximum of 90 days until it gets automatically unpaused. A paused pool cannot take any trades, joins or exit. It’s literally halted!
Vaultcontract, which many would rank as one of the most innovative building blocks from Balancer Labs. Unfortunately it doesn’t make sense to get into the details of it now because it’s just too amazing and deserves its own spotlight. So I’ll have to cover it in a later piece, no way around that! But for now you have to live with my short summary:
Vaultholds all pool tokens and performs the related bookkeeping. It serves as a single entry point for pool interactions like joins, exits and swaps and delegates to the respective pools. With that in mind, let’s see how the
BasePoolintegrates with the
Vaultwith its specialization in return for a
poolId. We’ll dig into pool specialization and
poolIdgeneration another time, otherwise we’ll get nowhere. In short, the pool specialization is for gas optimizations when swapping and the
poolIdencodes the BPT address with its specialization for more gas optimizations. Now that we are hooked up to the
Vault, it will call our liquidity management hooks,
onExitPoolwhich leads us to our next topic.
BasePoolprovides some base functionality for adding and removing liquidity. We start with the
onJoinPoolhook, which also acts as a pool initializer if it’s the first join.
Vaultright? We see that this is enforced by the
msg.senderis actually the
Vaultand if the provided
poolIdis actually the
poolIdof this pool. Fair enough, rather not mess that up! Knowing that this call is indeed coming from the
Vaultwe can assume that the arguments require no further validation. See the comments on the arguments for some context. We also check if the pool is not paused
_ensureNotPaused()and with that out of the way, we are ready for some business logic. Right away, we see that there is a different execution branch when
totalSupply == 0meaning this is the first join ever and therefore the pool initialization flow. We should’t skip this section cause there is some interesting details to uncover!
_onInitializePool(..)handler which has to be implemented by the implementing pool. The handler has to return
_getMinimumBpt(), which defaults to
1e6BPT, to the zero address acting as a buffer for rounding errors and preventing the pool from ever getting completely drained. Neat! The remaining BPT are minted to the recipient.
scalingFactors = _scalingFactors()where
_scalingFactors()has to be overridden by the implementing contract. So what are those scaling factors? Well once more, its about decimals. Not all tokens have the same amount of decimals, so to make the math work, all tokens get normalized to 18 decimals. The
BasePoolprovides a helper function for that
_downscaleUpArray(amountsIn, scalingFactors). Like the name suggests, it downscales the
scalingFactorswhere the result is scaled up, therefore
downscaleUp. Now this call reveals that the
amountsInreturned from the implementing contract have to be upscaled, which is also stated in the NatSpec
Vaulttogether with an empty array for the due protocol fees.
elsebranch which represents the regular joins.
balancesare the total token balances in the
Vaultfor this pool which are normalized to 18 decimals. We then delegate to the implementation with the
_onJoinPoolcall which returns us the amount of BPT to mint as well as the normalized
amountsIn. Same as with the pool initialization flow, we mint the BPT to the recipient and return the downscaled (or denormalized)
amountsIntogether with an empty array for the due protocol fees back to the
upscaleArray(balances, scalingFactors). Nothing new here, we are starting to feel comfy in here aren’t we? We then delegate to the implementation, receiving the BPT to burn and the
amountsOut. We downscale the
amountsOutso they are ready to be returned to the
Vault. Finally, we burn the BPT and return the
amountsOuttogether with an empty array for the due protocol fees to the
Vault. Easy enough! But now, what happened to those protocol fees? Let’s dive into it!
protocolFeesCollectorby piggybacking on joins and exits. That is why the
Vaultaccepts a second parameter in the return value of joins and exits, the due protocol fee. But now that we are returning a hardcoded empty array, how are those fees paid? Well, there is a new function available
BasePool, so I looked around and its for the implementing pools to figure out a gas efficient way of keeping track of the due BPT to be paid as as protocol fees and call this handler. We’ll see more about that once we dive into specific pool implementations!
BasePoolcontract, we can safely say that it’s an amazing building block for creating pools utilizing the Balancer ecosystem. There are some pitfalls and one still needs to understand a good chunk of the Balancer tech to safely integrate a new pool, but once you know the fundamentals, it’s almost easy!