bots: shark/fun strategies hit Panic 0x12 (div-by-zero) on currentCharmBoundsWad() at startup; recover but log noise #73

Closed
opened 2026-04-27 03:47:38 +00:00 by Brouie · 7 comments
Brouie commented 2026-04-27 03:47:38 +00:00 (Migrated from gitlab.com)

Found Monday 2026-04-27 during #61 live-stack verification on bd564b0+524288 fix.

Summary

After #61 fix landed (proxy address resolution working — initialMinBuy returns 1e18 from registry address, dev's verification step #4 PASS), bot swarm runs successfully overall:

  • 180 successful buy txs, 0 failed txs across 18 bots
  • Defenders correctly waiting for timer-end window
  • All bots running sustained traffic

But 6 of 18 bot logs (3x shark + 3x fun) show a single ContractPanicError: Panic error 0x12: Division by zero at startup. Bots recover after the error and proceed normally — this is NOT the Friday #61 regression where bots died with no recovery.

Repro

cd /srv/qa/repos/yieldomega
pkill -9 -f anvil; sleep 2
SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh
source bots/timecurve/.env.local
YIELDOMEGA_ANVIL_EXTRA_FUNDED_ADDRESSES=0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 timecurve-bot --send --allow-anvil-funding swarm
sleep 30
grep -l 'Panic error 0x12' /tmp/yieldomega_swarm_*.log

Call site

bots/timecurve/src/timecurve_bot/strategies/common.py L25:

def charm_bounds(tc: Contract) -> tuple[int, int]:
    lo, hi = tc.functions.currentCharmBoundsWad().call()
    return int(lo), int(hi)

The shark/fun strategies call this very early at startup (charm_for_buy wrapper). Defender / rando / pvp / seed-local don't hit it — different code paths or different probe order at startup, or seed-local bots seed state before the others probe.

Hypothesis

