Skip to content

fix: finalize-round.sh set -e silent exit on already-finalized round#3

Open
ClawdiaETH wants to merge 5 commits intofeat/twitter-cap-exceeded-guardfrom
fix/finalize-set-e
Open

fix: finalize-round.sh set -e silent exit on already-finalized round#3
ClawdiaETH wants to merge 5 commits intofeat/twitter-cap-exceeded-guardfrom
fix/finalize-set-e

Conversation

@ClawdiaETH
Copy link
Owner

Problem

set -e causes the script to exit with code 1 when cast send finalizeRound() returns non-zero (e.g. round is already finalized), before the elif grep "Already finalized" check runs. This produces noisy false failures with no actionable info.

Observed: Round 7 was already finalized but the script reported exit 1 on 2026-02-27 09:48 AM.

Fix

Wrap cast send with || true so the non-zero exit code doesn't trigger set -e. All outcome handling (success, already-finalized, real error) remains in the if/elif/else block below.

Why || true is safe here

The output is captured in $TX regardless of exit code. The downstream if echo "$TX" | grep -q checks handle all cases correctly — this was always the intent, just blocked by set -e.

- base.drpc.org free tier rejects ranges >10k blocks
- mainnet.base.org also limits to 10k AND was returning 503
- paginate the 25k-block SeedAndRulerRevealed lookup in 9500-block chunks
- switch RPC to base-rpc.publicnode.com (reliable, no range issues)
getCurrentRound() returns the new round N, but SeedAndRulerRevealed
event is for round N-1 (the round just revealed). Event filter was
using roundId=N and finding nothing, causing silent failure every round.

Fix: derive revealedRoundId = roundId - 1, use for event filter
and announcement text.
cast send returns non-zero when round is already finalized, which caused
set -e to kill the script before the 'Already finalized' check ran.
Adding || true prevents the premature exit while preserving all logic.

Observed: round 7 was already finalized but script reported exit 1 (2026-02-27).
@vercel
Copy link

vercel bot commented Mar 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spellblock Ready Ready Preview, Comment Mar 1, 2026 5:15am

Request Review

@cursor
Copy link

cursor bot commented Mar 1, 2026

PR Summary

Medium Risk
Touches round reveal/finalize flows by changing RPC providers and how on-chain SeedAndRulerRevealed logs are fetched, which could affect spell/length detection and downstream scoring/announcements if misconfigured. The shell script change is low risk but impacts operational behavior by preventing false failures on already-finalized rounds.

Overview
Improves reliability of on-chain event retrieval for SpellBlock reveal/finalize by switching the Base RPC endpoint and paginating viem getLogs calls in ~9.5k-block chunks to stay under common public RPC range limits.

Hardens scripts/finalize-round.sh by allowing cast send finalizeRound() to fail without tripping set -e, so the script can correctly report "Already finalized" vs real errors based on captured output.

Written by Cursor Bugbot for commit 4fe9ebd. This will update automatically on new commits. Configure here.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Wrong round ID used for reveal announcement
    • Updated the social reveal script to use the current open round ID for event lookup and announcement text instead of subtracting one.
Preview (4fe9ebd8b4)
diff --git a/bot/spellblock-finalize.mjs b/bot/spellblock-finalize.mjs
--- a/bot/spellblock-finalize.mjs
+++ b/bot/spellblock-finalize.mjs
@@ -19,7 +19,7 @@
 
 const __dir = dirname(fileURLToPath(import.meta.url));
 const CONTRACT = '0x43F8658F3E85D1F92289e3036168682A9D14c683';
-const RPC = 'https://base.drpc.org';
+const RPC = 'https://1rpc.io/base'; // paginated getLogs in 9500-block chunks — most public RPCs limit to 10k
 const FOUNDRY = '/Users/starl3xx/.foundry/bin';
 const BOT_BANKR_HANDLE = 'ClawdiaBotAI';
 const CLAWDIA_DECIMALS = 18n;
@@ -99,12 +99,17 @@
   const client = createPublicClient({ chain: base, transport: http(RPC) });
   const latest = await client.getBlockNumber();
 
-  const seedLogs = await client.getLogs({
-    address: CONTRACT,
-    event: parseAbiItem('event SeedAndRulerRevealed(uint256 indexed roundId, uint8 spellId, bytes32 spellParam, uint8[3] validLengths)'),
-    fromBlock: latest - 25000n, // ~14h of Base blocks (2s/block) covers reveal→finalize gap
-    toBlock: 'latest',
-  });
+  // Paginate getLogs in 9500-block chunks — public RPCs limit ranges to 10k blocks
+  const CHUNK = 9500n;
+  const lookback = 25000n;
+  const startBlock = latest - lookback;
+  const seedAbi = parseAbiItem('event SeedAndRulerRevealed(uint256 indexed roundId, uint8 spellId, bytes32 spellParam, uint8[3] validLengths)');
+  const seedLogs = [];
+  for (let from = startBlock; from <= latest; from += CHUNK) {
+    const to = from + CHUNK - 1n < latest ? from + CHUNK - 1n : latest;
+    const chunk = await client.getLogs({ address: CONTRACT, event: seedAbi, fromBlock: from, toBlock: to });
+    seedLogs.push(...chunk);
+  }
 
   const seedLog = seedLogs.filter(l => Number(l.args.roundId) === round.round_id).pop();
 

