Currently, underlying assets for alloyed asset has limiter to limit its risk from bridge/chain hack but there is no incentives for any actor to act on if any of the asset hitting the limit or reaching 0.
The goal is to ensure that if a swap moves the asset composition closer to a set limit on the bridge/chain, fee will be charged. However, if the swap moves the asset composition towards balance, it will receive an incentive.
| symbol | definition |
|---|---|
| upper limit, |
|
| ideal balance upper bound, |
|
| ideal balance lower bound, |
|
| critical balance upper bound, |
|
| critical balance lower bound, |
|
| base fee/incentive rate within |
|
| base fee/incentive rate within |
| symbol | definition |
|---|---|
| balance before swap, |
|
| balance after swap, |
|
| Total balance in the liquidity pool normalized by standard normalization factor. |
All the vectors are
Each asset will have their own
check out this interactive visualization to see how it behaves visually
def compute_v(
b: float,
b_prime: float,
B_total: float,
phi_l: float,
phi_u: float,
kappa_l: float,
kappa_u: float,
delta: float,
r_s: float,
r_c: float,
) -> float:
"""Compute fee or incentive adjustment for a single asset's balance movement.
This function calculates the incentive/rebate (if positive) or fee (if negative)
for a swap that moves an asset's balance from b to b_prime. The goal is to
encourage movements toward the ideal balance range [phi_l, phi_u] and
discourage movements away from it.
"""
# Define the five zones with their respective fee/incentive rates
# The zones create a graduated incentive structure:
# - Critical zones (highest rates): Danger zones where we strongly want to avoid
# - Strained zones (moderate rates): Warning zones approaching critical levels
# - Ideal zone (zero rate): Target range where we want balances to be
# This creates a "gravity well" effect pulling balances toward ideal range
zones = [
(0.0, kappa_l, r_c), # critical low: [0, kappa_l) - highest incentive to move out
(kappa_l, phi_l, r_s), # strained low: [kappa_l, phi_l) - moderate incentive to move up
(phi_l, phi_u, 0.0), # ideal zone: [phi_l, phi_u] - neutral, no fees or incentives
(phi_u, kappa_u, r_s), # strained high: (phi_u, kappa_u] - moderate incentive to move down
(kappa_u, delta, r_c), # critical high: (kappa_u, delta] - highest incentive to move out
]
v = 0.0 # Initialize total fee/incentive accumulator
# Process each zone separately because a single swap can cross multiple zones
# Example: A swap from 15% to 35% might cross critical low → strained low → ideal
# Each zone contributes its portion with its specific rate
for z_start, z_end, rate in zones:
# Get the portion of the swap path that overlaps with this zone
# This ensures we only account for movement within each specific zone
seg_start, seg_end = get_segment_overlap(b, b_prime, z_start, z_end)
if seg_end <= seg_start:
continue # No overlap with this zone, skip to next
# Determine if movement in this zone is toward ideal (+1), away from ideal (-1), or neutral (0)
# The direction logic considers:
# - If we're below ideal and moving up → good (+1)
# - If we're above ideal and moving down → good (+1)
# - If we're below ideal and moving further down → bad (-1)
# - If we're above ideal and moving further up → bad (-1)
# - Movement within ideal zone → neutral (0)
direction = get_direction(b, b_prime, z_start, z_end, phi_l, phi_u)
# Calculate the contribution from this zone:
# - segment_length: How much of the balance change happened in this zone
# - rate: The fee/incentive rate for this zone (higher for critical zones)
# - direction: Whether this movement is beneficial (+1) or harmful (-1)
# The multiplication gives us a signed value where:
# - Positive = incentive/rebate to be given (movement toward ideal)
# - Negative = fee to be charged (movement away from ideal)
segment_length = seg_end - seg_start
v += direction * rate * segment_length
# Scale by total pool balance to convert from percentage-based calculation
# to actual token amounts. This makes fees/incentives proportional to pool size:
# larger pools → larger absolute fees/incentives for the same percentage movement
return v * B_totalwhere
def get_segment_overlap(b: float, b_prime: float, z_start: float, z_end: float) -> tuple[float, float]:
"""Returns the overlapping segment [seg_start, seg_end] of the swap path within the given zone.
When a balance moves from b to b_prime, it traces a path. This function finds
what portion of that path falls within a specific zone [z_start, z_end].
This is crucial because different zones have different fee/incentive rates.
"""
# The swap path is from min(b, b_prime) to max(b, b_prime)
# regardless of direction (increasing or decreasing balance)
swap_start = min(b, b_prime)
swap_end = max(b, b_prime)
# Find the intersection of swap path with the zone
# seg_start: The later of swap start or zone start
# seg_end: The earlier of swap end or zone end
seg_start = max(swap_start, z_start)
seg_end = min(swap_end, z_end)
# If seg_end <= seg_start, there's no overlap (will be handled by caller)
return seg_start, seg_end
def get_direction(b: float, b_prime: float, z_start: float, z_end: float, phi_l: float, phi_u: float) -> int:
"""Returns +1 if movement in zone is toward ideal, -1 if away, 0 if neutral.
This function determines whether a balance movement within a specific zone
should be encouraged (toward ideal balance) or discouraged (away from ideal).
The ideal range is [phi_l, phi_u], and we want to incentivize movements
that bring the balance closer to or keep it within this range.
"""
if b == b_prime:
return 0 # No movement means no fee or incentive
# Given the zones in `compute_v`, a zone is always either
# entirely below, entirely above, or exactly matching the ideal range.
# We classify the zone's position relative to this ideal range.
is_below_ideal = z_end <= phi_l # Zone is entirely below ideal
is_above_ideal = z_start >= phi_u # Zone is entirely above ideal
is_ideal_zone = z_start == phi_l and z_end == phi_u # Zone exactly matches ideal range
# If we're in ideal zone, any movement is neutral
if is_ideal_zone:
return 0
# For rightward movement (increasing balance)
if b_prime > b:
# If we're below ideal, moving right (up) is good - approaching ideal
if is_below_ideal:
return +1
# If we're above ideal, moving right (up) is bad - moving away from ideal
if is_above_ideal:
return -1
# Otherwise we're in a zone that partially overlaps with ideal - neutral
return 0
# For leftward movement (decreasing balance)
if b_prime < b:
# If we're above ideal, moving left (down) is good - approaching ideal
if is_above_ideal:
return +1
# If we're below ideal, moving left (down) is bad - moving away from ideal
if is_below_ideal:
return -1
# Otherwise we're in a zone that partially overlaps with ideal - neutral
return 0This will give, for each pool asset, the normalized fee/incentive amount. Then we calculate
If
When a swap involves adding or removing liquidity (e.g., swapping alloyed assets in or out), B_total changes. This requires a nuanced approach to determine whether to use B_total before the swap (B_total_before) or after the swap (B_total_after) for the fee/incentive calculation.
A hybrid approach is used to ensure both fairness and security:
-
If the swap is beneficial (results in an incentive): The calculation is scaled by
B_total_before.- Justification: Incentives are paid from a pool of previously collected fees. It is logical to scale the reward based on the state of the pool before the user's helpful contribution. This provides a fair reward relative to the pool's history and prevents a single large, helpful deposit from draining a disproportionate amount of the incentive fund.
-
If the swap is harmful (results in a fee): The calculation is scaled by
max(B_total_before, B_total_after).- Justification: This provides maximum security for the pool.
- For harmful joins (liquidity addition), the fee is based on
B_total_after. This ensures the penalty is proportional to the new, larger pool size that the user has unbalanced. - For harmful exits (liquidity withdrawal), the fee is based on
B_total_before. This ensures the penalty is proportional to the state of the pool before it was damaged by the withdrawal.
- For harmful joins (liquidity addition), the fee is based on
- Justification: This provides maximum security for the pool.
This model robustly protects the pool against destabilizing actions while ensuring that incentive payouts are scaled conservatively and fairly.
This is a consideration per asset, meaning, each compute_v can have different B_total.
Swaps need to conform either SwapExactAmountIn or SwapExactAmountOut and we need to consider how that effects our fee/incentive calculation, token to be collected as fee into incentive pool and token to be distributed as incentive.
-
SwapExactAmountInneeds to keep the amount in constant, because if we deduct fee from amount in, the$v$ calculation will have circular dependency. So we deduct the amount out and put that into incentive pool. -
SwapExactAmountOutneeds to keep the amount out constant for the same reason as above, so we need to factor in the fee amount into amount in, meaning, if$a_{in}$ is used to compute$\sum{v}$ , effective amount in that user eventually have to pay is$a_{in} + fee$ , and collect that fee portion into incentive pool.
The simpliest solution here is to make the pool in debt to the swapping account and allow claiming later.
The reason here is that, we can't control or predict the token that will end up in the incentive pool, only its value that we have some control over. Because a swap can, for example:
SwapExactAmountInfrom A -> B- make A's position worse
- make B's position better
- total is worse, so collect fee
- fee collected in B (token out)
as oppose to the same balance shift but SwapExactAmountOut instead, the collected fee will be in A.
It's the same effect to the liquidity pool but it's collecting different token to the incentive pool (eventhough the total value is the same).
If we always have enough amount to pay incentive as token out (on SwapExactAmountIn) or as subsidizing token in (on SwapExactAmountOut), that would be ideal, because it's obvious to user (and any kind of swap route provider) when a swap is incentivized, but we can't gurantee that.
So making the pool in debt, and the interacting account can later claim their incentive in any token available in the incentive pool is simpliest and most effective given we are building bot to do the rebalancing.
Total debt must never exceed total incentive pool, if such case happen when calculating an incentive, deduct the excess incentive from that swap.
If
is less than
This reads: total incentive that needs to pay out when all the out-of-ideal-zone balance moved into ideal zone.
Then raise all fee rate to
When dealing with asset groups, we need to consider how balance changes affect both individual assets and their groups. Let's consider a scenario with:
- Asset group
$G$ containing denoms$d_1$ and$d_2$ - A separate denom
$d'$ not in group$G$
The fee/incentive calculation follows these rules:
-
Intra-group swaps (e.g.,
$d_1 \leftrightarrow d_2$ ):- The group-level calculation
$v_G(b_G, b'_G) = 0$ always - This is because swapping between assets in the same group doesn't affect the group's overall balance
- Individual asset calculations
$v_{d_1}$ and$v_{d_2}$ are still performed as normal
- The group-level calculation
-
Inter-group swaps (e.g.,
$d_1 \leftrightarrow d'$ ):- Calculate group-level effect:
$v_G(b_G, b'_G)$ - Calculate individual asset effects: $v_{d_1}(b_{d_1}, b'{d_1})$ and $v{d'}(b_{d'}, b'_{d'})$
- The final fee/incentive is the sum of these values
- Note: This means the same balance change can contribute to both group-level and individual asset-level incentives
- Calculate group-level effect:
This approach allows us to:
- Maintain separate incentive mechanisms for both individual assets and their groups
- Ensure that balance changes are properly incentivized at both levels
- Handle cases where a swap might be beneficial for one level but harmful for another
When any assets marked as corrupted, override the following params for those assets:
If it's a corrupted asset group, all underlying assets in the the asset group gets overwritten the same way as above.
Corrupted in the incentive pool also require claiming until it reaches 0
This will automatically incentivize all action that decrease corrupted assets.