diff --git a/scripts/devnet.sh b/scripts/devnet.sh index c89acfed7..2d1c24654 100755 --- a/scripts/devnet.sh +++ b/scripts/devnet.sh @@ -101,10 +101,36 @@ ensure_built() { start_hardhat() { local pidfile="$DEVNET_DIR/hardhat.pid" + local marker="$DEVNET_DIR/hardhat/deployed" + local artifacts_dir="$REPO_ROOT/packages/evm-module/artifacts/contracts" if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then - log "Hardhat node already running (PID $(cat "$pidfile"))" - return 0 + # Stale-bytecode guard. When contracts are recompiled while hardhat + # keeps running, the on-chain bytecode lags the artifacts on disk. + # The next `devnet start` short-circuits hardhat reuse here, then + # `deploy_contracts` skips on the still-present marker, so daemons + # connect to addresses whose code is older than the source. View + # methods added since the live deploy revert with "function selector + # not recognized" (e.g. CSS.getNodeStakeV10 in the staking loop). + # Detect the mismatch by comparing artifact mtimes to the marker; + # if any artifact is newer, kill hardhat so the fresh-start path + # below clears the marker + deployments and `deploy_contracts` + # re-deploys onto a fresh chain. + if [ -f "$marker" ] && [ -d "$artifacts_dir" ] \ + && [ -n "$(find "$artifacts_dir" -name '*.json' -newer "$marker" -print -quit 2>/dev/null)" ]; then + local stale_pid + stale_pid=$(cat "$pidfile") + log "Hardhat (PID $stale_pid) holds outdated contracts (artifacts newer than deploy marker) — restarting for fresh deploy" + kill "$stale_pid" 2>/dev/null || true + for _ in 1 2 3 4 5; do + kill -0 "$stale_pid" 2>/dev/null || break + sleep 1 + done + rm -f "$pidfile" + else + log "Hardhat node already running (PID $(cat "$pidfile"))" + return 0 + fi fi log "Starting Hardhat node on port $HARDHAT_PORT..." @@ -739,7 +765,7 @@ cmd_start() { const opSigners = new Array(n).fill(null); const nodeRoles = new Array(n).fill('edge'); // Codex round 4 on PR #368: read config.json FIRST and INDEPENDENTLY - // of wallets.json. The previous loop did wallets first and `continue`d + // of wallets.json. The previous loop did wallets first and \`continue\`d // on parse failure BEFORE reading config, so an intended core whose // wallets.json was malformed silently kept the 'edge' default, // dropped out of coreIdxs, and the lostCores guard never fired. @@ -865,7 +891,7 @@ cmd_start() { // this code path exists to prevent. // // Codex round 2 on PR #368: re-read 'pending' before EVERY tx - // (not once per node loop). The previous code did `nonce++` at + // (not once per node loop). The previous code did \`nonce++\` at // call site so a failed approve advanced the local counter even // though the chain nonce did not, leaving createConviction + // updateAsk to send with an inflated nonce that would either @@ -878,7 +904,7 @@ cmd_start() { return tx.wait(); }; // Codex round 4 on PR #368: skip createConviction if the daemon - // already opened a position. PR 366 wired `EVMChainAdapter.ensureProfile()` + // already opened a position. PR 366 wired \`EVMChainAdapter.ensureProfile()\` // to open a default 50k V10 conviction during agent startup, so by // the time this script reaches the staking loop the daemon has // (usually) already staked. Running createConviction again would @@ -895,8 +921,8 @@ cmd_start() { // updateAsk is INDEPENDENT of stake state, so we still attempt // it below regardless of the probe outcome. // - // Codex round 7 on PR #368: probe `getNodeStakeV10(idId)` instead - // of NFT `balanceOf(opSigner.address)`. The op wallet could hold + // Codex round 7 on PR #368: probe \`getNodeStakeV10(idId)\` instead + // of NFT \`balanceOf(opSigner.address)\`. The op wallet could hold // positions for OTHER identities (not in our devnet today, but // a wallet-scoped probe is the wrong semantic check); we want // to know whether THIS identity is staked.