104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
"""Tests for reserve tranching."""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from src.primitives.reserve_tranching import (
|
|
VaultMetadata, Vault, ReserveState,
|
|
current_weights, target_weights, weight_deviations,
|
|
is_safe_to_mint, is_safe_to_redeem,
|
|
optimal_deposit_split, update_flow, check_flow_limit,
|
|
)
|
|
|
|
|
|
def make_balanced_state():
|
|
"""Three vaults with equal balances and weights."""
|
|
vaults = [
|
|
Vault(
|
|
metadata=VaultMetadata(
|
|
name=f"vault_{i}", target_weight=1/3,
|
|
price_at_calibration=1.0, weight_at_calibration=1/3,
|
|
weight_previous=1/3, calibration_time=0,
|
|
transition_duration=100,
|
|
),
|
|
balance=1000.0, current_price=1.0,
|
|
)
|
|
for i in range(3)
|
|
]
|
|
return ReserveState(vaults=vaults, myco_supply=3000.0)
|
|
|
|
|
|
class TestWeights:
|
|
def test_balanced_weights(self):
|
|
state = make_balanced_state()
|
|
cw = current_weights(state)
|
|
np.testing.assert_allclose(cw, [1/3, 1/3, 1/3], atol=1e-10)
|
|
|
|
def test_target_weights_at_calibration(self):
|
|
state = make_balanced_state()
|
|
tw = target_weights(state, current_time=0)
|
|
np.testing.assert_allclose(tw, [1/3, 1/3, 1/3], atol=1e-10)
|
|
|
|
def test_weight_deviations_balanced(self):
|
|
state = make_balanced_state()
|
|
dev = weight_deviations(state, current_time=0)
|
|
np.testing.assert_allclose(dev, [0, 0, 0], atol=1e-10)
|
|
|
|
|
|
class TestSafety:
|
|
def test_proportional_deposit_safe(self):
|
|
state = make_balanced_state()
|
|
amounts = np.array([100.0, 100.0, 100.0])
|
|
safe, msg = is_safe_to_mint(state, amounts, current_time=0)
|
|
assert safe
|
|
|
|
def test_extreme_imbalance_blocked(self):
|
|
state = make_balanced_state()
|
|
# Deposit everything into vault 0 — should be blocked if already overweight
|
|
state.vaults[0].balance = 2000.0 # Make overweight
|
|
state.total_value = 4000.0
|
|
amounts = np.array([1000.0, 0.0, 0.0])
|
|
safe, msg = is_safe_to_mint(state, amounts, current_time=0)
|
|
# With max_deviation=0.1 and target=0.33, this should fail
|
|
assert not safe
|
|
|
|
def test_rebalancing_deposit_allowed(self):
|
|
state = make_balanced_state()
|
|
state.vaults[0].balance = 500.0 # Make underweight
|
|
state.total_value = 2500.0
|
|
amounts = np.array([500.0, 0.0, 0.0]) # Rebalance
|
|
safe, msg = is_safe_to_mint(state, amounts, current_time=0)
|
|
assert safe
|
|
|
|
|
|
class TestOptimal:
|
|
def test_balanced_gets_proportional(self):
|
|
state = make_balanced_state()
|
|
split = optimal_deposit_split(state, 300.0, current_time=0)
|
|
np.testing.assert_allclose(split, [100, 100, 100], atol=1e-6)
|
|
|
|
def test_underweight_gets_more(self):
|
|
state = make_balanced_state()
|
|
state.vaults[0].balance = 500.0 # Underweight
|
|
state.total_value = 2500.0
|
|
split = optimal_deposit_split(state, 500.0, current_time=0)
|
|
# Vault 0 should get the most
|
|
assert split[0] > split[1]
|
|
assert split[0] > split[2]
|
|
|
|
|
|
class TestFlow:
|
|
def test_flow_decays(self):
|
|
v = make_balanced_state().vaults[0]
|
|
v = update_flow(v, 100.0, current_time=0)
|
|
assert v.recent_flow == 100.0
|
|
|
|
v = update_flow(v, 0.0, current_time=100)
|
|
assert v.recent_flow < 100.0 # Should have decayed
|
|
|
|
def test_flow_limit_check(self):
|
|
v = make_balanced_state().vaults[0]
|
|
v = update_flow(v, 200.0, current_time=0)
|
|
ok, msg = check_flow_limit(v)
|
|
# 200 > 1000 * 0.05 = 50 → should exceed
|
|
assert not ok
|