REGRESSION: Bot swarm hits ContractPanicError (div-by-zero) and BadFunctionCallOutput on live Anvil stack after UUPS proxy migration #61

Closed
opened 2026-04-24 06:45:15 +00:00 by Brouie · 13 comments
Brouie commented 2026-04-24 06:45:15 +00:00 (Migrated from gitlab.com)

Found during #39 regression walkthrough. All 18 bots fail against a fresh SKIP_ANVIL_RICH_STATE=1 stack bring-up; 178/178 forge tests still pass. Deployment or proxy-init regression suspected.

Repro

  1. Fresh QA server: git pull origin main + git submodule update --init --recursive (HEAD at dec166e)
  2. cd /srv/qa/repos/yieldomega && SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh
  3. Wait for 'Stack is up.' — Anvil on 8545, indexer on 3101
  4. cd bots/timecurve && source .env.local && timecurve-bot --send --allow-anvil-funding swarm
  5. 18 bot processes launch. PIDs in /tmp/yieldomega_bot_swarm.pids.

Observed

Three distinct failure modes across 18 bots:

1. shark-0/1/2 + fun-0/1/2 (6 bots)

ContractPanicError: ('Panic error 0x12: Division by zero.', '0x4e487b71...0x12')

Panic 0x12 is Solidity's division-by-zero panic. Strategy bots hit it at their first buy attempt.

2. defender-0/1/2 + rando-0/1/2 + seed-local-0/1/2 (9 bots)

BadFunctionCallOutput: Could not transact with/call contract function, is contract deployed correctly and chain synced?

Failing at contract-call level. Reading state that returns empty, or hitting an address with no code deployed.

3. pvp-0/1/2 (3 bots)

Mock CL8Y minting succeeds (mint(CL8Y mock) tx=... status=1), then pvp: cycle failed (Could not transact with/call contract function...); retrying after delay on the subsequent sale buy.

