chain seed: Alchemy push decodes fills from the tx receipt — RTDS-silent markets copy at push speed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-14 00:09:24 -04:00
parent 7e5d634e0f
commit 5e97b13bc9
+192
View File
@@ -0,0 +1,192 @@
"""Stub tests for the chain-seed path (HANDOFF 2026-07-14): fills_from_tx
receipt decoding, forged-event rejection, addresses_in_payload tx hints, and
the on_wallet_activity hint funnel. Run: python3 tests/test_chainseed.py —
needs no network, no cache, no config; stubs the RPC and the engine.
"""
import os
import sys
import tempfile
import time
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import copybot # noqa: E402
_TMP = tempfile.mkdtemp(prefix="copybot_test_")
WALLET = "0xbadaf319415c17f28824a43ae0cd912b9d84d874"
OTHER = "0x30fb41b5a08fff5d3dd983f6323e3343931a7db4"
EXCH = "0xe111180000d2663c0091e4f400237545b87b996b"
TOK = "113883491847873555347283937250168614361193102079209733484211339732062843429513"
def topic_addr(a):
return "0x" + a[2:].lower().rjust(64, "0")
def word(v):
return hex(v)[2:].rjust(64, "0")
def order_filled_log(maker, cls, token, given, taken, address=EXCH,
topic0=copybot._ORDER_FILLED):
return {"address": address,
"topics": [topic0, "0x" + "ab" * 32,
topic_addr(maker), topic_addr(EXCH)],
"data": "0x" + word(cls) + word(int(token))
+ word(given) + word(taken) + word(0) + word(0) + word(0)}
def stub_rpc(receipt_logs, block_ts):
def _rpc(method, params):
if method == "eth_getTransactionReceipt":
return {"blockNumber": "0x100", "logs": receipt_logs}
if method == "eth_getBlockByNumber":
return {"timestamp": hex(block_ts)}
raise AssertionError(f"unexpected rpc {method}")
return _rpc
fails = []
def case(name):
def deco(fn):
rpc0, url0, meta0 = copybot._rpc, copybot._RPC_URL, copybot._token_market
try:
fn()
print(f"PASS {name}")
except AssertionError as e:
print(f"FAIL {name}: {e}")
fails.append(name)
finally:
copybot._rpc, copybot._RPC_URL = rpc0, url0
copybot._token_market = meta0
return deco
def with_meta():
copybot._token_market = lambda tok: ("Test Market?", "Yes", "0xcond1")
@case("BUY decodes: cash given -> size/price/usd from the maker row")
def t1():
copybot._RPC_URL = "stub"
ts = int(time.time()) - 3
copybot._rpc = stub_rpc([
# counterparty's own row — must be ignored (maker != wallet)
order_filled_log(OTHER, 0, TOK, 17_000_000, 20_000_000),
# the wallet's row: gave $3.00 cash, took 20 tokens -> BUY 20 @ 0.15
order_filled_log(WALLET, 0, TOK, 3_000_000, 20_000_000),
], ts)
with_meta()
fills = copybot.fills_from_tx("0xt1", WALLET)
assert len(fills) == 1, f"expected 1 fill, got {len(fills)}"
f = fills[0]
assert f["side"] == "BUY" and f["asset"] == TOK
assert abs(f["size"] - 20.0) < 1e-9 and abs(f["price"] - 0.15) < 1e-9
assert abs(f["usdcSize"] - 3.0) < 1e-9 and f["timestamp"] == ts
assert f["title"] == "Test Market?" and f["conditionId"] == "0xcond1"
@case("SELL decodes: tokens given -> reversed amounts")
def t2():
copybot._RPC_URL = "stub"
copybot._rpc = stub_rpc([
# gave 10.68 tokens, took $5.1264 -> SELL 10.68 @ 0.48
order_filled_log(WALLET, 1, TOK, 10_680_000, 5_126_400),
], 1)
with_meta()
f = copybot.fills_from_tx("0xt2", WALLET)[0]
assert f["side"] == "SELL"
assert abs(f["size"] - 10.68) < 1e-9 and abs(f["price"] - 0.48) < 1e-9
@case("forged events rejected: wrong emitter, wrong topic")
def t3():
copybot._RPC_URL = "stub"
copybot._rpc = stub_rpc([
# right shape, but emitted by a contract that is NOT a known exchange
order_filled_log(WALLET, 0, TOK, 3_000_000, 20_000_000,
address="0x" + "66" * 20),
# right emitter, wrong event signature
order_filled_log(WALLET, 0, TOK, 3_000_000, 20_000_000,
topic0="0x" + "77" * 32),
], 1)
with_meta()
assert copybot.fills_from_tx("0xt3", WALLET) == []
@case("fail-open: no RPC configured / metadata lookup down -> []")
def t4():
copybot._RPC_URL = None
assert copybot.fills_from_tx("0xt4", WALLET) == []
copybot._RPC_URL = "stub"
copybot._rpc = stub_rpc([
order_filled_log(WALLET, 0, TOK, 3_000_000, 20_000_000)], 1)
copybot._token_market = lambda tok: None # gamma down
assert copybot.fills_from_tx("0xt4", WALLET) == []
@case("addresses_in_payload maps watched wallets to their tx hashes")
def t5():
payload = {"event": {"activity": [
{"fromAddress": WALLET, "toAddress": EXCH, "hash": "0xaa"},
{"fromAddress": EXCH, "toAddress": WALLET, "hash": "0xaa"}, # dup tx
{"fromAddress": WALLET, "toAddress": EXCH, "hash": "0xbb"},
{"fromAddress": OTHER, "toAddress": EXCH, "hash": "0xcc"}, # unwatched
]}}
got = copybot.addresses_in_payload(payload, {WALLET.lower()})
assert got == {WALLET.lower(): ["0xaa", "0xbb"]}, got
@case("hint funnel: unseen hash seeds handle_trade; seen/skipped hashes don't")
def t6():
calls = []
class Eng:
seen = {"0xseen"}
state = {"my_pos": {}, "trade_cursor": {}}
def handle_trade(self, wallet, t):
calls.append(t["transactionHash"])
self.seen.add(t["transactionHash"])
class Bot(copybot.Copybot):
def __init__(self): # bare wiring only
self.engine = Eng()
self.names = {}
self.skipped = {"0xskip"}
self.conds = {}
self.lock = __import__("threading").Lock()
def _fetch_since_cursor(self, wallet):
return [] # the indexer is lagging
def _drain_fills(self):
return []
def check_book(self):
return 0.0
class Filt:
def check(self, wallet, t):
return True, None
bot = Bot()
bot.filt = Filt()
fresh = {"transactionHash": "0xnew", "asset": TOK, "side": "BUY",
"size": 20.0, "price": 0.15, "usdcSize": 3.0,
"title": "Test Market?", "outcome": "Yes",
"conditionId": "0xcond1", "timestamp": int(time.time())}
decoded = {"0xnew": [fresh]}
real = copybot.fills_from_tx
copybot.fills_from_tx = lambda tx, w: decoded.get(tx, [])
try:
bot.on_wallet_activity(WALLET, hint_txs=["0xseen", "0xskip", "0xnew"])
finally:
copybot.fills_from_tx = real
assert calls == ["0xnew"], calls
print()
print("FAILURES:", fails or "none")
sys.exit(1 if fails else 0)