Skip to content

Commit 97ba58a

Browse files
committed
feat: keeper bot automation of withdrawLeftoverPNK()
1 parent 041f3d9 commit 97ba58a

File tree

1 file changed

+69
-1
lines changed

1 file changed

+69
-1
lines changed

contracts/scripts/keeperBot.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,38 @@ const getDisputesWithContributionsNotYetWithdrawn = async (): Promise<Dispute[]>
217217
return getUniqueDisputes(disputes);
218218
};
219219

220+
const getUnstakedJurors = async (disputeId: string): Promise<string[]> => {
221+
const { gql, request } = await import("graphql-request"); // workaround for ESM import
222+
const query = gql`
223+
query UnstakedJurors($disputeId: String!) {
224+
dispute(id: $disputeId) {
225+
currentRound {
226+
drawnJurors(where: { juror_: { totalStake: 0 } }) {
227+
juror {
228+
userAddress
229+
}
230+
}
231+
}
232+
}
233+
}
234+
`;
235+
type UnstakedJurors = {
236+
dispute: {
237+
currentRound: {
238+
drawnJurors: { juror: { userAddress: string } }[];
239+
};
240+
};
241+
};
242+
const { dispute } = await request<UnstakedJurors>(SUBGRAPH_URL, query, { disputeId });
243+
if (!dispute || !dispute.currentRound) {
244+
return [];
245+
}
246+
const uniqueAddresses = [
247+
...new Set(dispute.currentRound.drawnJurors.map((drawnJuror) => drawnJuror.juror.userAddress)),
248+
];
249+
return uniqueAddresses;
250+
};
251+
220252
const handleError = (e: any) => {
221253
logger.error(e, "Failure");
222254
};
@@ -386,6 +418,29 @@ const executeRuling = async (dispute: { id: string }) => {
386418
return success;
387419
};
388420

421+
const withdrawLeftoverPNK = async (juror: string) => {
422+
const { sortition } = await getContracts();
423+
let success = false;
424+
try {
425+
await sortition.withdrawLeftoverPNK.staticCall(juror);
426+
} catch (e) {
427+
const error = e as CustomError;
428+
const errorDescription = sortition.interface.parseError(error.data)?.signature;
429+
logger.info(`WithdrawLeftoverPNK: failed for juror ${juror} because of ${errorDescription}, skipping`);
430+
return success;
431+
}
432+
try {
433+
const gas = ((await sortition.withdrawLeftoverPNK.estimateGas(juror)) * 150n) / 100n; // 50% extra gas
434+
const tx = await (await sortition.withdrawLeftoverPNK(juror, { gasLimit: gas })).wait();
435+
logger.info(`WithdrawLeftoverPNK txID: ${tx?.hash}`);
436+
} catch (e) {
437+
handleError(e);
438+
} finally {
439+
success = true;
440+
}
441+
return success;
442+
};
443+
389444
const withdrawAppealContribution = async (
390445
coreDisputeId: string,
391446
coreRoundId: string,
@@ -726,7 +781,7 @@ async function main() {
726781
do {
727782
const executeIterations = Math.min(MAX_EXECUTE_ITERATIONS, numberOfMissingRepartitions);
728783
if (executeIterations === 0) {
729-
continue;
784+
break;
730785
}
731786
logger.info(
732787
`Executing ${executeIterations} out of ${numberOfMissingRepartitions} repartitions needed for dispute #${dispute.id}`
@@ -739,6 +794,19 @@ async function main() {
739794
await delay(ITERATIONS_COOLDOWN_PERIOD); // To avoid spiking the gas price
740795
} while (numberOfMissingRepartitions != 0);
741796

797+
// ----------------------------------------------- //
798+
// WITHDRAW LEFTOVER PNK //
799+
// ----------------------------------------------- //
800+
const unstakedJurors = await getUnstakedJurors(dispute.id);
801+
logger.info(`Unstaked jurors: ${unstakedJurors.map((juror) => juror)}`);
802+
for (const juror of unstakedJurors) {
803+
const leftoverPNK = await sortition.getJurorLeftoverPNK(juror);
804+
if (leftoverPNK > 0n) {
805+
logger.info(`Leftover PNK for juror ${juror}: ${leftoverPNK}, withdrawing...`);
806+
await withdrawLeftoverPNK(juror);
807+
}
808+
}
809+
742810
// ----------------------------------------------- //
743811
// RULING EXECUTION //
744812
// ----------------------------------------------- //

0 commit comments

Comments
 (0)