diff --git a/bot/spellblock-social-reveal.mjs b/bot/spellblock-social-reveal.mjs
--- a/bot/spellblock-social-reveal.mjs
+++ b/bot/spellblock-social-reveal.mjs
@@ -16,7 +16,7 @@
 
 const __dir = dirname(fileURLToPath(import.meta.url));
 const CONTRACT = '0x43F8658F3E85D1F92289e3036168682A9D14c683';
-const RPC = 'https://base.drpc.org';
+const RPC = 'https://1rpc.io/base';
 
 const SPELL_NAMES = {
   0: { name: 'Veto 🚫',  desc: (p) => `word must NOT contain ${p}` },
@@ -79,27 +79,38 @@
   const { base } = await import('viem/chains');
   const client = createPublicClient({ chain: base, transport: http(RPC) });
 
-  // Get current round from DB
+  // Get current round from DB — reveal-seed-and-ruler.sh reveals for this same
+  // currently open round.
   const round = await db.getCurrentRound();
   if (!round) { log('No open round in DB'); await db.end(); return; }
 
   const roundId = round.round_id;
-  log(`Round ${roundId} | letters: ${round.letters}`);
+  const revealedRoundId = roundId; // The round that was just revealed on-chain
+  log(`Current round: ${roundId} | letters: ${round.letters} | Announcing reveal for round: ${revealedRoundId}`);
 
   // Read SeedAndRulerRevealed event from contract
+  // Paginate in 9500-block chunks to stay under public RPC limits (10k max)
   const latest = await client.getBlockNumber();
-  const logs = await client.getLogs({
-    address: CONTRACT,
-    event: parseAbiItem(
-      'event SeedAndRulerRevealed(uint256 indexed roundId, uint8 spellId, bytes32 spellParam, uint8[3] validLengths)'
-    ),
-    fromBlock: latest - 25000n, // ~14h of Base blocks; covers the reveal→post window
-    toBlock: 'latest',
-  });
+  const LOOKBACK = 25000n;
+  const CHUNK = 9500n;
+  const eventAbi = parseAbiItem(
+    'event SeedAndRulerRevealed(uint256 indexed roundId, uint8 spellId, bytes32 spellParam, uint8[3] validLengths)'
+  );
+  const logs = [];
+  for (let from = latest - LOOKBACK; from <= latest; from += CHUNK) {
+    const to = from + CHUNK - 1n < latest ? from + CHUNK - 1n : latest;
+    const chunk = await client.getLogs({
+      address: CONTRACT,
+      event: eventAbi,
+      fromBlock: from,
+      toBlock: to,
+    });
+    logs.push(...chunk);
+  }
 
-  const revealLog = logs.filter(l => Number(l.args.roundId) === roundId).pop();
+  const revealLog = logs.filter(l => Number(l.args.roundId) === revealedRoundId).pop();
   if (!revealLog) {
-    log('❌ No SeedAndRulerRevealed event found — round not yet revealed or event window too narrow');
+    log(`❌ No SeedAndRulerRevealed event found for round ${revealedRoundId} — not yet revealed or event window too narrow`);
     await db.end();
     return;
   }
@@ -118,7 +129,7 @@
 
   // Compose announcement
   const text =
-    `🔮 SpellBlock Round ${roundId} — Spell + Ruler revealed!\n\n` +
+    `🔮 SpellBlock Round ${revealedRoundId} — Spell + Ruler revealed!\n\n` +
     `${spell.name}: ${spellDesc}\n` +
     `📏 Valid lengths: ${validLengths.join(', ')}\n\n` +
     `Scoring + payouts happen automatically at 15:45 UTC (9:45 AM CT).\n\n` +

diff --git a/scripts/finalize-round.sh b/scripts/finalize-round.sh
--- a/scripts/finalize-round.sh
+++ b/scripts/finalize-round.sh
@@ -28,11 +28,13 @@
 fi
 
 echo "Finalizing Round $CURRENT_ROUND..."
+# Use || true so set -e doesn't exit on non-zero cast return (e.g. "Already finalized"),
+# letting the if/elif/else below handle all cases gracefully.
 TX=$(/Users/starl3xx/.foundry/bin/cast send $CONTRACT \
   "finalizeRound()" \
   --private-key $PRIVATE_KEY \
   --rpc-url https://mainnet.base.org \
-  --json 2>&1)
+  --json 2>&1) || true
 
 if echo "$TX" | grep -q "transactionHash"; then
   TX_HASH=$(echo $TX | /opt/homebrew/bin/jq -r '.transactionHash')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants