Skip to content

fix(hardfork): regenerate Zeta StakePool bytecode for PR #73#338

Merged
nekomoto911 merged 2 commits intogravity-testnet-v1.5from
fix/zeta-stakepool-bytecode
Apr 27, 2026
Merged

fix(hardfork): regenerate Zeta StakePool bytecode for PR #73#338
nekomoto911 merged 2 commits intogravity-testnet-v1.5from
fix/zeta-stakepool-bytecode

Conversation

@ByteYue
Copy link
Copy Markdown
Collaborator

@ByteYue ByteYue commented Apr 27, 2026

Summary

What would have broken on testnet without this fix

With the Gamma template applied, on every existing pool:

  • proposeStaker / acceptStaker / cancelStakerChange / proposeOperator / proposeVoter / *ChangeDelay() / MIN_ROLE_CHANGE_DELAY() would not exist on chain.
  • The deprecated immediate setters (setStaker / setOperator / setVoter) would still be callable, leaving the timelock inert.
  • scripts/verify_hardfork/hardforks/zeta.sh would fail at every PR feat(pipe): modify PipeExecLayerApi to adapt to gravity sdk #73 assertion in the StakePool block.

How the bytecode was regenerated

  • Source: out/StakePool.sol/StakePool.json (deployedBytecode.object, 9223 bytes) compiled from gravity-chain-core-contracts gravity-testnet-v1.5 (commit c112da2, fix(staking): 2-step timelock for StakePool role changes).
  • immutableReferences has a single entry (67435 = FACTORY) at offsets 3061 and 5098, length 32 each. Both are zero placeholders in the fresh artifact and get patched with the Staking system address (0x00000000000000000000000000000001625F2000) left-padded to a 32-byte word.
  • All four STAKEPOOL_ADDRESSES share the same FACTORY (singleton Staking), so one template applies to every pool — same pattern as Gamma and Beta.

Empirical verification

Selector check against the new bin (PUSH4 pattern 63XXXXXXXX):

Selector Gamma bin Zeta-new bin
proposeStaker(address) 06bec93a absent present
acceptStaker() 2315c946 absent present
MIN_ROLE_CHANGE_DELAY() d49be641 absent present
setStaker(address) a29a43bb present absent

cargo check -p reth-evm-ethereum passes.

