myco-bonding-curve/tests/test_intent_matching.py

197 lines
6.6 KiB
Python

"""Tests for CoW intent matching."""
import math
from src.crdt.intent_matching import (
Intent,
IntentSet,
Match,
add_intent,
compute_clearing_price,
expire_intents,
fill_intent,
find_cows,
merge_intents,
total_surplus,
)
def _buy_intent(iid: str, sell: float = 100.0, min_buy: float = 95.0,
valid: float = 100.0) -> Intent:
"""Create a USDC->MYCO buy intent."""
return Intent(iid, "alice", "USDC", sell, "MYCO", min_buy, valid)
def _sell_intent(iid: str, sell: float = 100.0, min_buy: float = 90.0,
valid: float = 100.0) -> Intent:
"""Create a MYCO->USDC sell intent."""
return Intent(iid, "bob", "MYCO", sell, "USDC", min_buy, valid)
class TestIntentSet:
def test_add_intent(self):
iset = IntentSet()
intent = _buy_intent("i1")
iset = add_intent(iset, intent)
assert "i1" in iset.intents
def test_add_duplicate_noop(self):
iset = IntentSet()
intent = _buy_intent("i1")
s1 = add_intent(iset, intent)
s2 = add_intent(s1, intent)
assert len(s2.intents) == 1
def test_expire_intents(self):
iset = IntentSet()
iset = add_intent(iset, _buy_intent("i1", valid=10.0))
iset = add_intent(iset, _buy_intent("i2", valid=50.0))
expired = expire_intents(iset, 20.0)
assert expired.intents["i1"].status == "expired"
assert expired.intents["i2"].status == "open"
def test_fill_intent(self):
iset = IntentSet()
iset = add_intent(iset, _buy_intent("i1"))
filled = fill_intent(iset, "i1")
assert filled.intents["i1"].status == "filled"
def test_fill_already_expired_noop(self):
iset = IntentSet()
iset = add_intent(iset, _buy_intent("i1", valid=5.0))
iset = expire_intents(iset, 10.0)
filled = fill_intent(iset, "i1")
assert filled.intents["i1"].status == "expired"
def test_pure_functional(self):
iset = IntentSet()
intent = _buy_intent("i1")
updated = add_intent(iset, intent)
assert "i1" not in iset.intents
assert "i1" in updated.intents
class TestClearingPrice:
def test_equal_limits_geometric_mean(self):
a = Intent("a", "alice", "USDC", 100.0, "MYCO", 100.0, 100.0)
b = Intent("b", "bob", "MYCO", 100.0, "USDC", 100.0, 100.0)
price = compute_clearing_price(a, b)
assert abs(price - 1.0) < 1e-10
def test_asymmetric_prices(self):
a = Intent("a", "alice", "USDC", 100.0, "MYCO", 90.0, 100.0)
b = Intent("b", "bob", "MYCO", 110.0, "USDC", 100.0, 100.0)
price = compute_clearing_price(a, b)
# a's limit: 90/100 = 0.9, b's offer: 110/100 = 1.1
expected = math.sqrt(0.9 * 1.1)
assert abs(price - expected) < 1e-10
class TestCoWMatching:
def test_compatible_pair_matches(self):
iset = IntentSet()
# Alice: sell 100 USDC, want at least 90 MYCO
iset = add_intent(iset, Intent("a", "alice", "USDC", 100.0, "MYCO", 90.0, 100.0))
# Bob: sell 100 MYCO, want at least 90 USDC
iset = add_intent(iset, Intent("b", "bob", "MYCO", 100.0, "USDC", 90.0, 100.0))
result = find_cows(iset)
assert len(result.matches) == 1
assert len(result.unmatched_ids) == 0
def test_incompatible_prices_no_match(self):
iset = IntentSet()
# Alice wants too much
iset = add_intent(iset, Intent("a", "alice", "USDC", 100.0, "MYCO", 200.0, 100.0))
# Bob wants too much
iset = add_intent(iset, Intent("b", "bob", "MYCO", 100.0, "USDC", 200.0, 100.0))
result = find_cows(iset)
assert len(result.matches) == 0
assert len(result.unmatched_ids) == 2
def test_same_token_no_match(self):
iset = IntentSet()
iset = add_intent(iset, Intent("a", "alice", "USDC", 100.0, "MYCO", 90.0, 100.0))
iset = add_intent(iset, Intent("b", "bob", "USDC", 100.0, "MYCO", 90.0, 100.0))
result = find_cows(iset)
assert len(result.matches) == 0
def test_multiple_pairs(self):
iset = IntentSet()
iset = add_intent(iset, Intent("a1", "alice", "USDC", 100.0, "MYCO", 90.0, 100.0))
iset = add_intent(iset, Intent("b1", "bob", "MYCO", 100.0, "USDC", 90.0, 100.0))
iset = add_intent(iset, Intent("a2", "carol", "USDC", 50.0, "MYCO", 45.0, 100.0))
iset = add_intent(iset, Intent("b2", "dave", "MYCO", 50.0, "USDC", 45.0, 100.0))
result = find_cows(iset)
assert len(result.matches) == 2
assert len(result.unmatched_ids) == 0
def test_unmatched_remainder(self):
iset = IntentSet()
iset = add_intent(iset, Intent("a", "alice", "USDC", 100.0, "MYCO", 90.0, 100.0))
iset = add_intent(iset, Intent("b", "bob", "MYCO", 100.0, "USDC", 90.0, 100.0))
iset = add_intent(iset, Intent("c", "carol", "USDC", 50.0, "MYCO", 45.0, 100.0))
result = find_cows(iset)
assert len(result.matches) == 1
assert len(result.unmatched_ids) == 1
class TestSurplus:
def test_surplus_positive(self):
matches = [Match("a", "b", 1.0, 100.0, 100.0)]
s = total_surplus(matches)
assert s > 0
class TestIntentMerge:
def test_merge_commutativity(self):
a = IntentSet()
a = add_intent(a, _buy_intent("i1"))
b = IntentSet()
b = add_intent(b, _sell_intent("i2"))
ab = merge_intents(a, b)
ba = merge_intents(b, a)
assert set(ab.intents) == set(ba.intents)
def test_merge_idempotency(self):
a = IntentSet()
a = add_intent(a, _buy_intent("i1"))
aa = merge_intents(a, a)
assert len(aa.intents) == 1
assert aa.intents["i1"].status == "open"
def test_merge_prefers_terminal_status(self):
a = IntentSet()
a = add_intent(a, _buy_intent("i1"))
b = IntentSet()
b = add_intent(b, _buy_intent("i1"))
b = fill_intent(b, "i1")
merged = merge_intents(a, b)
assert merged.intents["i1"].status == "filled"
def test_merge_filled_over_expired(self):
a = IntentSet()
a = add_intent(a, _buy_intent("i1", valid=5.0))
a = expire_intents(a, 10.0)
b = IntentSet()
b = add_intent(b, _buy_intent("i1", valid=5.0))
b = fill_intent(b, "i1")
merged = merge_intents(a, b)
assert merged.intents["i1"].status == "filled"
def test_merge_union(self):
a = IntentSet()
a = add_intent(a, _buy_intent("i1"))
b = IntentSet()
b = add_intent(b, _sell_intent("i2"))
merged = merge_intents(a, b)
assert len(merged.intents) == 2