Compounders: Deep Dive
How the Compounder Asset Manager works: immutable, stateless, permissionless fee compounding for concentrated liquidity positions. Covers pool balance verification, fee thresholds, fee rebalancing mat
Source: Arcadia Finance Blog
In this post we will outline how the compounders work and what the trust assumptions are.
Compounders are a specific implementation of an Arcadia Asset Manager, hence before we dive deeper into how the compounders work, let's start with explaining what Asset Managers are and how they can be used.
Asset Managers
An Asset Manager of an Arcadia Account is a privileged role that can, as the name implies, manage the assets of an Account. Their main purpose is to enable a wide range of automation for the owner of the Arcadia Account, without the owner having to give up self-custody of their assets.
Each Arcadia Account may have one or more Asset Managers, and only the owner of an Account can add or remove Asset Managers. Asset Managers can perform the following actions:
Deposit assets.
Withdraw assets.
Transfer funds from the Owner.
Execute flash actions (optimistically execute arbitrary logic with the withdrawn/transferred assets).
Any Ethereum address, whether a smart contract or an externally owned account, can be set as an Asset Manager. This opens up a wide range of solutions for users with varying trust assumptions. We can roughly define three models in this context: permissionless, permissioned, and custodial.
Non-Custodial Permissionless: Asset Managers are immutable, trustless smart contracts that can only perform a single action and do not require any additional user input. An example of this are the Compounders that will be described in more detail in this article.
Non-Custodial Permissioned: Asset Managers can be smart contracts that restrict the actions they can perform to a single purpose, but do require user input. An example would be an Asset Manager to rebalance Liquidity Positions. While the contract can only change the range of a position and not, say, withdraw assets, it might require a permissioned role that only triggers a rebalance when it makes sense.
Custodial: Asset Managers can be EOAs that run strategies as a service for users. While there might be off-chain agreements regarding what these managers can and cannot do, these are not enforced on-chain, and the Asset Managers essentially have full power over the Account.
Some examples how asset management can be automated with Arcadia Accounts and Asset Managers are:
Compounding fees
Rebalancing portfolios
Managing Liquidity ranges
Stop losses
Compounders
Uniswap V3 (and similar CLAMMs like Slipstream) do not natively compound the yield earned by liquidity providers. Automatically compounding the yield for these protocols is an effective way to boost returns, leading to an exponential rather than a linear increase in the portfolio's value.
The code of the Arcadia Compounders can be found here: https://github.com/arcadia-finance/asset-managers/blob/main/src/cl-managers/compounders/Compounder.sol
The compounder embodies the essence of true DeFi:
It is immutable.
It is 100% permissionless, with the compounder having no special privileges or admin roles. Anyone can initiate a compound, as long as all the contract-defined conditions are met.
It is stateless, the contract has no storage variables (except a reentrancy lock that is reset at the end of each transaction), but still operates for any liquidity positions in an Arcadia Account.
The contract relies on economic incentives, with the initiator of the compound earning a small reward when compounding the fees of a position.
We kept the logic for the Compounder as simple as possible, it only has a single function compoundFees().
The function only takes two variables as input, the Id of the Liquidity Position and the Arcadia Account that owns the Liquidity Position. That's it, all the logic regarding how to rebalance the fees, how to swap fees etc. is done on-chain.
The asset manager will execute a number of actions and checks, we will go into detail how each of these steps is done, and why they are necessary:
Verify that the pool is balanced
Before we compound the fees, it is important to check that the liquidity pool of our position is balanced.
If this was not the case, an attacker could execute something similar to a sandwich attack:
Bring the pool out of balance (this could be done with a flash loan).
Call our compounder, which now adds the yield in an unbalanced pool.
Bring the pool back into balance.
Since the total liquidity of the pool is bigger after the yield is added, the profit from bringing the pool in balance is bigger than the cost to bring the pool out of balance.
To check if a pool is balanced, we need to compare the current price of the pool (which can be manipulated) with the expected price, based on the trusted price feeds of both underlying assets. Luckily all that pricing logic is already implemented in the Arcadia Registry!
The compounder will fetch the USD-price for token0 (P0→usd) and token1 (P1→usd) and check that:
Collect the fees earned by the position
This step is straightforward, an amount of fee0 and fee1 is collected by the compounder.
The initiator will take a share of the fees collected, as a reward for executing the transaction. For the current Compounders, the reward for the initiator is 1% of the fees collected.
Verify that the fee value is bigger than the threshold required to trigger a compound
For too small values, rounding errors might give issues and we don't want to spam the sequencer with negligible value transactions.
Hence we check if the total value of the fees exceeds a certain Threshold, Tmin, ($5 for the current Compounders).
This extra check is almost for free anyway, since we already have the USD-prices of the underlying assets from step 1!
Rebalance the fee amounts so that the maximum amount of liquidity can be added
When adding liquidity to a Uniswap V3 position, token0 and token1 (the underlying tokens) have to be added in a certain ratio depending on the lower and upper tick of the liquidity position and on the current price of the pool.
The amounts of fee0 and fee1, do under most circumstances not match the required ratio, directly adding the collected fees as liquidity will result in some leftover for either token0 or token1.
In order to maximise the amount of liquidity that can be added, the compounder will first rebalance the fee amounts to match the ratio required by Uniswap V3.
For positions out of range this is straightforward:
If the current tick is above the upper tick, all fee0 has to be swapped to token1.
If the current tick is below the lower tick, all fee1 has to be swapped to token0.
For positions in range, we first need to calculate if we need to swap token0 to token1 or opposite, and next we need to know exactly how much to swap.
To do this we first calculate the ratio of how much of the total value of a liquidity position has to be provided in token1, based on the upper tick, the lower tick and the current pool price. We call this the Target ratio, since this ratio maximises how much fees we can add as liquidity:
If we price all values in token1:
For a Uniswap V3 liquidity position, all three unknowns (amount0, amount1 and P0→1) can be expressed in terms of sqrtRatios of the current Pool price (sqrtPrice), the lower tick (sqrtPriceLower), and the upper tick (sqrtPriceUpper), which are all known variables on-chain.
For P0→1:
For amount0:
For amount1:
Rewriting our ratio in these terms finally gives us:
Next we calculate the current ratio of the value of fee1 compared with the total value of the fees:
If we want to compound as much fees as possible, both ratios should be equal (so that there are no leftover token0 and token1 after increasing liquidity).
If Rtarget=Rfees, we need to swap token0 and token1 to bring Rfees in balance. The amount of token1 that needs to be swapped equals:
A positive Δamount1 means we have to swap token0 for token1, a negative we have to swap token1 for token0.
The calculations above assume zero slippage (and fees) for the swap. In reality all swaps do have fees and slippage. To protect the owner of the Liquidity Positions we use swaps with a fixed amountOut instead of a fixed amountIn. This results in the initiator getting less rewards instead of the owner getting less liquidity.
Verify that the slippage of the rebalance is within a defined tolerance
We have seen at the end of the previous step that slippage does not result in the owner getting less liquidity, but in the initiator getting less rewards.
Still there is another problem with slippage, since we swap in the pool of the Liquidity Position itself, a lot of slippage might result in the pool no longer being in balance!
Therefore we limit the max slippage so that the pool is still balanced after the swap.
Increase the liquidity of the current position
With all checks passing and the fee amounts balanced, we can finally add the fees to the Liquidity position.
Transfer a fee to the keeper that initiated the compound
Lastly, the reward is transferred to the initiator (1% of collected fees minus slippage and other costs related to the swap).
Last updated