docs(sec): SEC-16 — безвозвратная утечка ренты PDA-ставки с кошелька игрока
Найдено при приёмке боевого домена smart-updown.binom.pw (симптом «SOL не обновляется»). - SEC-16: при create_bet ренту PDA-аккаунта ставки (1 559 040 lamports = (96+32+128)*6960) платит БЕТТОР, а не шлюз; баланс шлюза-админа при этом не меняется (замер whose-fee.cjs). Аккаунт не закрывается никогда (в контракте 9ALs четыре инструкции, ни одной close = bettor) → рента теряется безвозвратно. В цепи 75 аккаунтов = 0.116928 SOL заперто, растёт линейно. Вердикт: на тестах терпимо (маскируется краном, 5 SOL = ~3207 раундов), перед mainnet — блокер. - TASKS.md: фича «если цена не изменилась — подождать ещё 15 с» (НЕ доделана), заметка про боевой домен на общем TS-шлюзе и лаг крана 8–13 с. - tools: доказательные скрипты (whose-fee, bet-pda-rent, faucet-latency, sol-balance-cycle, settle-math-test, backend-full-test) + README с питфоллами замера. - k8s/smart-updown-api-ingress.yaml: /api,/ws вынесены вне helm (релиз игры их сносил).
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
// Проверка: обновляется ли SOL после полного цикла ставки (на живом домене).
|
||||
const B = "https://smart-updown.binom.pw/api";
|
||||
const RPC = "https://smart-updown.binom.pw/solana-rpc/";
|
||||
const WS_URL = "wss://smart-updown.binom.pw/api/ws";
|
||||
const WebSocket = require(process.env.WS_MODULE || "ws");
|
||||
|
||||
const j = async (p, init) => {
|
||||
const r = await fetch(B + p, init);
|
||||
const t = await r.text();
|
||||
try { return { s: r.status, d: JSON.parse(t) }; } catch { return { s: r.status, d: t }; }
|
||||
};
|
||||
const rpcBal = async (addr) => {
|
||||
const r = await fetch(RPC, {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getBalance", params: [addr] }),
|
||||
});
|
||||
const d = await r.json();
|
||||
return d.result.value;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const log = (k, v) => console.log(`${k.padEnd(34)} ${v}`);
|
||||
|
||||
// 1. свежий кошелёк
|
||||
const w = await j("/wallet", { method: "POST" });
|
||||
const addr = w.d.address;
|
||||
log("1. /wallet → адрес", addr);
|
||||
|
||||
let bal = await rpcBal(addr);
|
||||
log("2. RPC getBalance (пусто)", bal);
|
||||
|
||||
// 2. кран
|
||||
const f = await j(`/faucet/${addr}`);
|
||||
log("3. /faucet", f.s + " sol=" + f.d.sol);
|
||||
|
||||
bal = await rpcBal(addr);
|
||||
log("4. RPC после крана", bal + " (" + (bal / 1e9).toFixed(4) + " SOL)");
|
||||
const gw1 = await j(`/balance/${addr}`);
|
||||
log("5. шлюз /balance", JSON.stringify(gw1.d));
|
||||
|
||||
// 3. WS
|
||||
const ws = new WebSocket(WS_URL);
|
||||
const events = [];
|
||||
await new Promise((res) => { ws.on("open", res); ws.on("error", res); setTimeout(res, 8000); });
|
||||
ws.on("message", (m) => { try { const e = JSON.parse(m); if (e.type && e.type !== "price") events.push(e); } catch {} });
|
||||
log("6. WS", ws.readyState === 1 ? "подключён" : "НЕ подключён");
|
||||
|
||||
// 4. ставка
|
||||
const bet = await j("/bet", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ address: addr, side: "UP", amountUnits: "100000000" }),
|
||||
});
|
||||
log("7. POST /bet", bet.s + " " + JSON.stringify(bet.d).slice(0, 110));
|
||||
|
||||
const before = await rpcBal(addr);
|
||||
log("8. RPC сразу после ставки", before + " (" + (before / 1e9).toFixed(6) + " SOL)");
|
||||
|
||||
// 5. ждём авто-закрытие
|
||||
const id = bet.d.id;
|
||||
await new Promise((r) => setTimeout(r, 20000));
|
||||
|
||||
const after = await rpcBal(addr);
|
||||
log("9. RPC после закрытия (~20с)", after + " (" + (after / 1e9).toFixed(6) + " SOL)");
|
||||
const gw2 = await j(`/balance/${addr}`);
|
||||
log("10. шлюз /balance после", JSON.stringify(gw2.d));
|
||||
|
||||
log("11. WS-события", JSON.stringify(events).slice(0, 300));
|
||||
|
||||
const delta = after - before;
|
||||
log("12. ДЕЛЬТА на цепи", delta + " lamports (" + (delta / 1e9).toFixed(6) + " SOL)");
|
||||
log("13. читается ли /api/bet/" + id, (await j(`/bet/${id}`)).s);
|
||||
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
})();
|
||||
Reference in New Issue
Block a user