Why suspicious for UUPS regression

  • forge test with FOUNDRY_PROFILE=ci = 178/178 passing (verified this morning per #54 closure)
  • But live DeployDev.s.sol via UUPSDeployLib now deploys everything behind ERC1967Proxy + initialize call
  • Contract tests set up their own state inline; bots depend on the deployed-and-initialized state from the DeployDev script
  • Proxy initialization may be missing a denominator value (e.g. initial reserve, initial totalShare, etc.) that in the non-upgradeable version was set in a constructor

Logs

  • /tmp/yieldomega_swarm_shark-0.log ends with ContractPanicError: Panic error 0x12: Division by zero.
  • /tmp/yieldomega_swarm_rando-0.log ends with BadFunctionCallOutput
  • /tmp/yieldomega_swarm_pvp-0.log shows partial success on CL8Y mint then cycle failure

Proposed diagnosis path

  • Diff contracts/src/TimeCurve.sol constructor-era state vs initialize() to find any state variable that was initialized in constructor (including default zeros) and now needs explicit initialize assignment
  • Check if any of PodiumPool, DoubLPIncentives, EcosystemTreasury, FeeRouter, ReferralRegistry, LinearCharmPrice have non-trivial defaults that got lost in the impl→initialize migration
  • Inspect Anvil state after DeployDev.s.sol runs — compare totalShare, totalBurned, any reserve balances that might be a denominator in TimeCurve buy math

Impact

  • #39 regression walkthrough blocked
  • #38 DOUB launch flow walkthrough blocked (same stack dependency)
  • Any live-stack frontend verification blocked — backend bots can't seed state
  • 178/178 forge test passing is insufficient signal for the deployment-level regression

Workaround for immediate QA

None yet. Attempting bash scripts/e2e-anvil.sh instead of stack script may or may not work (different deploy path); haven't tested.

cc @PlasticDigits — this looks like it came in with #54 merge 0cc6342. High priority for YO launch given timeline.

Found during #39 regression walkthrough. All 18 bots fail against a fresh SKIP_ANVIL_RICH_STATE=1 stack bring-up; 178/178 forge tests still pass. Deployment or proxy-init regression suspected. ## Repro 1. Fresh QA server: `git pull origin main` + `git submodule update --init --recursive` (HEAD at `dec166e`) 2. `cd /srv/qa/repos/yieldomega && SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh` 3. Wait for 'Stack is up.' — Anvil on 8545, indexer on 3101 4. `cd bots/timecurve && source .env.local && timecurve-bot --send --allow-anvil-funding swarm` 5. 18 bot processes launch. PIDs in `/tmp/yieldomega_bot_swarm.pids`. ## Observed Three distinct failure modes across 18 bots: ### 1. shark-0/1/2 + fun-0/1/2 (6 bots) `ContractPanicError: ('Panic error 0x12: Division by zero.', '0x4e487b71...0x12')` Panic 0x12 is Solidity's division-by-zero panic. Strategy bots hit it at their first buy attempt. ### 2. defender-0/1/2 + rando-0/1/2 + seed-local-0/1/2 (9 bots) `BadFunctionCallOutput: Could not transact with/call contract function, is contract deployed correctly and chain synced?` Failing at contract-call level. Reading state that returns empty, or hitting an address with no code deployed. ### 3. pvp-0/1/2 (3 bots) Mock CL8Y minting succeeds (`mint(CL8Y mock) tx=... status=1`), then `pvp: cycle failed (Could not transact with/call contract function...); retrying after delay` on the subsequent sale buy. ## Why suspicious for UUPS regression - `forge test` with FOUNDRY_PROFILE=ci = 178/178 passing (verified this morning per #54 closure) - But live `DeployDev.s.sol` via `UUPSDeployLib` now deploys everything behind ERC1967Proxy + initialize call - Contract tests set up their own state inline; bots depend on the deployed-and-initialized state from the DeployDev script - Proxy initialization may be missing a denominator value (e.g. initial reserve, initial totalShare, etc.) that in the non-upgradeable version was set in a constructor ## Logs - `/tmp/yieldomega_swarm_shark-0.log` ends with `ContractPanicError: Panic error 0x12: Division by zero.` - `/tmp/yieldomega_swarm_rando-0.log` ends with `BadFunctionCallOutput` - `/tmp/yieldomega_swarm_pvp-0.log` shows partial success on CL8Y mint then cycle failure ## Proposed diagnosis path - Diff `contracts/src/TimeCurve.sol` constructor-era state vs initialize() to find any state variable that was initialized in constructor (including default zeros) and now needs explicit initialize assignment - Check if any of PodiumPool, DoubLPIncentives, EcosystemTreasury, FeeRouter, ReferralRegistry, LinearCharmPrice have non-trivial defaults that got lost in the impl→initialize migration - Inspect Anvil state after DeployDev.s.sol runs — compare `totalShare`, `totalBurned`, any reserve balances that might be a denominator in TimeCurve buy math ## Impact - #39 regression walkthrough blocked - #38 DOUB launch flow walkthrough blocked (same stack dependency) - Any live-stack frontend verification blocked — backend bots can't seed state - 178/178 forge test passing is insufficient signal for the deployment-level regression ## Workaround for immediate QA None yet. Attempting `bash scripts/e2e-anvil.sh` instead of stack script may or may not work (different deploy path); haven't tested. cc @PlasticDigits — this looks like it came in with #54 merge `0cc6342`. High priority for YO launch given timeline.
PlasticDigits commented 2026-04-24 12:42:29 +00:00 (Migrated from gitlab.com)

mentioned in commit 482a25aafa

mentioned in commit 482a25aafa232dba2d264e1bdcb76096bd126e3d
PlasticDigits commented 2026-04-24 12:43:12 +00:00 (Migrated from gitlab.com)

Fix pushed to main (482a25a) — @brouie please verify

Root cause

After UUPS (#54), Foundry run-latest.json labels the implementation CREATE as contractName TimeCurve / RabbitTreasury. The canonical addresses are the ERC1967Proxy rows (constructor arguments[0] = implementation). anvil_rich_state.sh was defaulting env from the naive jq filter and picked implementations → uninitialized storage → panic 0x12 on charm bounds / odd eth_call failures. Same pitfall for any script or agent copying addresses from broadcast JSON by contractName alone.

What changed

  • New scripts/lib/broadcast_proxy_addresses.sh — map impl → ERC1967 proxy for TimeCurve / RabbitTreasury; direct CREATE for LeprechaunNFT.
  • contracts/script/anvil_rich_state.sh — uses that helper when loading from broadcast.
  • scripts/lib/anvil_deploy_dev.sh — anchor console grep to line start (same as start-local-anvil-stack.sh) so verbose forge output cannot pick a wrong TimeCurve: line.
  • Docs: docs/testing/invariants-and-business-logic.md, docs/testing/anvil-rich-state.md, root skills/README.md, bots/timecurve/SKILL.md, .cursor/skills/yieldomega-guardrails/SKILL.md — invariant + crosslinks (issue left open for your sign-off).

Checklist for you

  1. git pull origin main (commit 482a25a or newer).
  2. Fresh Anvil: SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh (or your QA equivalent) — confirm Stack is up and swarm completes without ContractPanicError 0x12 / mass BadFunctionCallOutput.
  3. Rich state path: run bash contracts/script/anvil_rich_state.sh with addresses unset (so it loads from run-latest.json) — confirm Part1/Part2 still succeed.
  4. Optional: cast call <TIMECURVE from .env> "initialMinBuy()(uint256)" — must be non-zero on the proxy; the implementation address from old jq would read 0 if called directly.
  5. If anything still fails, capture grep TimeCurve deploy.log / YIELDOMEGA_TIMECURVE_ADDRESS and whether it matches an ERC1967Proxy row in run-latest.json.

Leaving the issue open until you confirm.

## Fix pushed to `main` (482a25a) — @brouie please verify ### Root cause After UUPS (#54), Foundry `run-latest.json` labels the **implementation** `CREATE` as `contractName` `TimeCurve` / `RabbitTreasury`. The **canonical** addresses are the **ERC1967Proxy** rows (constructor `arguments[0]` = implementation). `anvil_rich_state.sh` was defaulting env from the naive `jq` filter and picked **implementations** → uninitialized storage → **panic 0x12** on charm bounds / odd `eth_call` failures. Same pitfall for any script or agent copying addresses from broadcast JSON by `contractName` alone. ### What changed - New `scripts/lib/broadcast_proxy_addresses.sh` — map impl → ERC1967 proxy for TimeCurve / RabbitTreasury; direct CREATE for LeprechaunNFT. - `contracts/script/anvil_rich_state.sh` — uses that helper when loading from broadcast. - `scripts/lib/anvil_deploy_dev.sh` — **anchor** console `grep` to line start (same as `start-local-anvil-stack.sh`) so verbose forge output cannot pick a wrong `TimeCurve:` line. - Docs: `docs/testing/invariants-and-business-logic.md`, `docs/testing/anvil-rich-state.md`, root `skills/README.md`, `bots/timecurve/SKILL.md`, `.cursor/skills/yieldomega-guardrails/SKILL.md` — invariant + crosslinks (**issue left open** for your sign-off). ### Checklist for you 1. `git pull origin main` (commit `482a25a` or newer). 2. Fresh Anvil: `SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh` (or your QA equivalent) — confirm **Stack is up** and swarm completes without `ContractPanicError 0x12` / mass `BadFunctionCallOutput`. 3. **Rich state path:** run `bash contracts/script/anvil_rich_state.sh` with addresses **unset** (so it loads from `run-latest.json`) — confirm Part1/Part2 still succeed. 4. Optional: `cast call <TIMECURVE from .env> "initialMinBuy()(uint256)"` — must be **non-zero** on the proxy; the implementation address from old `jq` would read **0** if called directly. 5. If anything still fails, capture `grep TimeCurve deploy.log` / `YIELDOMEGA_TIMECURVE_ADDRESS` and whether it matches an `ERC1967Proxy` row in `run-latest.json`. Leaving the issue **open** until you confirm.
Brouie commented 2026-04-26 05:40:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #55

mentioned in issue #55
Brouie commented 2026-04-27 02:46:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #72

mentioned in issue #72
Brouie commented 2026-04-27 03:47:39 +00:00 (Migrated from gitlab.com)

mentioned in issue #73

mentioned in issue #73
Brouie commented 2026-04-27 03:56:07 +00:00 (Migrated from gitlab.com)

@PlasticDigits verified on 1bdbda8 (full stack: bd564b0 anvil flag + this commit's forge --code-size-limit fix).

Verification

Commands:

pkill -9 -f anvil; sleep 2
SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh
cast call <TimeCurve from registry> 'initialMinBuy()(uint256)' --rpc-url http://127.0.0.1:8545
source bots/timecurve/.env.local
YIELDOMEGA_ANVIL_EXTRA_FUNDED_ADDRESSES=0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 timecurve-bot --send --allow-anvil-funding swarm

Results

  • Stack: Stack is up — RPC, indexer, registry, frontend env all written
  • Step #4 (proxy not impl): cast call initialMinBuy() returns 1000000000000000000 (1e18 non-zero) on the registry's TimeCurve address. Proxy resolution working — broadcast_proxy_addresses.sh maps impl CREATE rows to ERC1967Proxy correctly per 482a25a.
  • Step #2 (swarm): 18 bots launched; 198 successful buys, 0 failed txs within 30s. Friday's regression (mass ContractPanicError 0x12 + BadFunctionCallOutput killing all bots immediately) is fully gone — bots produce sustained traffic.
  • Defender bots correctly waiting for timer-end window (remaining 86541s >= 900s).

Forge environment note

forge --version on the QA server: 1.5.1-stable (b0a9dd9, 2025-12-22). Forge applies EIP-170 in its dry-run simulator independently of Anvil — your bd564b0 only set Anvil's code-size-limit, leaving forge's default to reject TimeCurve at 28454 bytes. The 1bdbda8 follow-up adding --code-size-limit 524288 to the forge script line resolves it. Worth noting in case future agents on differently-configured forge installs need the same flag.

Follow-up filed

#73 — 6 of 18 bots (3x shark + 3x fun) hit a single Panic 0x12 (div-by-zero) at startup on currentCharmBoundsWad() (bots/timecurve/src/timecurve_bot/strategies/common.py L25). Bots recover and proceed normally — this is NOT the original #61 regression (that one killed bots; this one is a recovered-edge in the bound math probing fresh-sale state before any seed buy lands). Tracked separately.

#61 fully resolved at the level you scoped it. Closing.

@PlasticDigits verified on 1bdbda8 (full stack: bd564b0 anvil flag + this commit's forge --code-size-limit fix). ## Verification Commands: ``` pkill -9 -f anvil; sleep 2 SKIP_ANVIL_RICH_STATE=1 bash scripts/start-local-anvil-stack.sh cast call <TimeCurve from registry> 'initialMinBuy()(uint256)' --rpc-url http://127.0.0.1:8545 source bots/timecurve/.env.local YIELDOMEGA_ANVIL_EXTRA_FUNDED_ADDRESSES=0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650 timecurve-bot --send --allow-anvil-funding swarm ``` ## Results - Stack: `Stack is up` — RPC, indexer, registry, frontend env all written - Step #4 (proxy not impl): `cast call initialMinBuy()` returns `1000000000000000000` (1e18 non-zero) on the registry's TimeCurve address. Proxy resolution working — broadcast_proxy_addresses.sh maps impl CREATE rows to ERC1967Proxy correctly per 482a25a. - Step #2 (swarm): 18 bots launched; **198 successful buys, 0 failed txs** within 30s. Friday's regression (mass ContractPanicError 0x12 + BadFunctionCallOutput killing all bots immediately) is fully gone — bots produce sustained traffic. - Defender bots correctly waiting for timer-end window (`remaining 86541s >= 900s`). ## Forge environment note forge --version on the QA server: 1.5.1-stable (b0a9dd9, 2025-12-22). Forge applies EIP-170 in its dry-run simulator independently of Anvil — your bd564b0 only set Anvil's code-size-limit, leaving forge's default to reject TimeCurve at 28454 bytes. The 1bdbda8 follow-up adding --code-size-limit 524288 to the forge script line resolves it. Worth noting in case future agents on differently-configured forge installs need the same flag. ## Follow-up filed #73 — 6 of 18 bots (3x shark + 3x fun) hit a single Panic 0x12 (div-by-zero) at startup on `currentCharmBoundsWad()` (bots/timecurve/src/timecurve_bot/strategies/common.py L25). Bots recover and proceed normally — this is NOT the original #61 regression (that one killed bots; this one is a recovered-edge in the bound math probing fresh-sale state before any seed buy lands). Tracked separately. #61 fully resolved at the level you scoped it. Closing.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-27 03:56:09 +00:00
PlasticDigits commented 2026-04-30 05:25:11 +00:00 (Migrated from gitlab.com)

mentioned in issue #100

mentioned in issue #100
PlasticDigits commented 2026-05-01 06:11:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #104

mentioned in issue #104
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
PlasticDigits commented 2026-05-05 13:57:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #162

mentioned in issue #162
PlasticDigits commented 2026-05-13 16:07:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #207

mentioned in issue #207
PlasticDigits commented 2026-05-20 04:30:23 +00:00 (Migrated from gitlab.com)

mentioned in issue #224

mentioned in issue #224
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#61
No description provided.