mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-08 07:13:53 +02:00
129 lines
4.6 KiB
Python
129 lines
4.6 KiB
Python
"""Sprint 3 — easy-bet farming detection + enforcement.
|
|
|
|
Maps to IMPLEMENTATION_PLAN.md §1.2: "Farming detection (`farming_pct`)
|
|
... NEEDS penalty enforcement (60%/80% thresholds)."
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from services.infonet.config import CONFIG
|
|
from services.infonet.reputation import compute_oracle_rep
|
|
from services.infonet.reputation.anti_gaming import (
|
|
compute_farming_pct,
|
|
farming_multiplier,
|
|
)
|
|
from services.infonet.tests._chain_factory import make_event, make_market_chain
|
|
|
|
|
|
def _free_pred(market_id: str, node_id: str, side: str, prob: float, ts: float, seq: int) -> dict:
|
|
return make_event(
|
|
"prediction_place", node_id,
|
|
{"market_id": market_id, "side": side, "probability_at_bet": prob},
|
|
timestamp=ts, sequence=seq,
|
|
)
|
|
|
|
|
|
def test_no_predictions_means_zero_farming():
|
|
assert compute_farming_pct("alice", []) == 0.0
|
|
|
|
|
|
def test_all_easy_bets_pct_is_one():
|
|
"""Every prediction at 90% on the picked side → farming_pct = 1.0."""
|
|
chain = [
|
|
_free_pred("m1", "alice", "yes", 90.0, ts=100.0, seq=1),
|
|
_free_pred("m2", "alice", "yes", 95.0, ts=200.0, seq=2),
|
|
_free_pred("m3", "alice", "yes", 88.0, ts=300.0, seq=3),
|
|
]
|
|
assert compute_farming_pct("alice", chain) == 1.0
|
|
|
|
|
|
def test_no_easy_bets_pct_is_zero():
|
|
"""50/50 picks are not easy bets."""
|
|
chain = [
|
|
_free_pred("m1", "alice", "yes", 50.0, ts=100.0, seq=1),
|
|
_free_pred("m2", "alice", "no", 50.0, ts=200.0, seq=2),
|
|
]
|
|
assert compute_farming_pct("alice", chain) == 0.0
|
|
|
|
|
|
def test_picked_side_probability_handles_no_pick():
|
|
"""A no-pick at p_yes=10 means picked-side probability is 90 → easy bet."""
|
|
chain = [
|
|
_free_pred("m1", "alice", "no", 10.0, ts=100.0, seq=1),
|
|
]
|
|
# picked side = no → P(no) = 100 - 10 = 90 > 80 cutoff → easy.
|
|
assert compute_farming_pct("alice", chain) == 1.0
|
|
|
|
|
|
def test_contrarian_prediction_is_not_easy():
|
|
"""A 'yes' at p_yes=10 (going against the chain consensus) is the
|
|
opposite of farming."""
|
|
chain = [
|
|
_free_pred("m1", "alice", "yes", 10.0, ts=100.0, seq=1),
|
|
]
|
|
assert compute_farming_pct("alice", chain) == 0.0
|
|
|
|
|
|
def test_farming_multiplier_below_soft_threshold_is_full():
|
|
soft = float(CONFIG["farming_soft_threshold"])
|
|
assert farming_multiplier(soft - 0.01) == 1.0
|
|
assert farming_multiplier(0.0) == 1.0
|
|
|
|
|
|
def test_farming_multiplier_in_soft_band_is_half():
|
|
soft = float(CONFIG["farming_soft_threshold"])
|
|
hard = float(CONFIG["farming_hard_threshold"])
|
|
assert farming_multiplier((soft + hard) / 2.0) == 0.50
|
|
|
|
|
|
def test_farming_multiplier_above_hard_threshold_is_tenth():
|
|
hard = float(CONFIG["farming_hard_threshold"])
|
|
assert farming_multiplier(hard + 0.001) == 0.10
|
|
assert farming_multiplier(1.0) == 0.10
|
|
|
|
|
|
def test_oracle_rep_with_high_farming_is_reduced_to_ten_percent():
|
|
"""Integration: a node whose ALL free picks are easy bets gets 10%
|
|
of normal mint when those picks resolve correctly.
|
|
"""
|
|
base = 1_000_000.0
|
|
chain = []
|
|
chain += make_market_chain(
|
|
"m1", "creator", outcome="yes",
|
|
predictions=[{"node_id": "alice", "side": "yes", "probability_at_bet": 90.0}],
|
|
base_ts=base,
|
|
participants=5, total_stake=10.0,
|
|
)
|
|
chain += make_market_chain(
|
|
"m2", "creator", outcome="yes",
|
|
predictions=[{"node_id": "alice", "side": "yes", "probability_at_bet": 92.0}],
|
|
base_ts=base + 100_000,
|
|
participants=5, total_stake=10.0,
|
|
)
|
|
# alice has 100% easy bets → 10% multiplier.
|
|
# Each market mint without farming: max(0.01, 1 - 0.90) = 0.10 and 0.08
|
|
# Both correct. Total without farming = 0.18. With 10% farming = 0.018.
|
|
rep = compute_oracle_rep("alice", chain)
|
|
assert abs(rep - 0.018) < 1e-9
|
|
|
|
|
|
def test_staked_predictions_NOT_farming_penalized():
|
|
"""Spec semantics: farming applies to free picks, not staked
|
|
positions where the farmer is risking actual rep."""
|
|
base = 1_000_000.0
|
|
chain = make_market_chain(
|
|
"m1", "creator", outcome="yes",
|
|
predictions=[
|
|
# alice's only prediction is a STAKED easy bet — picked-side prob 92%.
|
|
{"node_id": "alice", "side": "yes", "stake_amount": 10.0,
|
|
"probability_at_bet": 92.0},
|
|
{"node_id": "loser", "side": "no", "stake_amount": 10.0,
|
|
"probability_at_bet": 92.0},
|
|
],
|
|
base_ts=base, participants=5, total_stake=20.0,
|
|
)
|
|
# alice has 1/1 easy bets → farming_pct = 1.0 → 10% multiplier on FREE picks only.
|
|
# Her staked return is unmultiplied: 20.0 (stake 10 + 10 from loser pool).
|
|
rep = compute_oracle_rep("alice", chain)
|
|
assert rep == 20.0
|