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,37 @@
|
||||
// Сколько lamports лежит в PDA ставок и кому принадлежит рента
|
||||
const { PublicKey } = require(process.env.SOLANA_WEB3_MODULE || "@solana/web3.js");
|
||||
const RPC = "https://smart-updown.binom.pw/solana-rpc/";
|
||||
const PROGRAM = new PublicKey("9ALsnxXNzDBv3mokngCHbruRTWUZiWR7pf7vswS1fCpf");
|
||||
const BET_SEED = Buffer.from("bet");
|
||||
const GLOBAL_SEED = Buffer.from("global");
|
||||
const VAULT_SEED = Buffer.from("vault");
|
||||
|
||||
const rpc = async (m, p) => {
|
||||
const r = await fetch(RPC, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: m, params: p }) });
|
||||
const x = await r.json();
|
||||
if (x.error) throw new Error(JSON.stringify(x.error).slice(0, 150));
|
||||
return x.result;
|
||||
};
|
||||
const betPda = (id) => { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(id)); return PublicKey.findProgramAddressSync([BET_SEED, b], PROGRAM)[0]; };
|
||||
|
||||
(async () => {
|
||||
console.log(`программа: ${PROGRAM.toBase58()}`);
|
||||
const g = PublicKey.findProgramAddressSync([GLOBAL_SEED], PROGRAM)[0];
|
||||
const v = PublicKey.findProgramAddressSync([GLOBAL_SEED, VAULT_SEED], PROGRAM)[0];
|
||||
console.log(`global PDA: ${g.toBase58()}`);
|
||||
console.log(`vault PDA: ${v.toBase58()}\n`);
|
||||
|
||||
let total = 0n, alive = 0;
|
||||
for (const id of [60, 65, 67, 68, 69, 70, 71, 72, 73, 74]) {
|
||||
const p = betPda(id);
|
||||
const r = await rpc("getAccountInfo", [p.toBase58(), { encoding: "base64" }]);
|
||||
const info = r.value;
|
||||
if (!info) { console.log(`ставка ${id}: PDA ${p.toBase58().slice(0, 8)}… НЕТ в цепи`); continue; }
|
||||
alive++; total += BigInt(info.lamports);
|
||||
console.log(`ставка ${id}: PDA ${p.toBase58().slice(0, 8)}… lamports=${info.lamports} owner=${info.owner.slice(0, 8)}… размер=${info.data[0].length * 3 / 4} байт`);
|
||||
}
|
||||
console.log(`\nвсего PDA ставок: ${alive}; суммарно lamports в них: ${total} (${Number(total) / 1e9} SOL)`);
|
||||
const gr = await rpc("getAccountInfo", [g.toBase58(), { encoding: "base64" }]);
|
||||
console.log(`global PDA lamports: ${gr.value ? gr.value.lamports : "нет"}`);
|
||||
process.exit(0);
|
||||
})();
|
||||
Reference in New Issue
Block a user