myco-bonding-curve/tests/test_batch_settlement.py

138 lines
4.8 KiB
Python

"""Tests for batch settlement orchestration."""
from src.crdt.batch_settlement import (
BatchConfig,
PendingBatch,
Settlement,
create_batch,
net_curve_position,
simulate_batch_efficiency,
solve_batch,
)
from src.crdt.intent_matching import Intent, IntentSet, add_intent
from src.crdt.labor_crdt import (
AttestationEntry,
CRDTLaborSystem,
submit_attestation,
)
RATES = {"code": 10.0, "governance": 5.0}
def _buy(iid: str, amount: float = 100.0) -> Intent:
return Intent(iid, "buyer", "USDC", amount, "MYCO", amount * 0.9, 1000.0)
def _sell(iid: str, amount: float = 100.0) -> Intent:
return Intent(iid, "seller", "MYCO", amount, "USDC", amount * 0.9, 1000.0)
class TestCreateBatch:
def test_creates_batch(self):
iset = IntentSet()
iset = add_intent(iset, _buy("b1"))
config = BatchConfig()
batch = create_batch(iset, CRDTLaborSystem(), config, 0.0)
assert len(batch.intents.intents) == 1
assert batch.window_end == config.settlement_interval
def test_expires_stale_intents(self):
iset = IntentSet()
iset = add_intent(iset, Intent("old", "alice", "USDC", 100, "MYCO", 90, valid_until=5.0))
iset = add_intent(iset, _buy("new"))
config = BatchConfig()
batch = create_batch(iset, CRDTLaborSystem(), config, 10.0)
assert batch.intents.intents["old"].status == "expired"
assert batch.intents.intents["new"].status == "open"
def test_caps_batch_size(self):
iset = IntentSet()
for i in range(20):
iset = add_intent(iset, _buy(f"b{i}"))
config = BatchConfig(max_batch_size=5)
batch = create_batch(iset, CRDTLaborSystem(), config, 0.0)
open_count = sum(1 for i in batch.intents.intents.values() if i.status == "open")
assert open_count <= 5
class TestSolveBatch:
def test_cow_matching(self):
iset = IntentSet()
iset = add_intent(iset, _buy("b1"))
iset = add_intent(iset, _sell("s1"))
config = BatchConfig()
batch = create_batch(iset, CRDTLaborSystem(), config, 0.0)
settlement = solve_batch(batch, RATES)
assert len(settlement.cow_matches) == 1
assert settlement.net_curve_buy == 0.0
assert settlement.net_curve_sell == 0.0
def test_unmatched_goes_to_curve(self):
iset = IntentSet()
iset = add_intent(iset, _buy("b1", 100.0))
config = BatchConfig()
batch = create_batch(iset, CRDTLaborSystem(), config, 0.0)
settlement = solve_batch(batch, RATES)
assert len(settlement.cow_matches) == 0
assert settlement.net_curve_buy == 100.0
def test_labor_mints_included(self):
labor = CRDTLaborSystem()
entry = AttestationEntry("e1", "code", 5.0, 0.0, "oracle")
labor = submit_attestation(labor, "alice", entry)
config = BatchConfig()
batch = create_batch(IntentSet(), labor, config, 0.0)
settlement = solve_batch(batch, RATES)
assert "alice" in settlement.labor_mints
assert settlement.labor_mints["alice"] == 50.0 # 5 * 10
def test_mixed_batch(self):
iset = IntentSet()
iset = add_intent(iset, _buy("b1"))
iset = add_intent(iset, _sell("s1"))
iset = add_intent(iset, _buy("b2", 50.0)) # Unmatched
labor = CRDTLaborSystem()
labor = submit_attestation(labor, "alice",
AttestationEntry("e1", "code", 3.0, 0.0, "oracle"))
config = BatchConfig()
batch = create_batch(iset, labor, config, 0.0)
settlement = solve_batch(batch, RATES)
assert len(settlement.cow_matches) == 1
assert settlement.net_curve_buy == 50.0
assert settlement.labor_mints["alice"] == 30.0
class TestNetPosition:
def test_net_position(self):
settlement = Settlement(
net_curve_buy=500.0,
net_curve_sell=200.0,
)
usdc, myco = net_curve_position(settlement)
assert usdc == 500.0
assert myco == 200.0
class TestSimulation:
def test_simulation_returns_stats(self):
result = simulate_batch_efficiency(
n_intents=20, cow_probability=0.5, n_batches=10,
)
assert "avg_match_rate" in result
assert "avg_surplus" in result
assert "avg_unmatched" in result
assert 0 <= result["avg_match_rate"] <= 1.0
def test_more_intents_more_matches(self):
small = simulate_batch_efficiency(n_intents=4, cow_probability=0.5, n_batches=50)
large = simulate_batch_efficiency(n_intents=40, cow_probability=0.5, n_batches=50)
# Larger batches should have at least as good match rates on average
# (not guaranteed per run but statistically likely)
# Just check both are valid
assert small["avg_match_rate"] >= 0
assert large["avg_match_rate"] >= 0