Rebalancers: Deep Dive

How the Rebalancer Asset Manager works: why CLPs need rebalancing, trust assumptions (permissioned but non-custodial), the rebalance() function, pool balance verification, liquidity burn, theoretical

Source: Arcadia Finance Blogarrow-up-right

The Rebalancer serves a single purpose: to automate the rebalancing of concentrated liquidity positions (CLPs) when the relative prices of their underlying assets change.

In this post we will outline what Rebalancers are, what the trust assumptions are and how the Rebalancers work.

Why you need Rebalancers

DEXs like Uniswap V3, Uniswap V4, Slipstream, use concentrated liquidity. As a Liquidity Provider you only supply liquidity between a lower and an upper price. If the price moves outside of this range, you will no longer be earning fees as LP.

Hence in order to continue earning fees as Liquidity provider, these CLPs have to be managed and rebalanced so that the amount of fees earned is maximised, while costs (like swap fees, Impermanent Loss (IL) etc.) are minimised.

Finding the optimal strategy that determines when to rebalance, and which new lower and upper price to use, is a complex problem to solve, and it depends on multiple factors:

  • Type of liquidity pool (stable pool, correlated assets, volatile assets...)

  • Market conditions (bullish, bearish, flat)

  • Objectives of the position owner

  • Chain conditions e.g. gas price

Some simple rebalance strategies can be:

  • Rebalance when out of range back to a 50/50 position

  • Trailing strategies: only rebalance when out of range in one direction

  • Rebalance after fixed time periods

But they can (and should) be much more complex:

  • Non-symmetric positions

  • Use variable ranges depending on volatility

  • Increase ranges to avoid IL

  • Multi CLP strategies with different liquidity distributions (gaussian, triangle...)

The Rebalancer is made in such a way that it works with any rebalance strategy. The main logic is strategy agnostic: it takes as input an old position, the new upper price and the new lower price. Optionally, Arcadia Account Owners can impose strategy specific restrictions (such as min/max ranges, cooldown periods etc.) via separate hooks contracts.

Trust Assumptions

The Rebalancer is of the Non-Custodial Permissioned type. It is a smart contract that can only perform a single atomic action (rebalancing CLPs), it never holds assets and can never be used to withdraw assets from an Account.

But it does require input from a permissioned (whitelisted) user (further called the Initiator).

The initiator has to call the Rebalancer contract and has to determine:

  • When to rebalance

  • The range of the new Liquidity Position

A malicious initiator can still only rebalance via the Rebalancer, while it is a trusted role, they can never steal funds or Liquidity from the Account Owner.

The worst a malicious/incompetent Rebalancer can do, is trigger Rebalances at bad moments and move liquidity to non-optimal ranges, resulting in value lost due to swap fees, slippage and opportunity cost. But they can never "rug" the assets of an Account.

Since Initiators can be revoked/replaced at any time by the Account Owner, they are incentivised to rebalance optimally, to keep earning fees for their services.

The Rebalancer is permissioned, but that does not mean it is centralised. Each Account Owner can choose if they enable the Rebalancer at all. And if they do, they can choose who they set as initiator. Different initiators can for instance run different rebalance strategies, or ask different fee amounts for their services.

Rebalancer Implementation

The code of the Arcadia Rebalancer can be found here: https://github.com/arcadia-finance/asset-managers/blob/main/src/cl-managers/rebalancers/Rebalancer.solarrow-up-right

We kept the logic for the Rebalancer as simple as possible, it has a single function to rebalance the position:

Which accepts the following inputs (to be provided by the Initiator):

  • The account for which a CLP has to be rebalanced

  • Information about the old position: positionManager and oldId. The Rebalancer works for different CLAMMs such as Uniswap V3, Slipstream (both staked and unstaked)

  • Information about the new position: tickLower and tickUpper

  • Optionally swapData: the initiator can specify custom swap data (to limit slippage). If not provided, the Rebalancer will use the underlying Pool of the CLP to rebalance.

The Rebalancer will execute a number of checks and actions:

Verifications before the rebalance

