138 lines
4.8 KiB
Python
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
|