197 lines
6.6 KiB
Python
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
|