103 lines
4.0 KiB
Python
103 lines
4.0 KiB
Python
"""Tests for P-AMM redemption curve."""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from src.primitives.redemption_curve import (
|
|
PAMMParams, PAMMState,
|
|
compute_redemption_rate, redeem,
|
|
backing_ratio_trajectory,
|
|
)
|
|
|
|
|
|
class TestRedemptionRate:
|
|
def test_fully_backed_redeems_at_par(self):
|
|
"""When ba >= 1, rate should be 1.0."""
|
|
state = PAMMState(reserve_value=10000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
rate = compute_redemption_rate(state, params, 100.0)
|
|
assert abs(rate - 1.0) < 1e-10
|
|
|
|
def test_overbacked_redeems_at_par(self):
|
|
"""When ba > 1, rate should be 1.0 (not more)."""
|
|
state = PAMMState(reserve_value=15000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
rate = compute_redemption_rate(state, params, 100.0)
|
|
assert abs(rate - 1.0) < 1e-10
|
|
|
|
def test_underbacked_gives_discount(self):
|
|
"""When ba < 1, rate should be less than 1."""
|
|
state = PAMMState(reserve_value=8000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
rate = compute_redemption_rate(state, params, 100.0)
|
|
assert rate < 1.0
|
|
assert rate > 0
|
|
|
|
def test_deeply_underbacked_hits_floor(self):
|
|
"""Very low backing should approach θ̄."""
|
|
state = PAMMState(reserve_value=100, myco_supply=10000)
|
|
params = PAMMParams(theta_bar=0.5)
|
|
rate = compute_redemption_rate(state, params, 100.0)
|
|
assert rate >= params.theta_bar
|
|
|
|
def test_rate_bounded(self):
|
|
"""Rate should always be in [θ̄, 1.0]."""
|
|
params = PAMMParams(theta_bar=0.3)
|
|
for ba in [0.01, 0.1, 0.3, 0.5, 0.7, 0.9, 1.0, 1.5]:
|
|
state = PAMMState(
|
|
reserve_value=ba * 10000,
|
|
myco_supply=10000,
|
|
)
|
|
rate = compute_redemption_rate(state, params, 100.0)
|
|
assert params.theta_bar <= rate <= 1.0
|
|
|
|
|
|
class TestRedeem:
|
|
def test_redeem_reduces_reserve(self):
|
|
state = PAMMState(reserve_value=10000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
new_state, usd_out = redeem(state, params, 100.0, current_time=0)
|
|
assert new_state.reserve_value < state.reserve_value
|
|
assert new_state.myco_supply < state.myco_supply
|
|
assert usd_out > 0
|
|
|
|
def test_redeem_never_exceeds_reserve(self):
|
|
"""Should never return more USD than available."""
|
|
state = PAMMState(reserve_value=50, myco_supply=10000)
|
|
params = PAMMParams()
|
|
new_state, usd_out = redeem(state, params, 5000.0, current_time=0)
|
|
assert usd_out <= 50
|
|
assert new_state.reserve_value >= 0
|
|
|
|
def test_sequential_redemptions_degrade(self):
|
|
"""Sequential redemptions should get progressively worse rates."""
|
|
state = PAMMState(reserve_value=8000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
rates = []
|
|
for t in range(5):
|
|
rate = compute_redemption_rate(state, params, 500.0)
|
|
state, _ = redeem(state, params, 500.0, current_time=float(t))
|
|
rates.append(rate)
|
|
# Later redemptions should get same or worse rate
|
|
# (backing ratio decreases)
|
|
assert all(rates[i] >= rates[i+1] - 0.01 for i in range(len(rates)-1))
|
|
|
|
|
|
class TestTrajectory:
|
|
def test_trajectory_length(self):
|
|
state = PAMMState(reserve_value=10000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
schedule = [(float(t), 100.0) for t in range(10)]
|
|
traj = backing_ratio_trajectory(state, params, schedule)
|
|
assert len(traj) == 10
|
|
|
|
def test_trajectory_backing_decreases(self):
|
|
"""Backing ratio should decrease with continuous redemptions."""
|
|
state = PAMMState(reserve_value=10000, myco_supply=10000)
|
|
params = PAMMParams()
|
|
schedule = [(float(t), 200.0) for t in range(10)]
|
|
traj = backing_ratio_trajectory(state, params, schedule)
|
|
backing_ratios = [t[1] for t in traj]
|
|
# Each step should have lower or equal backing
|
|
for i in range(len(backing_ratios) - 1):
|
|
assert backing_ratios[i+1] <= backing_ratios[i] + 1e-10
|