6e7132e935
Механизм: шлюз берёт цену закрытия из локальной истории на момент 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). Остаток: в контракте верхней границы окна по-прежнему нет (второй слой, не критично); серверное авто-закрытие — следующая итерация.
105 lines
5.1 KiB
JavaScript
105 lines
5.1 KiB
JavaScript
/**
|
||
* Проверка схемы «цена закрытия из истории» на РЕАЛЬНОЙ ставке.
|
||
* Читает аккаунт 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); });
|