Files
pump-game-ops/tools/price-history-probe.cjs
subochev 6e7132e935 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).

Остаток: в контракте верхней границы окна по-прежнему нет (второй слой, не критично);
серверное авто-закрытие — следующая итерация.
2026-09-13 14:47:21 +03:00

105 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Проверка схемы «цена закрытия из истории» на РЕАЛЬНОЙ ставке.
* Читает аккаунт Bet из localnet СЫРЫМИ БАЙТАМИ (без Anchor-обёрток),
* берёт bet_time/expire_time/entry_price и спрашивает Postgres binance-market.
*
* Раскладка Bet (Anchor, после 8-байтового дискриминатора):
* offset 8 : amount u64
* offset 16 : bet_time i64
* offset 24 : expire_time i64
* offset 32 : entry_price u64
* offset 40 : exit_price u64
* offset 48 : side u64
* offset 56 : status u64
* offset 64 : bettor Pubkey(32)
*/
const { Connection, PublicKey } = require("@solana/web3.js");
const { Client } = require("pg");
const PROGRAM_ID = new PublicKey(process.argv[2] || "9ALsnxXNzDBv3mokngCHbruRTWUZiWR7pf7vswS1fCpf");
const BET_ID = Number(process.argv[3] || 6);
const RPC = "http://192.168.76.181:8899";
const u64 = (b, o) => b.readBigUInt64LE(o);
const i64 = (b, o) => b.readBigInt64LE(o);
(async () => {
const conn = new Connection(RPC, "confirmed");
const seeds = Buffer.concat([Buffer.from("bet"), (() => {
const a = Buffer.alloc(8); a.writeBigUInt64LE(BigInt(BET_ID)); return a;
})()]);
const [betPda] = PublicKey.findProgramAddressSync([seeds], PROGRAM_ID);
const info = await conn.getAccountInfo(betPda, "confirmed");
if (!info) throw new Error("bet account not found: " + betPda.toBase58());
const d = info.data;
const amount = u64(d, 8);
const betTime = Number(i64(d, 16));
const exp = Number(i64(d, 24));
const entry = u64(d, 32);
const exitP = u64(d, 40);
const side = u64(d, 48);
const status = u64(d, 56);
const bettor = new PublicKey(d.subarray(64, 96)).toBase58();
console.log("=== СТАВКА №" + BET_ID + " (сырые байты аккаунта) ===");
console.log("PDA :", betPda.toBase58());
console.log("amount :", amount.toString());
console.log("bet_time :", betTime, new Date(betTime * 1000).toISOString());
console.log("expire_time:", exp, new Date(exp * 1000).toISOString(), "(=bet_time+" + (exp - betTime) + "с)");
console.log("entry_price:", entry.toString(), " exit_price:", exitP.toString());
console.log("side:", side.toString(), " status:", status.toString(), " bettor:", bettor);
console.log("СЕЙЧАС :", Math.floor(Date.now() / 1000), new Date().toISOString());
console.log("просрочена на:", Math.floor(Date.now() / 1000) - exp, "с");
const c = new Client({ host: "192.168.76.182", port: 5434, user: "postgres", password: "postgres", database: "binance_market" });
await c.connect();
const q = async (label, sql, params) => {
const r = await c.query(sql, params);
console.log("\n--- " + label + " ---");
if (!r.rows.length) console.log("(ПУСТО — цены нет вовсе)");
for (const row of r.rows) console.log(JSON.stringify(row));
return r.rows;
};
const bMs = betTime * 1000, eMs = exp * 1000;
await q("1) сделки в окне ставки [bet_time, expire]", `
SELECT price::text AS price, event_time, to_timestamp(event_time/1000) AS t
FROM trade_event WHERE symbol='solusdt' AND event_time BETWEEN $1 AND $2
ORDER BY event_time`, [bMs, eMs]);
const e1 = await q("2) ENTRY = последняя цена НЕ ПОЗЖЕ bet_time", `
SELECT price::text AS price, event_time, to_timestamp(event_time/1000) AS t
FROM trade_event WHERE symbol='solusdt' AND event_time <= $1
ORDER BY event_time DESC LIMIT 1`, [bMs]);
const e2 = await q("3) EXIT = последняя цена НЕ ПОЗЖЕ expire_time", `
SELECT price::text AS price, event_time, to_timestamp(event_time/1000) AS t
FROM trade_event WHERE symbol='solusdt' AND event_time <= $1
ORDER BY event_time DESC LIMIT 1`, [eMs]);
if (e1[0] && e2[0]) {
const p1 = BigInt(e1[0].price.replace(".", "").padEnd(0));
const a = e1[0].price, b = e2[0].price;
console.log("\n=== ИТОГ ПО СХЕМЕ ===");
console.log("entry из истории:", a, "| entry on-chain:", entry.toString(), "(on-chain = price*1e8)");
console.log("exit из истории:", b);
const toI = (s) => { const [i, f = ""] = s.split("."); return BigInt(i + f.padEnd(8, "0").slice(0, 8)); };
const ai = toI(a), bi = toI(b);
console.log("entry(int):", ai.toString(), " exit(int):", bi.toString());
console.log("вердикт по контракту:", bi > ai ? "UP выиграл" : bi < ai ? "DOWN выиграл" : "РАВНО — ставка зависает (текущий контракт не финализирует)");
}
await q("4) ЧТО БЫЛО БЫ при 'ждать выгодной цены' (после экспирации, +2 мин)", `
SELECT min(price)::text AS min_p, max(price)::text AS max_p, count(*) AS n,
min(to_timestamp(event_time/1000)) AS с, max(to_timestamp(event_time/1000)) AS по
FROM trade_event WHERE symbol='solusdt' AND event_time > $1 AND event_time <= $2`,
[eMs, (exp + 120) * 1000]);
await c.end();
})().catch((e) => { console.error("ERR:", e.message); process.exit(1); });