SHA256
Переписать shine_payments и обновить тестовый UI с известным багом state
This commit is contained in:
@@ -83,12 +83,14 @@
|
||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||
|
||||
const SEEDS = {
|
||||
config: "shine_payments_v3_config",
|
||||
coef: "shine_payments_v3_coef_limit",
|
||||
queues: "shine_payments_v3_queues",
|
||||
ticketQ1: "shine_payments_v3_q1_ticket",
|
||||
ticketQ2: "shine_payments_v3_q2_ticket",
|
||||
config: "shine_payments_config",
|
||||
coef: "shine_payments_coef_limit",
|
||||
queues: "shine_payments_queues",
|
||||
ticketQ1: "shine_payments_q1_ticket",
|
||||
ticketQ2: "shine_payments_q2_ticket",
|
||||
ticketQ3: "shine_payments_q3_ticket",
|
||||
};
|
||||
const IX = { changeTicketRecipient: 9, stepPayout: 8 };
|
||||
|
||||
const LAMPORTS_PER_SOL = 1_000_000_000n;
|
||||
let walletPubkey = null;
|
||||
@@ -133,10 +135,8 @@
|
||||
function centsToUsdStr(c) {
|
||||
return trimZeros((Number(c) / 100).toFixed(2));
|
||||
}
|
||||
async function ixDiscriminator(name) {
|
||||
const msg = utf8("global:" + name);
|
||||
const hash = await crypto.subtle.digest("SHA-256", msg);
|
||||
return new Uint8Array(hash).slice(0, 8);
|
||||
function ixData(tag, ...parts) {
|
||||
return concat(new Uint8Array([tag]), ...parts);
|
||||
}
|
||||
function isNotEnoughForStep(msg) {
|
||||
const s = String(msg || "").toLowerCase();
|
||||
@@ -187,7 +187,11 @@
|
||||
const q2Paid = readU64(data, o); o += 8;
|
||||
const q2SumTotal = readU64(data, o); o += 8;
|
||||
const q2SumPaid = readU64(data, o); o += 8;
|
||||
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid };
|
||||
const q3Total = readU64(data, o); o += 8;
|
||||
const q3Paid = readU64(data, o); o += 8;
|
||||
const q3SumTotal = readU64(data, o); o += 8;
|
||||
const q3SumPaid = readU64(data, o); o += 8;
|
||||
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid, q3Total, q3Paid, q3SumTotal, q3SumPaid };
|
||||
}
|
||||
function parseTicket(data) {
|
||||
let o = 0;
|
||||
@@ -232,7 +236,7 @@
|
||||
return { configPda, coefPda, queuesPda };
|
||||
}
|
||||
function deriveTicketPda(queueId, index) {
|
||||
const seed = queueId === 1 ? SEEDS.ticketQ1 : SEEDS.ticketQ2;
|
||||
const seed = queueId === 1 ? SEEDS.ticketQ1 : (queueId === 2 ? SEEDS.ticketQ2 : SEEDS.ticketQ3);
|
||||
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
||||
return pda;
|
||||
}
|
||||
@@ -262,14 +266,16 @@
|
||||
function nextStepQueue(queues) {
|
||||
const q1Pending = queues.q1Total - queues.q1Paid;
|
||||
const q2Pending = queues.q2Total - queues.q2Paid;
|
||||
const q3Pending = queues.q3Total - queues.q3Paid;
|
||||
if (q1Pending > 0n) return 1;
|
||||
if (q2Pending > 0n) return 2;
|
||||
if (q3Pending > 0n) return 3;
|
||||
return 0;
|
||||
}
|
||||
function nextPayoutTicket(queues) {
|
||||
const queue = nextStepQueue(queues);
|
||||
if (queue === 0) return null;
|
||||
const index = queue === 1 ? (queues.q1Paid + 1n) : (queues.q2Paid + 1n);
|
||||
const index = queue === 1 ? (queues.q1Paid + 1n) : (queue === 2 ? (queues.q2Paid + 1n) : (queues.q3Paid + 1n));
|
||||
return { queue, index };
|
||||
}
|
||||
|
||||
@@ -284,13 +290,13 @@
|
||||
<div>Курс SOL/USD (Pyth): <b>${trimZeros((Number(core.pyth.num) / Number(core.pyth.den) / 100).toFixed(6))}</b>, возраст <b>${pythAge} сек</b></div>
|
||||
<div>DAO: <code>${core.config.dao.toBase58()}</code></div>
|
||||
<div class="muted">Сейчас это тестовый DAO-кошелек. В production здесь будет адрес реального DAO.</div>
|
||||
<div>Обе очереди пусты/полностью выплачены.</div>
|
||||
<div>Все очереди пусты/полностью выплачены.</div>
|
||||
<div>На inflow vault доступно (сверх ренты): <b>${lamportsToSolStr(core.available)} SOL</b></div>
|
||||
<div class="warn">При шаге эта сумма уйдет в DAO, награда не начисляется.</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : core.queues.q2Paid + 1n;
|
||||
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : (queue === 2 ? core.queues.q2Paid + 1n : core.queues.q3Paid + 1n);
|
||||
const nextPda = deriveTicketPda(queue, nextIndex);
|
||||
const nextAi = await connection.getAccountInfo(nextPda, "confirmed");
|
||||
if (!nextAi) {
|
||||
@@ -299,7 +305,7 @@
|
||||
}
|
||||
const next = parseTicket(nextAi.data);
|
||||
const ticketLamports = usdCentsToLamportsCeil(next.payoutUsdCents, core.pyth);
|
||||
const daoUsd = queue === 1 ? next.payoutUsdCents : (next.payoutUsdCents * 2n);
|
||||
const daoUsd = next.payoutUsdCents * BigInt(queue);
|
||||
const daoLamports = usdCentsToLamportsCeil(daoUsd, core.pyth);
|
||||
const need = ticketLamports + daoLamports + core.coef.reward;
|
||||
const missing = core.available >= need ? 0n : (need - core.available);
|
||||
@@ -313,7 +319,7 @@
|
||||
<div>DAO на этом шаге: <b>${centsToUsdStr(daoUsd)} USD</b> (~${lamportsToSolStr(daoLamports)} SOL)</div>
|
||||
<div>Награда за шаг: <b>${lamportsToSolStr(core.coef.reward)} SOL</b></div>
|
||||
<div>Нужно для шага: <b>${lamportsToSolStr(need)} SOL</b></div>
|
||||
<div>Формула: <b>${queue === 1 ? "ticket + dao(1x) + reward" : "ticket + dao(2x) + reward"}</b></div>
|
||||
<div>Формула: <b>ticket + dao(${queue}x) + reward</b></div>
|
||||
<div>Доступно в inflow vault: <b>${lamportsToSolStr(core.available)} SOL</b></div>
|
||||
<div>${missing === 0n
|
||||
? '<span class="ok">Хватает для шага выплаты.</span>'
|
||||
@@ -374,8 +380,7 @@
|
||||
const newRecipient = new solanaWeb3.PublicKey(newRecipientRaw);
|
||||
|
||||
const core = cachedCore || await loadCoreState();
|
||||
const disc = await ixDiscriminator("change_ticket_recipient");
|
||||
const data = concat(disc, newRecipient.toBytes());
|
||||
const data = ixData(IX.changeTicketRecipient, newRecipient.toBytes());
|
||||
const keys = [
|
||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||
{ pubkey: core.pdas.queuesPda, isSigner: false, isWritable: true },
|
||||
@@ -404,7 +409,7 @@
|
||||
|
||||
if (idxRaw) {
|
||||
const idx = BigInt(idxRaw);
|
||||
for (const queue of [1, 2]) {
|
||||
for (const queue of [1, 2, 3]) {
|
||||
const pda = deriveTicketPda(queue, idx);
|
||||
const ai = await connection.getAccountInfo(pda, "confirmed");
|
||||
if (!ai) continue;
|
||||
@@ -413,8 +418,8 @@
|
||||
if (results.length === 0) throw new Error(`Тикет #${idx.toString()} не найден ни в одной очереди`);
|
||||
} else if (walletRaw) {
|
||||
const recipient = new solanaWeb3.PublicKey(walletRaw);
|
||||
for (const queue of [1, 2]) {
|
||||
const total = queue === 1 ? core.queues.q1Total : core.queues.q2Total;
|
||||
for (const queue of [1, 2, 3]) {
|
||||
const total = queue === 1 ? core.queues.q1Total : (queue === 2 ? core.queues.q2Total : core.queues.q3Total);
|
||||
for (let i = 1n; i <= total; i++) {
|
||||
const pda = deriveTicketPda(queue, i);
|
||||
const ai = await connection.getAccountInfo(pda, "confirmed");
|
||||
@@ -450,15 +455,14 @@
|
||||
nextTicketPda = deriveTicketPda(1, core.queues.q1Paid + 1n);
|
||||
recipient = walletPubkey;
|
||||
} else {
|
||||
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : core.queues.q2Paid + 1n;
|
||||
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : (queue === 2 ? core.queues.q2Paid + 1n : core.queues.q3Paid + 1n);
|
||||
nextTicketPda = deriveTicketPda(queue, nextIndex);
|
||||
const ai = await connection.getAccountInfo(nextTicketPda, "confirmed");
|
||||
if (!ai) throw new Error(`Следующий тикет #${nextIndex.toString()} для очереди ${queue} не найден`);
|
||||
recipient = parseTicket(ai.data).recipient;
|
||||
}
|
||||
|
||||
const disc = await ixDiscriminator("step_payout");
|
||||
const data = concat(disc);
|
||||
const data = ixData(IX.stepPayout);
|
||||
const keys = [
|
||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||
{ pubkey: core.pdas.configPda, isSigner: false, isWritable: true },
|
||||
|
||||
Reference in New Issue
Block a user