The following checks are performed before the rebalance:

  • The Arcadia Account exists

  • The Initiator is whitelisted by the Account Owner

  • Optionally: any strategy specific constraints (such as cooldown periods, range limitations...) are checked on the Strategy Hook contract.

  • Verify that the pool is balanced (see next paragraph).

Verify that the pool is balanced

Before we rebalance, it is important to check that the liquidity pool of our position is balanced.

If this was not the case, a malicious Initiator could execute something similar to a sandwich attack:

  • Bring the pool out of balance (this could be done with a flash loan).

  • Call the Rebalancer, which now creates a new position in an unbalanced pool.

  • Bring the pool back into balance.

If the total active liquidity of the pool is bigger after the rebalance, the profit from bringing the pool in balance is bigger than the cost to bring the pool out of balance.

Note that the Account Owner must have approved this malicious Initiator before this is possible!

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 Rebalancer will fetch the USD-price for token0 (P0usdP_{0 \rightarrow usd}) and token1 (P1usdP_{1 \rightarrow usd}) and check that:

Ppool=P0usdP1usdP_{pool} = \frac{P_{0 \rightarrow usd}}{P_{1 \rightarrow usd}}

Burn the old Liquidity position

This step is straightforward, the liquidity position is decomposed in its underlying assets, and any pending fees (or rewards for staked positions) are claimed.

After burning the old position, the Rebalancer will hold a certain balance0balance_0 and balance1balance_1 of the underlying assets.

Calculate the theoretical Maximum of liquidity that can be added

After the new position is minted, the actual minted liquidity is compared with the theoretical Maximum and should be within acceptable bounds.

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 balance0balance_0 and balance1balance_1, do under most circumstances not match the required ratio, directly adding the balances as liquidity will result in some leftover for either token0 or token1.

To calculate the theoretical Maximum amount of liquidity, LmaxL_{max}, we first rebalance token0 and token1 such that after minting the liquidity position there are no leftovers in either token0 or token1. In order to do this, an amount of token0 has to be swapped into token1 (or opposite).

In the theoretical maximum we assume we can swap without slippage (the current price of the pool, P01P_{0 \rightarrow 1}, remains constant), but we take into account swapping fees.

We can calculate the optimal swap amounts Δamount0\Delta amount_{0} and Δamount1\Delta amount_{1} analytically in the no slippage case.

Derivation of optimal swap amounts

In the no slippage case (constant pool price), the relation between amountinamount_{in} and its corresponding amountoutamount_{out} is given as:

amountout=(1fee)amountinPinout(1)amount_{out} = (1-fee) \cdot amount_{in} \cdot P_{in \rightarrow out} \quad (1)

For new positions that are out of range the optimal swap amounts are straightforward:

  • If the current tick is above the upper tick, all balance0balance_0 has to be swapped to token1.

  • If the current tick is below the lower tick, all balance1balance_1 has to be swapped to token0.

For positions in range, we first calculate the Target ratio:

Rtarget=sqrtPricesqrtRatioLower2sqrtPricesqrtRatioLowersqrtPrice2sqrtRatioUpper(2)R_{target} = \frac{sqrtPrice - sqrtRatioLower}{2 \cdot sqrtPrice - sqrtRatioLower - \frac{sqrtPrice^2}{sqrtRatioUpper}} \quad (2)

And the current ratio:

Rcurrent:=balance1balance0sqrtPrice2+balance1(3)R_{current} := \frac{balance_1 }{balance_0 \cdot sqrtPrice^2 + balance_1} \quad (3)

If we want to mint as much liquidity as possible, both ratios should be equal after the swap:

Rtarget=balance1+Δamount1(balance0+Δamount0)sqrtPrice2+(balance1+Δamount1)(4)R_{target} = \frac{balance_1 + \Delta amount_{1}}{(balance_0+ \Delta amount_{0}) \cdot sqrtPrice^2 + (balance_1+ \Delta amount_{1})} \quad (4)

Combining equations (1), (3) and (4):

If Rtarget>RcurrentR_{target} > R_{current}, we need to swap token0 for token1:

