"""Tests for 2-CLP concentrated liquidity pool.""" import numpy as np import pytest from src.primitives.concentrated_2clp import ( compute_invariant, virtual_offset_x, virtual_offset_y, calc_out_given_in, calc_in_given_out, spot_price, price_bounds, ) class TestInvariant: def test_balanced_pool(self): """Balanced pool should have positive invariant.""" L = compute_invariant(1000.0, 1000.0, 0.9, 1.1) assert L > 0 def test_invariant_increases_with_reserves(self): """More reserves → larger invariant.""" L1 = compute_invariant(100.0, 100.0, 0.9, 1.1) L2 = compute_invariant(200.0, 200.0, 0.9, 1.1) assert L2 > L1 def test_narrow_range_higher_invariant(self): """Narrower price range → higher L for same balances (more concentrated).""" L_wide = compute_invariant(1000.0, 1000.0, 0.5, 2.0) L_narrow = compute_invariant(1000.0, 1000.0, 0.95, 1.05) assert L_narrow > L_wide def test_virtual_reserves_satisfy_cpmm(self): """(x + a)(y + b) should equal L².""" x, y = 500.0, 800.0 sa, sb = 0.8, 1.2 L = compute_invariant(x, y, sa, sb) a = virtual_offset_x(L, sb) b = virtual_offset_y(L, sa) product = (x + a) * (y + b) assert abs(product - L**2) < 1e-6 class TestSwaps: def test_invariant_preserved(self): """Swap should preserve L.""" x, y = 1000.0, 1000.0 sa, sb = 0.9, 1.1 L_before = compute_invariant(x, y, sa, sb) dy = calc_out_given_in(x, y, sa, sb, 100.0, token_in=0) L_after = compute_invariant(x + 100.0, y - dy, sa, sb) assert abs(L_after - L_before) / L_before < 1e-10 def test_round_trip(self): """calc_in_given_out inverts calc_out_given_in.""" x, y = 1000.0, 1000.0 sa, sb = 0.9, 1.1 amount_in = 50.0 amount_out = calc_out_given_in(x, y, sa, sb, amount_in, token_in=0) recovered = calc_in_given_out(x, y, sa, sb, amount_out, token_out=1) assert abs(recovered - amount_in) < 1e-8 def test_spot_price_in_range(self): """Spot price should be within [α, β].""" x, y = 1000.0, 1000.0 sa, sb = 0.9, 1.1 sp = spot_price(x, y, sa, sb) alpha, beta = price_bounds(sa, sb) assert alpha <= sp <= beta def test_concentrated_less_slippage(self): """Narrower range = less slippage for same-size swap.""" x, y = 1000.0, 1000.0 # Narrow range out_narrow = calc_out_given_in(x, y, 0.95, 1.05, 50.0) # Wide range out_wide = calc_out_given_in(x, y, 0.5, 2.0, 50.0) # Narrow range should give better price (more output) assert out_narrow > out_wide def test_both_directions(self): """Swap in both directions should work.""" x, y = 1000.0, 1000.0 sa, sb = 0.9, 1.1 # x → y dy = calc_out_given_in(x, y, sa, sb, 100.0, token_in=0) assert dy > 0 # y → x dx = calc_out_given_in(x, y, sa, sb, 100.0, token_in=1) assert dx > 0