docs(security): SEC-1, SEC-2, SEC-4 закрыты по сути — цена закрытия из истории

Механизм: шлюз берёт цену закрытия из локальной истории на момент expire_time
ставки (priceHistory, node:sqlite, ретеншен 24ч), а не текущую. Момент клика
перестаёт влиять на исход — окно выбора времени закрыто.

Живая проверка (ставка №10, релиз updown-relayer:3):
  цена на момент экспирации 9977000000
  текущая цена в момент клика 9976000000
  exitPrice в транзакции 9977000000  -> взята история, не 'сейчас'
  статус payout_done

Также: tools/ — скрипты проверки (price-history-probe, exploit-decisive,
exploit-check, check-close-window.sh).

Остаток: в контракте верхней границы окна по-прежнему нет (второй слой, не критично);
серверное авто-закрытие — следующая итерация.
This commit is contained in:
2026-09-13 14:47:21 +03:00
parent 4caddd6a73
commit 6e7132e935
5 changed files with 466 additions and 5 deletions
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Живая проверка того, что окно выбора времени ЗАКРЫТО.
#
# Схема проверки (повторяет поведение вора):
# 1. создаём кошелёк, пополняем
# 2. ставим ставку
# 3. ЖДЁМ существенно дольше экспирации (по умолчанию 60 с; экспирация 15 с)
# 4. закрываем и смотрим, какую цену вернул шлюз
#
# Ожидание ПОСЛЕ фикса: цена закрытия == цена на момент экспирации (из истории),
# а НЕ текущая цена на момент клика. Плюс шлюз должен отказать, если цена закрытия
# совпала с ценой входа (ставка останется open — это отдельная задача).
#
# Использование: bash check-exploit.sh [секунды_ожидания]
set -uo pipefail
G="${GW_URL:-https://testfront.binom.pw/api}"
WAIT_SEC="${1:-60}"
jqget() { python3 -c "import sys,json;print(json.load(sys.stdin)$1)" 2>/dev/null; }
echo "=== 1. кошелёк ==="
W=$(curl -s --max-time 30 -X POST "$G/wallet" -H 'Content-Type: application/json' -d '{}')
A=$(echo "$W" | jqget "['address']")
echo "address=$A"
[ -z "$A" ] && { echo "ОШИБКА: адрес не получен ($W)"; exit 1; }
echo "=== 2. пополнение ==="
curl -s --max-time 60 "$G/faucet/$A" | head -c 200; echo
echo "=== 3. ставка ==="
BET=$(curl -s --max-time 45 -X POST "$G/bet" -H 'Content-Type: application/json' \
-d "{\"side\":\"UP\",\"amountUnits\":\"100000000\",\"address\":\"$A\"}")
echo "$BET"
ID=$(echo "$BET" | jqget "['id']")
ENTRY=$(echo "$BET" | jqget "['entryPrice']")
[ -z "$ID" ] && { echo "ОШИБКА: ставка не создана"; exit 1; }
echo "id=$ID entryPrice=$ENTRY"
echo
echo "=== 4. цена шлюза СЕЙЧАС (до экспирации) ==="
curl -s --max-time 20 "$G/price"; echo
echo
echo "=== 5. ждём ${WAIT_SEC}с (экспирация 15с — окно прошло) ==="
sleep "$WAIT_SEC"
echo "=== 6. цена шлюза ПОСЛЕ ожидания (текущая, которую вор хотел бы получить) ==="
P_NOW=$(curl -s --max-time 20 "$G/price")
echo "$P_NOW"
echo
echo "=== 7. ЗАКРЫТИЕ — какую цену возьмёт? ==="
BEFORE=$(date -u +%s)
CLOSE=$(curl -s --max-time 45 -X POST "$G/close" -H 'Content-Type: application/json' -d "{\"id\":\"$ID\"}")
AFTER=$(date -u +%s)
echo "$CLOSE"
EXIT=$(echo "$CLOSE" | jqget "['exitPrice']")
echo
echo "================ ИТОГ ================"
echo "entryPrice (вход) : $ENTRY"
echo "exitPrice (шлюз вернул) : $EXIT"
echo "цена в момент клика : $P_NOW"
echo
if [ -z "$EXIT" ]; then
echo "ВЕРДИКТ: закрытие НЕ выполнилось (см. ответ выше)."
elif [ "$EXIT" = "$ENTRY" ]; then
echo "ВЕРДИКТ: exit == entry -> ставка осталась open (равные цены, отдельная задача)."
echo " Главное: цена НЕ взята произвольно с момента клика."
else
# Сравниваем exit с ценой, которая была бы при старом поведении
CUR=$(echo "$P_NOW" | jqget "['price']")
if [ -n "$CUR" ]; then
echo "Текущая цена в момент клика: $CUR (в целом виде $(python3 -c "print(int(float('$CUR')*10**8))" 2>/dev/null))"
fi
echo "Смотрим глазами: exitPrice должен соответствовать цене НА МОМЕНТ ЭКСПИРАЦИИ,"
echo "а не текущей. Если exitPrice == текущей цене клика — ОКНО ОТКРЫТО, фикс не работает."
fi
echo "закрытие заняло: $((AFTER-BEFORE))с"