101a4b60a9
Найдено при приёмке боевого домена 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 (релиз игры их сносил).
77 lines
2.9 KiB
JavaScript
77 lines
2.9 KiB
JavaScript
#!/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);
|
|
})();
|