Δamount0=Δamount1(1fee)sqrtPrice2\Delta amount_{0} = -\frac{\Delta amount_{1}} {(1-fee) \cdot sqrtPrice^2}
Δamount1=(RtargetRcurrent)(balance0sqrtPrice2+balance1)1+Rtargetfee1fee\Delta amount_1 = \frac{(R_{target} - R_{current}) \cdot (balance_0 \cdot sqrtPrice^2 + balance_1)}{1 + R_{target} \frac{fee}{1-fee}}

If Rtarget<RcurrentR_{target} < R_{current}, we swap token1 for token0:

Δamount0=(1fee)Δamount1sqrtPrice2\Delta amount_{0} = \frac{(1-fee) \cdot \Delta amount_{1}} {sqrtPrice^2}
Δamount1=(RcurrentRtarget)(balance0sqrtPrice2+balance1)1Rtargetfee\Delta amount_1 = - \frac{(R_{current} - R_{target}) \cdot (balance_0 \cdot sqrtPrice^2 + balance_1)}{1 - R_{target} \cdot fee}

Given the balances after the swap and the required upper and lower price of the range we can calculate the minted liquidity LmaxL_{max} via getLiquidityForAmounts().

This theoretical maximum will be used in a later check after the position is minted, to enforce that the amount of liquidity added is close to the theoretical maximum.

It protects the Account Owner to loss of liquidity due to any of the following attack vectors:

  • Excessive slippage during the swap.

  • Initiator who steals funds during a custom swap.

  • Minting of the new position with unbalanced underlying amounts, resulting in leftovers of either token0 or token1.

Rebalance the underlying assets

To execute the actual rebalance on-chain, there are two options: swap through the pool itself or the initiator can provide a custom router and custom swap data.

The second option is mainly for big positions, or for pools with limited liquidity, where doing the rebalance through the underlying pool might result in excessive slippage (resulting in reverting rebalances, not in funds lost!).

Swaps through the underlying pool: If no custom swap data is provided, the swap will be done through the underlying pool itself. For actual swaps, the no slippage hypothesis no longer holds, since the current price of the pool changes after the swap. A recursive approach is used to approximate the solution, using the analytical no-slippage solution as the initial approximation.

Swaps with custom router: Initiators can provide custom swap data as input, specifying the router contract, the amountIn, and the calldata required by the router. This allows optimizing the routing and minimising swapping fees and slippage.

Mint the new Liquidity Position

The new position will be minted in the same pool (and be staked if the old position was staked).

Verifications after the rebalance

The following checks are performed after the rebalance:

  • The amount of liquidity minted should be very close to the calculated LmaxL_{max}.

  • The pool should still be balanced after the swap.

  • Optionally: any strategy specific constraints are checked on the Strategy Hook contract.

Transfer a fee to the initiator

The reward is calculated as a fixed percentage of the amountIn of the token that had to be swapped.

Taking a fee as a percentage of the amount swapped is a deliberate choice. It works well for very different rebalance strategies:

  • Rebalancing very often, but the amounts that need to be swapped to rebalance the position are limited each time: many small fee amounts for the initiator.

  • Rebalancing as little as possible, but requiring big amounts to be swapped: limited number of big fee amounts for the initiator.

An Arcadia Account can never end up in an unhealthy position at the end of a transaction. This includes the rebalancer. Make sure your Account remains in a healthy state, otherwise it won't be rebalanced.

Overview fees & costs involved with rebalancing

  • Initiator fee: The initiator of the rebalance (who triggers it) receives a 0.05% fee on the amount swapped.

  • Swap fee: The swap fee is at most the fee of the LP pool itself (e.g., a 0.01% pool means a max swap fee of 0.01%).

  • Slippage & leftovers: Slippage and leftovers are capped at 99% of the optimal rebalance (i.e., assuming infinite liquidity and no slippage). Leftovers aren't an actual cost.

  • Impermanent loss (IL): Rebalancing locks in any impermanent loss, making it permanent. However, IL itself isn't a direct cost.

To minimize swap fees and slippage we use optimizers for routing like Odos.

Last updated