Reflection — why the original mistake happened

  1. Conflated immutables with bytecode template. "No new immutables" is true (FACTORY is still the only immutable) but irrelevant: any added/removed external function changes the dispatcher and code section regardless of immutables. The argument needed to be "no selectors added/removed and no opcode changes" — which PR feat(pipe): modify PipeExecLayerApi to adapt to gravity sdk #73 obviously violates.
  2. The contract repo never regenerated bytecodes/zeta/StakePool.bin. The Zeta bundle was assembled from whatever bins the contracts repo produced. StakePool was missing. Instead of pushing back, the reth side rationalized using the Gamma bin. The right move would have been to regenerate the artifact (this PR does that).
  3. No automated guard. There is no CI check that, for each hardfork, asserts the bundled bytecodes match the deployedBytecode of the contract source at the corresponding contracts-repo tag. Adding one (e.g. a build script that compiles gravity-chain-core-contracts at the pinned commit and diffs against bytecodes/<hardfork>/*.bin) would have surfaced this immediately.
  4. Smoke tests are post-deployment. verify_hardfork/zeta.sh would have caught this on a real upgrade rehearsal, but it runs against a live RPC after activation. There is no equivalent assertion at the reth crate level (e.g., a unit test that searches for the PR feat(pipe): modify PipeExecLayerApi to adapt to gravity sdk #73 selectors inside STAKEPOOL_BYTECODE). A trivial test like assert!(STAKEPOOL_BYTECODE.windows(5).any(|w| w == [0x63, 0x06, 0xbe, 0xc9, 0x3a])) (proposeStaker push4) would have made this a compile-time / cargo test failure. Worth a follow-up.
  5. Comment-as-justification anti-pattern. The original comment reads as "we know what we are doing" but the reasoning chain doesn't hold. When a comment exists to defend a non-obvious choice, that is a strong signal to add a corresponding test instead of trusting the comment.

Test plan

  • cargo check -p reth-evm-ethereum (done locally)
  • cargo test -p reth-evm-ethereum against this branch
  • On testnet rehearsal: run scripts/verify_hardfork/verify.sh zeta <rpc> after activation and confirm the StakePool block in zeta.sh passes (MIN_ROLE_CHANGE_DELAY == 86400, proposeStaker / acceptStaker / cancelStakerChange / proposeOperator / proposeVoter all present, stakerChangeDelay / operatorChangeDelay / voterChangeDelay all readable, FACTORY view returns the Staking address).
  • Spot-check that for each pool, cast code <pool> after activation has codehash equal to keccak256(bytecodes/zeta/StakePool.bin).

Follow-up suggestions (not in this PR)

  • Add a cargo test that asserts the presence/absence of a few selectors per STAKEPOOL_BYTECODE per hardfork (cheap regression guard).
  • Add a build-time check (or xtask) that recompiles the contracts repo at the pinned commit and diffs against the bundled bins.

The Zeta hardfork was including bytecodes/gamma/stakepool.bin for every
existing pool with the rationale that PR #73 added "no new immutables"
so the Gamma template was still correct. That conflated two orthogonal
concerns: immutable layout (which controls per-instance patching) is
unrelated to whether the runtime bytecode template itself changed.

PR #73 (#73 in gravity-chain-core-contracts) replaces the role-change
surface on StakePool:

  removed:  setStaker / setOperator / setVoter
  added:    proposeStaker / acceptStaker / cancelStakerChange
            proposeOperator / acceptOperator / cancelOperatorChange
            proposeVoter / acceptVoter / cancelVoterChange
            setStakerChangeDelay / setOperatorChangeDelay / setVoterChangeDelay
            MIN_ROLE_CHANGE_DELAY (constant)

Both the dispatcher selector table and the code section change. With the
Gamma template, the upgrade is effectively a no-op: each pool's bytecode
is "replaced" with what it already runs, the new ABI is missing on chain,
the deprecated immediate setters remain callable, and the timelock is
inert. scripts/verify_hardfork/hardforks/zeta.sh would fail at every
PR #73 assertion (proposeStaker / acceptStaker / MIN_ROLE_CHANGE_DELAY /
*ChangeDelay views).

Fix:
- Regenerate bytecodes/zeta/StakePool.bin from the v1.5 contract artifact
  (out/StakePool.sol/StakePool.json, deployedBytecode.object, 9223 bytes)
  with the FACTORY immutable patched at the two immutableReferences
  offsets (3061 and 5098) to the Staking system address (0x...01625F2000).
  All four pools share the same FACTORY (singleton Staking), so a single
  template applies to every entry in STAKEPOOL_ADDRESSES — same pattern
  as Gamma and Beta.
- Point STAKEPOOL_BYTECODE in zeta.rs at the new bin and rewrite the
  misleading comment to spell out the dispatcher / code-section change.

Verified empirically against the new bin: dispatcher contains the
proposeStaker / acceptStaker / MIN_ROLE_CHANGE_DELAY selectors and no
longer contains setStaker.
Pre-existing on `gravity-testnet-v1.5` but surfaced by lint CI on this
PR (the previous Zeta merge slipped past with broken fmt/clippy):

- doc_markdown: backtick `StakePool` in the ZETA_EXTRA_UPGRADES doc
  comment.
- rustfmt: collapse multi-line `B256::new(hex_to_bytes32("…"))` calls in
  ALLOWED_POOLS_SLOTS into single lines, and rewrap module-level doc
  comments (`use_small_heuristics = "Max"`, `comment_width = 100`).

No semantic changes. `cargo fmt --all --check` and
`cargo clippy --lib --tests --benches -p reth-evm-ethereum --all-features
--locked` now pass under nightly-2026-02-01 (the CI toolchain).
@nekomoto911 nekomoto911 merged commit fd3b0d6 into gravity-testnet-v1.5 Apr 27, 2026
26 checks passed
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