mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-09-30 12:41:54 +02:00
fix(infonet): reject non-finite stalemate burn inputs (#514)
* fix(infonet): reject non-finite stalemate burn inputs * test(infonet): cover non-finite stalemate burn inputs
This commit is contained in:
1 parent
072bd29445
commit
e9e33ca637
2 files changed
+63
-14
No files matched your search
@@ -30,28 +30,43 @@ specific branches in ``resolution.py`` that match the spec's
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterable
|
import math
|
||||||
|
from typing import Any, Iterable
|
||||||
|
|
||||||
from services.infonet.config import CONFIG
|
from services.infonet.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
def _finite_float(value: Any) -> float | None:
|
||||||
|
try:
|
||||||
|
parsed = float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
return parsed if math.isfinite(parsed) else None
|
||||||
|
|
||||||
|
|
||||||
def stalemate_burn_pct() -> float:
|
def stalemate_burn_pct() -> float:
|
||||||
"""Current burn percentage from CONFIG. Helper so callers don't
|
"""Current burn percentage from CONFIG. Helper so callers don't
|
||||||
need to remember the key name."""
|
need to remember the key name."""
|
||||||
return float(CONFIG["resolution_stalemate_burn_pct"])
|
pct = _finite_float(CONFIG["resolution_stalemate_burn_pct"])
|
||||||
|
if pct is None:
|
||||||
|
raise ValueError("resolution_stalemate_burn_pct must be finite")
|
||||||
|
return pct
|
||||||
|
|
||||||
|
|
||||||
def split_burn_and_return(amount: float, burn_pct: float | None = None) -> tuple[float, float]:
|
def split_burn_and_return(amount: float, burn_pct: float | None = None) -> tuple[float, float]:
|
||||||
"""Compute (burn_amount, returned_amount) for a single stake."""
|
"""Compute (burn_amount, returned_amount) for a single stake."""
|
||||||
if amount <= 0:
|
amt = _finite_float(amount)
|
||||||
|
if amt is None or amt <= 0:
|
||||||
return 0.0, 0.0
|
return 0.0, 0.0
|
||||||
pct = float(stalemate_burn_pct() if burn_pct is None else burn_pct)
|
pct = _finite_float(stalemate_burn_pct() if burn_pct is None else burn_pct)
|
||||||
|
if pct is None:
|
||||||
|
raise ValueError("burn_pct must be finite")
|
||||||
if pct <= 0:
|
if pct <= 0:
|
||||||
return 0.0, float(amount)
|
return 0.0, amt
|
||||||
if pct >= 1:
|
if pct >= 1:
|
||||||
return float(amount), 0.0
|
return amt, 0.0
|
||||||
burn = float(amount) * pct
|
burn = amt * pct
|
||||||
returned = float(amount) - burn
|
returned = amt - burn
|
||||||
return burn, returned
|
return burn, returned
|
||||||
|
|
||||||
|
|
||||||
@@ -67,18 +82,17 @@ def apply_to_stakes(
|
|||||||
folds these into the larger ``ResolutionResult`` rather than
|
folds these into the larger ``ResolutionResult`` rather than
|
||||||
mutating any state directly.
|
mutating any state directly.
|
||||||
"""
|
"""
|
||||||
pct = float(stalemate_burn_pct() if burn_pct is None else burn_pct)
|
pct = _finite_float(stalemate_burn_pct() if burn_pct is None else burn_pct)
|
||||||
|
if pct is None:
|
||||||
|
raise ValueError("burn_pct must be finite")
|
||||||
returns: dict[tuple[str, str], float] = {}
|
returns: dict[tuple[str, str], float] = {}
|
||||||
total_burned = 0.0
|
total_burned = 0.0
|
||||||
for s in stakes:
|
for s in stakes:
|
||||||
node_id = s.get("node_id") if isinstance(s, dict) else getattr(s, "node_id", None)
|
node_id = s.get("node_id") if isinstance(s, dict) else getattr(s, "node_id", None)
|
||||||
rep_type = s.get("rep_type") if isinstance(s, dict) else getattr(s, "rep_type", None)
|
rep_type = s.get("rep_type") if isinstance(s, dict) else getattr(s, "rep_type", None)
|
||||||
amount = s.get("amount") if isinstance(s, dict) else getattr(s, "amount", None)
|
amount = s.get("amount") if isinstance(s, dict) else getattr(s, "amount", None)
|
||||||
try:
|
amt = _finite_float(amount)
|
||||||
amt = float(amount) if amount is not None else 0.0
|
if amt is None or amt <= 0 or not isinstance(node_id, str) or rep_type not in ("oracle", "common"):
|
||||||
except (TypeError, ValueError):
|
|
||||||
amt = 0.0
|
|
||||||
if amt <= 0 or not isinstance(node_id, str) or rep_type not in ("oracle", "common"):
|
|
||||||
continue
|
continue
|
||||||
burn, ret = split_burn_and_return(amt, pct)
|
burn, ret = split_burn_and_return(amt, pct)
|
||||||
if ret > 0:
|
if ret > 0:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""Regression coverage for non-finite stalemate burn inputs."""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from services.infonet.markets.stalemate_burn import apply_to_stakes, split_burn_and_return
|
||||||
|
|
||||||
|
|
||||||
|
def test_split_rejects_nonfinite_amounts():
|
||||||
|
assert split_burn_and_return(float("nan"), 0.1) == (0.0, 0.0)
|
||||||
|
assert split_burn_and_return(float("inf"), 0.1) == (0.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_skips_nonfinite_stakes_without_poisoning_totals():
|
||||||
|
returns, burned = apply_to_stakes(
|
||||||
|
[
|
||||||
|
{"node_id": "alice", "rep_type": "oracle", "amount": 10.0},
|
||||||
|
{"node_id": "mallory", "rep_type": "oracle", "amount": float("nan")},
|
||||||
|
{"node_id": "eve", "rep_type": "common", "amount": float("inf")},
|
||||||
|
],
|
||||||
|
burn_pct=0.1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert returns == {("alice", "oracle"): 9.0}
|
||||||
|
assert burned == 1.0
|
||||||
|
assert math.isfinite(burned)
|
||||||
|
|
||||||
|
|
||||||
|
def test_nonfinite_burn_percentage_is_rejected():
|
||||||
|
with pytest.raises(ValueError, match="burn_pct must be finite"):
|
||||||
|
split_burn_and_return(10.0, float("nan"))
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="burn_pct must be finite"):
|
||||||
|
apply_to_stakes([], burn_pct=float("inf"))
|
||||||
Reference in new issue
Block a user