Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invariant rate checks #81

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/src/add_liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def add_liquidity(add_liquidity_input, pool_state, pool_class, hook_class, hook_
max_amounts_in_scaled18,
pool_state["totalSupply"],
pool_state["swapFee"],
pool_class.get_maximum_invariant_ratio(),
lambda balances_live_scaled18, rounding: pool_class.compute_invariant(
balances_live_scaled18, rounding
),
Expand All @@ -71,6 +72,7 @@ def add_liquidity(add_liquidity_input, pool_state, pool_class, hook_class, hook_
bpt_amount_out,
pool_state["totalSupply"],
pool_state["swapFee"],
pool_class.get_maximum_invariant_ratio(),
lambda balances_live_scaled18, token_index, invariant_ratio: pool_class.compute_balance(
balances_live_scaled18, token_index, invariant_ratio
),
Expand Down
37 changes: 33 additions & 4 deletions python/src/base_pool_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def compute_add_liquidity_unbalanced(
exact_amounts,
total_supply,
swap_fee_percentage,
max_invariant_ratio,
compute_invariant,
):
# /***********************************************************************
Expand Down Expand Up @@ -47,6 +48,12 @@ def compute_add_liquidity_unbalanced(
# Calculate the new invariant ratio by dividing the new invariant by the old invariant.
invariant_ratio = div_down_fixed(new_invariant, current_invariant)

# Add check for max invariant ratio
if invariant_ratio > max_invariant_ratio:
raise ValueError(
f"InvariantRatioAboveMax {invariant_ratio} {max_invariant_ratio}"
)

# Loop through each token to apply fees if necessary.
for index in range(len(current_balances)):
# // Check if the new balance is greater than the equivalent proportional balance.
Expand Down Expand Up @@ -104,17 +111,23 @@ def compute_add_liquidity_single_token_exact_out(
exact_bpt_amount_out,
total_supply,
swap_fee_percentage,
max_invariant_ratio,
compute_balance,
):
# Calculate new supply after minting exactBptamount_out
new_supply = exact_bpt_amount_out + total_supply

invariant_ratio = div_up_fixed(new_supply, total_supply)
# Add check for max invariant ratio
if invariant_ratio > max_invariant_ratio:
raise ValueError(
f"InvariantRatioAboveMax {invariant_ratio} {max_invariant_ratio}"
)

# Calculate the initial amount of the input token needed for the desired amount of BPT out
# "divUp" leads to a higher "new_balance," which in turn results in a larger "amountIn."
# This leads to receiving more tokens for the same amount of BTP minted.
new_balance = compute_balance(
current_balances, token_in_index, div_up_fixed(new_supply, total_supply)
)
new_balance = compute_balance(current_balances, token_in_index, invariant_ratio)
amount_in = new_balance - current_balances[token_in_index]

# Calculate the taxable amount, which is the difference
Expand Down Expand Up @@ -199,17 +212,26 @@ def compute_remove_liquidity_single_token_exact_in(
exact_bpt_amount_in,
total_supply,
swap_fee_percentage,
min_invariant_ratio,
compute_balance,
):
# // Calculate new supply accounting for burning exactBptAmountIn
new_supply = total_supply - exact_bpt_amount_in

invariant_ratio = div_up_fixed(new_supply, total_supply)
# Add check for min invariant ratio
if invariant_ratio < min_invariant_ratio:
raise ValueError(
f"InvariantRatioBelowMin {invariant_ratio} {min_invariant_ratio}"
)

# // Calculate the new balance of the output token after the BPT burn.
# // "divUp" leads to a higher "new_balance," which in turn results in a lower "amount_out."
# // This leads to giving less tokens for the same amount of BTP burned.
new_balance = compute_balance(
current_balances,
token_out_index,
div_up_fixed(new_supply, total_supply),
invariant_ratio,
)

# // Compute the amount to be withdrawn from the pool.
Expand Down Expand Up @@ -253,6 +275,7 @@ def compute_remove_liquidity_single_token_exact_out(
exact_amount_out,
total_supply,
swap_fee_percentage,
min_invariant_ratio,
compute_invariant,
):
# // Determine the number of tokens in the pool.
Expand All @@ -275,6 +298,12 @@ def compute_remove_liquidity_single_token_exact_out(
compute_invariant(new_balances, Rounding.ROUND_UP), current_invariant
)

# Add check for min invariant ratio
if invariant_ratio < min_invariant_ratio:
raise ValueError(
f"InvariantRatioBelowMin {invariant_ratio} {min_invariant_ratio}"
)

# Taxable amount is proportional to invariant ratio; a larger taxable amount rounds in the Vault's favor.
taxable_amount = (
mul_up_fixed(
Expand Down
8 changes: 8 additions & 0 deletions python/src/pools/stable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
compute_out_given_exact_in,
compute_in_given_exact_out,
compute_balance,
_MAX_INVARIANT_RATIO,
_MIN_INVARIANT_RATIO,
)
from src.swap import SwapKind

Expand All @@ -12,6 +14,12 @@ class Stable:
def __init__(self, pool_state):
self.amp = pool_state["amp"]

def get_maximum_invariant_ratio(self) -> int:
return _MAX_INVARIANT_RATIO

def get_minimum_invariant_ratio(self) -> int:
return _MIN_INVARIANT_RATIO

def on_swap(self, swap_params):
invariant = compute_invariant(self.amp, swap_params["balances_live_scaled18"])

Expand Down
5 changes: 5 additions & 0 deletions python/src/pools/stable_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
# we have chosen the rounding direction to favor the protocol in all cases.
AMP_PRECISION = int(1000)

# Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
_MIN_INVARIANT_RATIO = int(60e16) # 60%
# Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
_MAX_INVARIANT_RATIO = int(500e16) # 500%


def compute_invariant(amplification_parameter: int, balances: list[int]) -> int:
"""
Expand Down
8 changes: 8 additions & 0 deletions python/src/pools/weighted.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
compute_invariant_up,
compute_invariant_down,
compute_balance_out_given_invariant,
_MAX_INVARIANT_RATIO,
_MIN_INVARIANT_RATIO,
)
from src.swap import SwapKind

Expand All @@ -13,6 +15,12 @@ class Weighted:
def __init__(self, pool_state):
self.normalized_weights = pool_state["weights"]

def get_maximum_invariant_ratio(self) -> int:
return _MAX_INVARIANT_RATIO

def get_minimum_invariant_ratio(self) -> int:
return _MIN_INVARIANT_RATIO

def on_swap(self, swap_params):
if swap_params["swap_kind"] == SwapKind.GIVENIN.value:
return compute_out_given_exact_in(
Expand Down
2 changes: 2 additions & 0 deletions python/src/remove_liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def remove_liquidity(
remove_liquidity_input["max_bpt_amount_in_raw"],
pool_state["totalSupply"],
pool_state["swapFee"],
pool_class.get_minimum_invariant_ratio(),
lambda balancesLiveScaled18, tokenIndex, invariantRatio: pool_class.compute_balance(
balancesLiveScaled18, tokenIndex, invariantRatio
),
Expand All @@ -92,6 +93,7 @@ def remove_liquidity(
amounts_out_scaled18[token_out_index],
pool_state["totalSupply"],
pool_state["swapFee"],
pool_class.get_minimum_invariant_ratio(),
lambda balances_live_scaled18, rounding: pool_class.compute_invariant(
balances_live_scaled18, rounding
),
Expand Down
6 changes: 6 additions & 0 deletions python/test/hooks/after_remove_liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class CustomPool():
def __init__(self, pool_state):
self.pool_state = pool_state

def get_maximum_invariant_ratio(self) -> int:
return 1

def get_minimum_invariant_ratio(self) -> int:
return 1

def on_swap(self, swap_params):
return 1

Expand Down
6 changes: 6 additions & 0 deletions python/test/hooks/before_remove_liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ class CustomPool():
def __init__(self, pool_state):
self.pool_state = pool_state

def get_maximum_invariant_ratio(self) -> int:
return 1

def get_minimum_invariant_ratio(self) -> int:
return 1

def on_swap(self, swap_params):
return 1

Expand Down
5 changes: 5 additions & 0 deletions typescript/src/stable/stableMath.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { MathSol } from '../utils/math';

// Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
export const _MIN_INVARIANT_RATIO = BigInt(60e16); // 60%
// Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
export const _MAX_INVARIANT_RATIO = BigInt(500e16); // 500%

// For security reasons, to help ensure that for all possible "round trip" paths
// the caller always receives the same or fewer tokens than supplied,
// we have chosen the rounding direction to favor the protocol in all cases.
Expand Down
9 changes: 9 additions & 0 deletions typescript/src/stable/stablePool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MAX_UINT256, MAX_BALANCE } from '../constants';
import { MathSol } from '../utils/math';
import { toRawUndoRateRoundDown } from '../vault/utils';
import { _MAX_INVARIANT_RATIO, _MIN_INVARIANT_RATIO } from './stableMath';
import {
MaxSingleTokenRemoveParams,
MaxSwapParams,
Expand All @@ -24,6 +25,14 @@ export class Stable implements PoolBase {
this.amp = poolState.amp;
}

getMaximumInvariantRatio(): bigint {
return _MAX_INVARIANT_RATIO;
}

getMinimumInvariantRatio(): bigint {
return _MIN_INVARIANT_RATIO;
}

/**
* Returns the max amount that can be swapped in relation to the swapKind.
* @param maxSwapParams
Expand Down
58 changes: 41 additions & 17 deletions typescript/src/vault/basePoolMath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ export function computeAddLiquidityUnbalanced(
exactAmounts: bigint[],
totalSupply: bigint,
swapFeePercentage: bigint,
maxInvariantRatio: bigint,
computeInvariant: (balances: bigint[], rounding: Rounding) => bigint,
): { bptAmountOut: bigint; swapFeeAmounts: bigint[] } {
/***********************************************************************
// //
// s = totalSupply (iFees - iCur) //
// b = tokenBalance bptOut = s * -------------- //
// bptOut = bptAmountOut iCur //
// iFees = invariantWithFeesApplied //
// iCur = currentInvariant //
// iNew = newInvariant //
***********************************************************************/
// //
// s = totalSupply (iFees - iCur) //
// b = tokenBalance bptOut = s * -------------- //
// bptOut = bptAmountOut iCur //
// iFees = invariantWithFeesApplied //
// iCur = currentInvariant //
// iNew = newInvariant //
***********************************************************************/

// Determine the number of tokens in the pool.
const numTokens = currentBalances.length;
Expand All @@ -43,6 +44,11 @@ export function computeAddLiquidityUnbalanced(
// Calculate the new invariant ratio by dividing the new invariant by the old invariant.
const invariantRatio = MathSol.divDownFixed(newInvariant, currentInvariant);

// ensureInvariantRatioBelowMaximumBound(pool, invariantRatio);
if (invariantRatio > maxInvariantRatio) {
throw Error(`InvariantRatioAboveMax ${invariantRatio} ${maxInvariantRatio}`);
}

// Loop through each token to apply fees if necessary.
for (let index = 0; index < currentBalances.length; index++) {
// Check if the new balance is greater than the equivalent proportional balance.
Expand Down Expand Up @@ -97,6 +103,7 @@ export function computeAddLiquiditySingleTokenExactOut(
exactBptAmountOut: bigint,
totalSupply: bigint,
swapFeePercentage: bigint,
maxInvariantRatio: bigint,
computeBalance: (
balancesLiveScaled18: bigint[],
tokenInIndex: number,
Expand All @@ -108,13 +115,19 @@ export function computeAddLiquiditySingleTokenExactOut(
} {
// Calculate new supply after minting exactBptAmountOut
const newSupply = exactBptAmountOut + totalSupply;

const invariantRatio = MathSol.divUpFixed(newSupply, totalSupply)
// ensureInvariantRatioBelowMaximumBound(pool, invariantRatio);
if (invariantRatio > maxInvariantRatio) {
throw Error(`InvariantRatioAboveMax ${invariantRatio} ${maxInvariantRatio}`);
}
// Calculate the initial amount of the input token needed for the desired amount of BPT out
// "divUp" leads to a higher "newBalance," which in turn results in a larger "amountIn."
// This leads to receiving more tokens for the same amount of BTP minted.
const newBalance = computeBalance(
currentBalances,
tokenInIndex,
MathSol.divUpFixed(newSupply, totalSupply),
invariantRatio,
);
const amountIn = newBalance - currentBalances[tokenInIndex];

Expand Down Expand Up @@ -164,13 +177,13 @@ export function computeProportionalAmountsOut(
bptAmountIn: bigint,
): bigint[] {
/**********************************************************************************************
// computeProportionalAmountsOut //
// (per token) //
// aO = tokenAmountOut / bptIn \ //
// b = tokenBalance a0 = b * | --------------------- | //
// bptIn = bptAmountIn \ bptTotalSupply / //
// bpt = bptTotalSupply //
**********************************************************************************************/
// computeProportionalAmountsOut //
// (per token) //
// aO = tokenAmountOut / bptIn \ //
// b = tokenBalance a0 = b * | --------------------- | //
// bptIn = bptAmountIn \ bptTotalSupply / //
// bpt = bptTotalSupply //
**********************************************************************************************/

// Create a new array to hold the amounts of each token to be withdrawn.
const amountsOut: bigint[] = [];
Expand Down Expand Up @@ -201,6 +214,7 @@ export function computeRemoveLiquiditySingleTokenExactIn(
exactBptAmountIn: bigint,
totalSupply: bigint,
swapFeePercentage: bigint,
minInvariantRatio: bigint,
computeBalance: (
balancesLiveScaled18: bigint[],
tokenInIndex: number,
Expand All @@ -209,13 +223,18 @@ export function computeRemoveLiquiditySingleTokenExactIn(
): { amountOutWithFee: bigint; swapFeeAmounts: bigint[] } {
// Calculate new supply accounting for burning exactBptAmountIn
const newSupply = totalSupply - exactBptAmountIn;

const invariantRatio = MathSol.divUpFixed(newSupply, totalSupply);
if (invariantRatio < minInvariantRatio) {
throw Error(`InvariantRatioBelowMin ${invariantRatio} ${minInvariantRatio}`);
}
// Calculate the new balance of the output token after the BPT burn.
// "divUp" leads to a higher "newBalance," which in turn results in a lower "amountOut."
// This leads to giving less tokens for the same amount of BTP burned.
const newBalance = computeBalance(
currentBalances,
tokenOutIndex,
MathSol.divUpFixed(newSupply, totalSupply),
invariantRatio,
);

// Compute the amount to be withdrawn from the pool.
Expand Down Expand Up @@ -262,6 +281,7 @@ export function computeRemoveLiquiditySingleTokenExactOut(
exactAmountOut: bigint,
totalSupply: bigint,
swapFeePercentage: bigint,
minInvariantRatio: bigint,
computeInvariant: (balances: bigint[], rounding: Rounding) => bigint,
): {
bptAmountIn: bigint;
Expand Down Expand Up @@ -295,6 +315,10 @@ export function computeRemoveLiquiditySingleTokenExactOut(
currentInvariant,
);

if (invariantRatio < minInvariantRatio) {
throw Error(`InvariantRatioBelowMin ${invariantRatio} ${minInvariantRatio}`);
}

// Taxable amount is proportional to invariant ratio; a larger taxable amount rounds in the Vault's favor.
const taxableAmount =
MathSol.mulUpFixed(invariantRatio, currentBalances[tokenOutIndex]) -
Expand Down
2 changes: 2 additions & 0 deletions typescript/src/vault/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export interface PoolBase {
tokenInIndex: number,
invariantRatio: bigint,
): bigint;
getMaximumInvariantRatio(): bigint;
getMinimumInvariantRatio(): bigint;
}

export type MaxSwapParams = {
Expand Down
Loading
Loading