Skip to content

fix(devnet): re-deploy contracts when artifacts outpace running chain#383

Open
zsculac wants to merge 2 commits intomainfrom
fix/devnet-redeploy-on-stop
Open

fix(devnet): re-deploy contracts when artifacts outpace running chain#383
zsculac wants to merge 2 commits intomainfrom
fix/devnet-redeploy-on-stop

Conversation

@zsculac
Copy link
Copy Markdown
Contributor

@zsculac zsculac commented May 4, 2026

Summary

Two fixes to scripts/devnet.sh so a long-running Hardhat node doesn't silently serve stale bytecode after a contract recompile.

1. Stale-bytecode guard in start_hardhat

When Hardhat is left running across edits to .sol sources, on-chain bytecode lags the recompiled artifacts on disk. Next devnet start short-circuits on the alive PID, then deploy_contracts skips on the still-present .devnet/hardhat/deployed marker. Daemons connect to addresses whose code predates the latest source; view methods added since that deploy revert with "function selector was not recognized." Ethers surfaces this as require(false) with no return data, masking the real cause.

On a 6-node devnet this manifested as FATAL: only 0/4 core node(s) staked because the staking loop's CSS.getNodeStakeV10 probe failed for every node.

The fix compares artifact JSON mtimes to the marker. If any artifact is newer, kill Hardhat so the existing fresh-start path (clears marker + localhost_contracts.json + hardhat_contracts.json) fires and deploy_contracts redeploys onto a fresh chain. No-op when artifacts are unchanged — keeps the fast path intact for the common edit-nothing-restart-devnet flow.

2. Escape backticks in node -e JS comments

Five JS comments inside the staking heredoc (lines 742, 868, 881, 898, 899) had unescaped backticks (`continue`, `nonce++`, `EVMChainAdapter.ensureProfile()`, `getNodeStakeV10(idId)`, `balanceOf(opSigner.address)`). Bash command-substituted them inside the surrounding node -e "..." double-quoted string, so every devnet start printed:

scripts/devnet.sh: line 694: continue: only meaningful in a `for', `while', or `until' loop
scripts/devnet.sh: line 694: nonce++: command not found
scripts/devnet.sh: line 694: syntax error near unexpected token `idId'

Text was inside // comments so the staking script still ran, but the diagnostic noise drowned out real failures (e.g. the stale-bytecode revert above). Lines 730-733 and 768 already use ``` correctly; these five were oversights.

Test plan

  • bash -n scripts/devnet.sh syntax check
  • Fresh devnet stop && devnet start 6 no longer prints bash backtick errors
  • Confirmed contract redeploy on artifacts-newer scenario by leaving long-running hardhat alive across recompile, then devnet start — stale-bytecode guard fires, contracts redeployed fresh
  • Reviewer: walk through fast path (artifacts unchanged) — start_hardhat short-circuits with no marker mtime check overhead

Out of scope

A pre-existing nonce race between the daemon's ensureProfile() auto-stake and the bootstrap script's stake loop (both target the same op wallet within seconds of each other) was masked by the bash syntax errors. With these fixes the race surfaces as Stake failed for node N: nonce has already been used. Filing separately — not introduced by this PR.

🤖 Generated with Claude Code

Zvonimir and others added 2 commits May 4, 2026 16:36
Five backticks inside JS comments at lines 742, 868, 881, 898, 899
were not escaped, so bash interpreted them as command substitution
inside the surrounding `node -e "..."` double-quoted string. On every
`devnet start` this produced noisy errors like:

  scripts/devnet.sh: line 694: continue: only meaningful in a `for'...
  scripts/devnet.sh: line 694: nonce++: command not found
  scripts/devnet.sh: line 694: syntax error near unexpected token `idId'

The text was inside JS `//` comments so the staking script still ran,
but the diagnostics drowned out real failures (e.g. the stale-bytecode
revert this commit's sibling fixes). Lines 730-733 and 768 already use
`\`` correctly; these five were oversights.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When hardhat is left running across edits to .sol sources, the on-chain
bytecode lags the recompiled artifacts on disk. The next `devnet start`
short-circuits in `start_hardhat` (alive PID), then `deploy_contracts`
skips on the still-present `.devnet/hardhat/deployed` marker, so daemons
connect to addresses whose code predates the latest source. View methods
added since that deploy revert with "function selector not recognized";
ethers surfaces this as `require(false)` (no return data, no fallback).
On a 6-node devnet this manifests as `0/4 core node(s) staked` because
the staking loop's `CSS.getNodeStakeV10` probe fails for every node.

Detect the mismatch by comparing artifact mtimes to the marker. If any
contract artifact JSON is newer than the marker, kill the running
hardhat so the existing fresh-start path (which clears the marker plus
`localhost_contracts.json` + `hardhat_contracts.json`) runs and
`deploy_contracts` re-deploys onto a fresh chain. No-op when artifacts
are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread scripts/devnet.sh
kill -0 "$stale_pid" 2>/dev/null || break
sleep 1
done
rm -f "$pidfile"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Issue: This restart path removes hardhat.pid and immediately starts a replacement even if the old Hardhat process is still alive after the 5s wait. On a slow shutdown, the new hardhat node can fail to bind :$HARDHAT_PORT, turning the stale-artifact recovery into a flaky devnet start. Re-check kill -0 after the loop and either keep waiting/escalate the kill or return non-zero before deleting the pidfile and launching a new node.

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.

1 participant