currentCharmBoundsWad() divides by something that is zero on a fresh sale before any seed buy has landed. The function is callable on the proxy (so #61 root-cause-resolution proxy plumbing is correct), but the underlying math has an edge case for "sale started, but zero charm sold yet, fun/shark probe before seed-local lands".

The seed-local bots that prime CL8Y mock balances mint and approve, but those don't issue buys — so total_charm_weight or another denominator stays zero until the first real buy.

Severity

LOW. Bots recover; swarm produces 180+ successful buys. But:

  • Logs are noisy with the panic traceback (rich-traceback python output ~30 lines per occurrence)
  • Friday's actual #61 regression marker is the same string, so it's harder to distinguish recovered-edge from broken-stack on future regressions
  • If a real frontend / agent hits this same view function on a fresh stack, it'll panic too — frontend should null-coalesce on view reverts but currentCharmBoundsWad is a high-traffic read

Suggested fixes

Either / or:

  1. Contract side: currentCharmBoundsWad() should return a sane fallback (e.g., (initialMinBuy, initialMaxBuy)) when the underlying denominator is zero (pre-first-buy state). Catch in the math.
  2. Bot side: wrap the startup probe in a retry-with-backoff that swallows Panic 0x12 specifically and waits for the first seed-local buy to land before retrying. Cleaner from QA's perspective since it lets the noise go away without contract changes.

Out of scope

Friday #61 regression itself — separate root cause (proxy address resolution, fixed in 482a25a, verified working today).

cc @PlasticDigits

Found Monday 2026-04-27 during #61 live-stack verification on bd564b0+524288 fix. ## Summary After #61 fix landed (proxy address resolution working — initialMinBuy returns 1e18 from registry address, dev's verification step #4 PASS), bot swarm runs successfully overall: - 180 successful buy txs, 0 failed txs across 18 bots - Defenders correctly waiting for timer-end window - All bots running sustained traffic But **6 of 18 bot logs (3x shark + 3x fun)** show a single `ContractPanicError: Panic error 0x12: Division by zero` at startup. Bots **recover after the error and proceed normally** — this is NOT the Friday #61 regression where bots died with no recovery. ## Repro ``` cd /srv/qa/repos/yieldomega pkill -9 -f anvil; sleep 2 SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh source bots/timecurve/.env.local YIELDOMEGA_ANVIL_EXTRA_FUNDED_ADDRESSES=0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 timecurve-bot --send --allow-anvil-funding swarm sleep 30 grep -l 'Panic error 0x12' /tmp/yieldomega_swarm_*.log ``` ## Call site `bots/timecurve/src/timecurve_bot/strategies/common.py` L25: ``` def charm_bounds(tc: Contract) -> tuple[int, int]: lo, hi = tc.functions.currentCharmBoundsWad().call() return int(lo), int(hi) ``` The shark/fun strategies call this very early at startup (`charm_for_buy` wrapper). Defender / rando / pvp / seed-local don't hit it — different code paths or different probe order at startup, or seed-local bots seed state before the others probe. ## Hypothesis `currentCharmBoundsWad()` divides by something that is zero on a fresh sale before any seed buy has landed. The function is callable on the proxy (so #61 root-cause-resolution proxy plumbing is correct), but the underlying math has an edge case for "sale started, but zero charm sold yet, fun/shark probe before seed-local lands". The seed-local bots that prime CL8Y mock balances mint and approve, but those don't issue buys — so total_charm_weight or another denominator stays zero until the first real buy. ## Severity LOW. Bots recover; swarm produces 180+ successful buys. But: - Logs are noisy with the panic traceback (rich-traceback python output ~30 lines per occurrence) - Friday's actual #61 regression marker is the same string, so it's harder to distinguish recovered-edge from broken-stack on future regressions - If a real frontend / agent hits this same view function on a fresh stack, it'll panic too — frontend should null-coalesce on view reverts but currentCharmBoundsWad is a high-traffic read ## Suggested fixes Either / or: 1. **Contract side:** `currentCharmBoundsWad()` should return a sane fallback (e.g., (initialMinBuy, initialMaxBuy)) when the underlying denominator is zero (pre-first-buy state). Catch in the math. 2. **Bot side:** wrap the startup probe in a retry-with-backoff that swallows Panic 0x12 specifically and waits for the first seed-local buy to land before retrying. Cleaner from QA's perspective since it lets the noise go away without contract changes. ## Out of scope Friday #61 regression itself — separate root cause (proxy address resolution, fixed in 482a25a, verified working today). cc @PlasticDigits
Brouie commented 2026-04-27 03:56:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #61

mentioned in issue #61
PlasticDigits commented 2026-04-27 04:05:47 +00:00 (Migrated from gitlab.com)

mentioned in commit a4052d7b10

mentioned in commit a4052d7b10df7d7fa78a6e02461645276ef11cff
PlasticDigits commented 2026-04-27 04:05:58 +00:00 (Migrated from gitlab.com)

Fix landed on main (ee09f16) — please verify

Summary: TimeCurve._charmBounds no longer divides by initialMinBuy when it is zero (uninitialized implementation / degenerate storage). currentCharmBoundsWad() returns the base CHARM band (0.99e18, 10e18) instead of panic 0x12, so shark/fun startup probes and similar reads do not spew tracebacks. Authorized sale state and buys still require the ERC1967 proxy — this is a view-only resilience fix.

Also: One-line header note on scripts/start-local-anvil-stack.sh that DeployDev uses --code-size-limit 524288 (the stack deploy line itself was already fixed on main in prior commits).

Docs / cross-links: docs/testing/invariants-and-business-logic.md (business table + TimeCurve test map), bots/timecurve/README.md, skills/README.md, .cursor/skills/yieldomega-guardrails/SKILL.md (#73). Forge: test_currentCharmBoundsWad_zero_initialMinBuy_returns_base_envelope.

Checklist for @brouie

  • cd contracts && FOUNDRY_PROFILE=ci forge test --match-test test_currentCharmBoundsWad_zero_initialMinBuy -vv — pass
  • Re-run your swarm repro (SKIP_ANVIL_RICH_STATE=1 stack + timecurve-bot … swarm); grep -l 'Panic error 0x12' /tmp/yieldomega_swarm_*.log — no matches (or only from unrelated / wrong-address calls)
  • On the proxy, cast call … currentCharmBoundsWad() matches previous behavior (same min/max as before this change for initialized sale)
  • Optionally cast call against the implementation address from run-latest.json: should return (99000000000000000, 10000000000000000000) without revert (not for production use)

Leaving the issue open as requested.

## Fix landed on `main` (ee09f16) — please verify **Summary:** `TimeCurve._charmBounds` no longer divides by `initialMinBuy` when it is zero (uninitialized implementation / degenerate storage). `currentCharmBoundsWad()` returns the **base CHARM band** **`(0.99e18, 10e18)`** instead of **panic 0x12**, so shark/fun startup probes and similar reads do not spew tracebacks. **Authorized sale state and buys still require the ERC1967 proxy** — this is a view-only resilience fix. **Also:** One-line header note on `scripts/start-local-anvil-stack.sh` that DeployDev uses `--code-size-limit 524288` (the stack deploy line itself was already fixed on `main` in prior commits). **Docs / cross-links:** `docs/testing/invariants-and-business-logic.md` (business table + TimeCurve test map), `bots/timecurve/README.md`, `skills/README.md`, `.cursor/skills/yieldomega-guardrails/SKILL.md` (#73). Forge: `test_currentCharmBoundsWad_zero_initialMinBuy_returns_base_envelope`. **Checklist for @brouie** - [ ] `cd contracts && FOUNDRY_PROFILE=ci forge test --match-test test_currentCharmBoundsWad_zero_initialMinBuy -vv` — pass - [ ] Re-run your swarm repro (`SKIP_ANVIL_RICH_STATE=1` stack + `timecurve-bot … swarm`); `grep -l 'Panic error 0x12' /tmp/yieldomega_swarm_*.log` — **no matches** (or only from unrelated / wrong-address calls) - [ ] On the **proxy**, `cast call … currentCharmBoundsWad()` matches previous behavior (same min/max as before this change for initialized sale) - [ ] Optionally `cast call` against the **implementation** address from `run-latest.json`: should return **`(99000000000000000, 10000000000000000000)`** without revert (not for production use) Leaving the issue **open** as requested.
Brouie commented 2026-04-27 04:42:49 +00:00 (Migrated from gitlab.com)

@PlasticDigits verified on a4052d7 (with ee09f16 docs companion).

Tests

cd contracts && FOUNDRY_PROFILE=ci forge test --match-test test_currentCharmBoundsWad_zero_initialMinBuy -vv:

  • 1/1 PASS: test_currentCharmBoundsWad_zero_initialMinBuy_returns_base_envelope (gas: 5847049)

Live swarm repro

Same commands as the original repro:

pkill -9 -f anvil; sleep 2
SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh
source bots/timecurve/.env.local
YIELDOMEGA_ANVIL_EXTRA_FUNDED_ADDRESSES=0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 timecurve-bot --send --allow-anvil-funding swarm
sleep 65
grep -l 'Panic error 0x12' /tmp/yieldomega_swarm_*.log

Results:

  • successful buys: 21+ (sustained)
  • failed txs: 0 (the race-condition 'execution reverted 0x' on individual shark cycles is retry-recoverable, normal swarm behavior)
  • panic 0x12 logs: 0 (was 6 of 18 before fix)
  • All 18 bot processes alive

Closing #73.

@PlasticDigits verified on a4052d7 (with ee09f16 docs companion). ## Tests cd contracts && FOUNDRY_PROFILE=ci forge test --match-test test_currentCharmBoundsWad_zero_initialMinBuy -vv: - 1/1 PASS: test_currentCharmBoundsWad_zero_initialMinBuy_returns_base_envelope (gas: 5847049) ## Live swarm repro Same commands as the original repro: ``` pkill -9 -f anvil; sleep 2 SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh source bots/timecurve/.env.local YIELDOMEGA_ANVIL_EXTRA_FUNDED_ADDRESSES=0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 timecurve-bot --send --allow-anvil-funding swarm sleep 65 grep -l 'Panic error 0x12' /tmp/yieldomega_swarm_*.log ``` Results: - successful buys: 21+ (sustained) - failed txs: 0 (the race-condition 'execution reverted 0x' on individual shark cycles is retry-recoverable, normal swarm behavior) - **panic 0x12 logs: 0** (was 6 of 18 before fix) - All 18 bot processes alive Closing #73.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-27 04:42:51 +00:00
Brouie commented 2026-04-27 05:07:22 +00:00 (Migrated from gitlab.com)

mentioned in issue #78

mentioned in issue #78
Brouie commented 2026-05-02 01:07:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #109

mentioned in issue #109
PlasticDigits commented 2026-05-02 04:53:17 +00:00 (Migrated from gitlab.com)

mentioned in commit f6d907ffed

mentioned in commit f6d907ffedc965dc0a223c976b0d7da18d3b4a68
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#73
No description provided.