115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
"""Tests for weighted constant product invariant."""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from src.primitives.weighted_product import (
|
|
compute_invariant,
|
|
spot_price,
|
|
calc_out_given_in,
|
|
calc_in_given_out,
|
|
calc_bpt_out_given_exact_tokens_in,
|
|
calc_token_out_given_exact_bpt_in,
|
|
)
|
|
|
|
|
|
class TestInvariant:
|
|
def test_two_token_equal_weight(self):
|
|
"""50/50 pool: I = sqrt(x * y)."""
|
|
balances = np.array([100.0, 100.0])
|
|
weights = np.array([0.5, 0.5])
|
|
inv = compute_invariant(balances, weights)
|
|
assert abs(inv - 100.0) < 1e-10
|
|
|
|
def test_two_token_unequal_weight(self):
|
|
"""80/20 pool."""
|
|
balances = np.array([100.0, 100.0])
|
|
weights = np.array([0.8, 0.2])
|
|
inv = compute_invariant(balances, weights)
|
|
expected = 100.0**0.8 * 100.0**0.2
|
|
assert abs(inv - expected) < 1e-10
|
|
|
|
def test_three_tokens(self):
|
|
"""33/33/34 pool."""
|
|
balances = np.array([100.0, 200.0, 300.0])
|
|
weights = np.array([0.33, 0.33, 0.34])
|
|
inv = compute_invariant(balances, weights)
|
|
expected = 100.0**0.33 * 200.0**0.33 * 300.0**0.34
|
|
assert abs(inv - expected) < 1e-8
|
|
|
|
def test_homogeneity_degree_1(self):
|
|
"""I(k*b) = k * I(b) for all k > 0."""
|
|
balances = np.array([50.0, 150.0, 75.0])
|
|
weights = np.array([0.25, 0.5, 0.25])
|
|
inv_base = compute_invariant(balances, weights)
|
|
for k in [0.5, 2.0, 10.0]:
|
|
inv_scaled = compute_invariant(k * balances, weights)
|
|
assert abs(inv_scaled - k * inv_base) < 1e-8 * k * inv_base
|
|
|
|
|
|
class TestSwaps:
|
|
def test_invariant_preserved_after_swap(self):
|
|
"""Swap should preserve the invariant."""
|
|
balances = np.array([1000.0, 1000.0])
|
|
weights = np.array([0.5, 0.5])
|
|
inv_before = compute_invariant(balances, weights)
|
|
|
|
amount_in = 100.0
|
|
amount_out = calc_out_given_in(1000.0, 0.5, 1000.0, 0.5, amount_in)
|
|
|
|
new_balances = np.array([1000.0 + amount_in, 1000.0 - amount_out])
|
|
inv_after = compute_invariant(new_balances, weights)
|
|
assert abs(inv_after - inv_before) < 1e-8
|
|
|
|
def test_round_trip(self):
|
|
"""calc_in_given_out should invert calc_out_given_in."""
|
|
amount_in = 50.0
|
|
amount_out = calc_out_given_in(1000.0, 0.6, 500.0, 0.4, amount_in)
|
|
recovered_in = calc_in_given_out(1000.0, 0.6, 500.0, 0.4, amount_out)
|
|
assert abs(recovered_in - amount_in) < 1e-8
|
|
|
|
def test_small_swap_matches_spot_price(self):
|
|
"""Very small swap should trade at approximately the spot price."""
|
|
bi, wi, bo, wo = 1000.0, 0.5, 1000.0, 0.5
|
|
sp = spot_price(bi, wi, bo, wo)
|
|
dx = 0.001
|
|
dy = calc_out_given_in(bi, wi, bo, wo, dx)
|
|
assert abs(dy / dx - sp) < 1e-4
|
|
|
|
def test_unequal_weight_swap(self):
|
|
"""80/20 swap prices should reflect weight asymmetry."""
|
|
# In an 80/20 pool, token 0 (80%) is more price-stable
|
|
sp = spot_price(1000.0, 0.8, 1000.0, 0.2)
|
|
# price = (1000/0.2) / (1000/0.8) = 4.0
|
|
assert abs(sp - 4.0) < 1e-10
|
|
|
|
|
|
class TestLiquidity:
|
|
def test_proportional_deposit(self):
|
|
"""Proportional deposit should mint BPT proportional to invariant increase."""
|
|
balances = np.array([1000.0, 1000.0])
|
|
weights = np.array([0.5, 0.5])
|
|
bpt_supply = 1000.0
|
|
|
|
# 10% proportional deposit
|
|
amounts_in = np.array([100.0, 100.0])
|
|
bpt_out = calc_bpt_out_given_exact_tokens_in(
|
|
balances, weights, amounts_in, bpt_supply
|
|
)
|
|
assert abs(bpt_out - 100.0) < 1e-8 # 10% increase
|
|
|
|
def test_single_sided_exit(self):
|
|
"""Single-token exit: burn BPT, receive one token."""
|
|
balances = np.array([1000.0, 1000.0])
|
|
weights = np.array([0.5, 0.5])
|
|
bpt_supply = 1000.0
|
|
|
|
# Burn 10% of supply, exit via token 0
|
|
token_out = calc_token_out_given_exact_bpt_in(
|
|
balances, weights, 0, 100.0, bpt_supply
|
|
)
|
|
# Should get less than 100 tokens due to single-sided penalty
|
|
assert 0 < token_out < 200.0
|
|
# For 50/50 pool: new_balance = 1000 * 0.9^(1/0.5) = 1000 * 0.81 = 810
|
|
# token_out = 1000 - 810 = 190
|
|
assert abs(token_out - 190.0) < 1e-8
|