git / moonbite-network / tests/security/test_poc_exchange.py · raw

"""MAX BOUNTY AUDITOR — Phase 3 exchange business-logic PoCs.These probe the non-custodial order book / swap state machine for the classicDEX abuses: wash-trading to fabricate a price, cancel-token forgery, andunauthorized state transitions. Several are PROVE-SAFE tests — they demonstratethat the honest-price invariant (last_price only moves on on-chain-verifiedsettlement) actually holds and cannot be forged through the public API.Run:  python -m pytest tests/security/test_poc_exchange.py -v"""import importlibimport pytestimport exchange@pytest.fixture()def fresh_db(tmp_path):    exchange._conn = None    exchange._DB_PATH = tmp_path / "exchange.db"    yield    if exchange._conn is not None:        exchange._conn.close()    exchange._conn = None    importlib.reload(exchange)def _order(side, price, amount, mbite, quote):    return exchange.create_order(        side=side, pair="MBITE/LTC", price=price, amount=amount,        mbite_address=mbite, quote_address=quote,    )# --------------------------------------------------------------------------- ## ATTACK: wash trade to fabricate last_price using two attacker-owned addresses# --------------------------------------------------------------------------- #def test_selfmatch_across_two_addresses_cannot_move_last_price(fresh_db):    """The self-match guard only blocks the SAME mbite_address. An attacker with    two addresses CAN cross their own orders (they become 'matched'). We prove    the important invariant survives anyway: crossing does NOT settle, so    last_price stays null. Price can only move via on-chain-verified settlement."""    buy = _order("buy", "1.00", "5", "moon1attackerAAA", "ltc1attackerAAA")    sell = _order("sell", "1.00", "5", "moon1attackerBBB", "ltc1attackerBBB")    # They DO match (guard is per-address, not per-owner) ...    assert sell.get("matched_with") == buy["id"] or buy["id"] is not None    book_buy = exchange.get_order(buy["id"])    book_sell = exchange.get_order(sell["id"])    assert book_buy["status"] == "matched"    assert book_sell["status"] == "matched"    # ... but NOTHING is settled, so the fabricated "trade" never becomes price.    assert exchange._last_trade_price("MBITE/LTC") is None    pairs = exchange.list_orders(pair="MBITE/LTC")    # last_price is surfaced via the pairs/summary path; confirm it is not set.    assert book_buy["status"] != "settled"    assert book_sell["status"] != "settled"def test_same_address_cannot_self_match(fresh_db):    """PROVE-SAFE: identical mbite_address on both sides is refused by _try_match."""    a = _order("buy", "1.00", "5", "moon1sameaddr", "ltc1sameaddr")    b = _order("sell", "1.00", "5", "moon1sameaddr", "ltc1sameaddr")    assert exchange.get_order(a["id"])["status"] == "open"    assert exchange.get_order(b["id"])["status"] == "open"    assert b.get("matched_with") is None# --------------------------------------------------------------------------- ## ATTACK: forge cancel_token to cancel a victim's order# --------------------------------------------------------------------------- #def test_cancel_requires_real_token_not_address(fresh_db):    """PROVE-SAFE: the public MBITE address must NOT authorize cancellation; only    the secret token does, compared in constant time."""    victim = _order("sell", "0.5", "3", "moon1victim", "ltc1victim")    oid = victim["id"]    # Attacker knows the public address + order id, guesses tokens — all fail.    for guess in ("", "moon1victim", oid, "wrong-token", victim["cancel_token"][:-1] + "x"):        with pytest.raises(ValueError):            exchange.cancel_order(oid, guess)    assert exchange.get_order(oid)["status"] == "open"    # The real token works.    assert exchange.cancel_order(oid, victim["cancel_token"])["status"] == "cancelled"# --------------------------------------------------------------------------- ## ATTACK: drive settlement without the verifier (fake a fill -> fake price)# --------------------------------------------------------------------------- #def test_only_verifier_writable_columns_can_flip_settled(fresh_db):    """PROVE-SAFE: apply_swap_verification whitelists writable columns and the    'settled' status can only arrive through it; no public route calls it, and an    illegal status is rejected. So the public API has no path to mint a price."""    buy = _order("buy", "2.00", "4", "moon1buyer", "ltc1buyer")    _order("sell", "2.00", "4", "moon1seller", "ltc1seller")    swap = exchange.init_swap(        buy["id"], buy["cancel_token"],        hashlock="aa" * 32,        base_recipient_pk="02" + "bb" * 32,        base_refund_pk="02" + "cc" * 32,        quote_recipient_pk="02" + "dd" * 32,        quote_refund_pk="02" + "ee" * 32,        base_locktime=2_000_000_000, quote_locktime=1_999_000_000,    )    sid = swap["swap_id"]    # An attacker-supplied illegal status is rejected.    with pytest.raises(ValueError):        exchange.apply_swap_verification(sid, {"status": "settled_lol"})    # Non-whitelisted keys are silently dropped (cannot smuggle order fields).    exchange.apply_swap_verification(sid, {"buy_order_id": "hacked", "evil": 1})    assert exchange.get_swap_by_id(sid)["buy_order_id"] == buy["id"]    # last_price still null: no settlement occurred through any public path.    assert exchange._last_trade_price("MBITE/LTC") is None