Open
Conversation
Adds an optional sPOL/LST contract deployment phase on top of the existing PoS devnet. Opt in with `dev.deploy_lst_contracts: true` (see `params-spol-test.yml` for the canonical example). When enabled, after the base PoS + validator stake are up, a new starlark module runs a `spol-contract-deployer` container which: 1. Deploys mock PolygonMigration + RootChainManager on L1 (the real contracts don't exist on a fresh devnet). 2. Writes a kurtosis-devnet scenario into script/input.json wiring the runtime-discovered PoS addresses + mock addresses. 3. Invokes the existing `Deploy.s.sol:run(string)` entrypoint to deploy the full sPOL suite to L1 and L2. 4. Runs a kurtosis-specific SetupInitialValidators variant that registers the single devnet validator (id 1). The resulting addresses are exposed via a `lst-contract-addresses` kurtosis artifact which consumers (e.g. lst-api#93) read by name. ## Deployer image Follows the existing `pos-contract-deployer` / `pos-el-genesis-builder` / `pos-validator-config-generator` pattern: - `docker/spol-contract-deployer.Dockerfile` — clones 0xPolygon/spol-contracts at a pinned ref, overlays kurtosis mocks from `docker/spol-mocks/`, pre-installs soldeer deps, warms the forge cache. - `.github/workflows/publish-images.yaml` — gains a `publish-spol-contract-deployer` job mirroring the three existing ones, pinned to spol-contracts main @ 3c4bdf6c. No changes required in spol-contracts itself — the runtime scenario-JSON write reuses upstream's existing `loadConfigFromJson` path. ## Config / tests / docs - `DEV_ARGS` gains `deploy_lst_contracts` (bool, default false) and `lst_deployer_params` (reward_fee / fee_receiver / max_divergence). - `sanity_check.star` validates both the top-level flag and the `lst_deployer_params` sub-keys against a LST_DEPLOYER_PARAMS allowlist. - Parser and sanity_check tests cover defaults merge, partial override, the `deploy_lst_contracts requires should_deploy_l1` invariant, and unknown-key rejection. - `docs/configuration/reference.md` and the README document the new options under existing sections. ## Static files layout `static_files/contracts/` split into `pos/` (existing L1/L2 scripts) and `lst/deploy-lst-contracts.sh` so each deployer only uploads the files it needs. Verified end-to-end against a live kurtosis devnet — L1 Anvil + L2 Bor + Heimdall, sPOL L1+L2 deploy produces consistent CREATE2 addresses (accessManagerL1 == accessManagerL2, polBridger symmetric).
The previous implementation did `git clone --depth 1 --branch main`
followed by `git checkout ${SHA}`. The shallow clone only fetches the
tip of main, so the checkout fails with "pathspec did not match any
file(s) known to git" — but its non-zero exit was swallowed by the
`|| true` on the trailing `find`, so every image was silently built
from whatever main currently pointed at, not the pinned SHA.
This was benign up to now because the commits on main since the pin
(3c4bdf6c -> a2a24ea) are docs/license-only, so the compiled Solidity
matches. A Solidity-affecting commit landing on main between pin and
publish would have shipped an image whose tag claimed one SHA but
whose content matched another, with no diagnostic.
Fix by dropping --depth 1 and --branch so the checkout works for any
ref form (short SHA, full SHA, tag, branch). Removes the now-unused
SPOL_CONTRACTS_BRANCH arg from the publish workflow.
Forge's `new X{salt: y}(args)` in solidity compiles to a CALL into the
canonical deterministic deployer at
0x4e59b44847b379578588920cA78FbF26c0B4956C. Bor's genesis here had
`"alloc": {}` — so that address has no code on chain, forge's
simulation still succeeds against its in-memory model (including
`_verifyDeploymentL2` view checks), the deployment artifact gets
written with the predicted CREATE2 addresses, the deployer container
exits zero, but the broadcast-side CREATE2 calls silently no-op and
the predicted addresses have no code on L2.
The usual runtime recovery — fund the EIP-2470 signer and broadcast
its pre-signed legacy deploy tx — cannot work here because
`eip155Block: 0` in this genesis rejects legacy tx without chain-id
replay protection. Pre-alloc'ing the deployer's runtime bytecode at
genesis matches what Anvil does by default and is the only way to
guarantee forge CREATE2 emission materialises contracts on Bor.
Evidence: lst-api#93 e2e runs against `params-spol-test.yml` (anvil L1
+ Bor L2) deploy sPOL successfully as far as forge is concerned
— `All verifications passed for L2!` — but the subsequent
`sPOLChild.paused()` at the reported `0xF70710...` address returns
0x (no code). On the sibling kurtosis-pos PR #534, the same root
cause surfaces earlier and more loudly: jobs whose L1 backend is
`ethereum-package` (geth) fail outright with
`Error: script failed: missing CREATE2 deployer: 0x4e59b44847b379578588920cA78FbF26c0B4956C`,
because forge's pre-flight check runs against the first fork and
geth L1 — unlike anvil L1 — doesn't come with the deployer either.
The anvil-L1 CI job passes only because forge's check runs on L1
(anvil, which has the deployer) and never rechecks on L2 after the
second `vm.createSelectFork`.
The prior commit added the EIP-2470 CREATE2 deployer to the genesis
template at static_files/el/genesis/genesis.json. That was the wrong
spot: builder.sh runs after the template is rendered and, as its final
step, overwrites the template's `alloc` field with the alloc produced
by `generate-genesis.js` (genesis-contracts):
# Add the alloc field to the temporary EL genesis to create the final EL genesis.
jq --arg key 'alloc' '. + {($key): input | .[$key]}' \
"${EL_GENESIS_FILE}" "${EL_GENESIS_ALLOC_FILE}" > tmp.json
mv tmp.json "${EL_GENESIS_FILE}"
Consequence: the template's alloc is discarded, and the deployer never
appears in the final L2 genesis. Confirmed from the enclave dump on the
lst-api#93 e2e run: l2-el-genesis-tmp/genesis.json had the entry,
l2-el-genesis/genesis.json (the one Bor actually uses) did not.
Move the alloc into builder.sh as a jq step on `EL_GENESIS_ALLOC_FILE`,
mirroring the existing pattern for the EIP-2935 block-hash history
contract. That file is then preserved by the subsequent merge.
Revert the no-op alloc in the template so the source of the rule is
single-sited in builder.sh.
…me as network_params Mainnet-realistic defaults (128 blocks / 120s buffer) preserve today's cadence for everyone who doesn't override. Test profiles can compress checkpoint round-trips to ~15-20s by setting e.g. 8 / 10s — which is what params-spol-test.yml now does so the sPOL e2e suite can observe burn → checkpoint → exit inside a single test timeout without waiting 2–3 minutes per checkpoint. Pattern mirrors cl_checkpoint_poll_interval: a network_params knob with a centrally-defined default, consumed from the single template boundary in src/cl/genesis.star, validated via sanity_check.star. Motivated by the sPOL LST service's end-to-end migration-exit test, which needs real matic.js proofs round-tripping through kurtosis. The old 128-block cadence made that path infeasible inside a reasonable test timeout.
Mirrors the existing WithdrawManager `exitPeriod=1` override. Without it, a validator unstake scheduled by `StakeManager.unstake(validatorId)` does not become claimable until `currentEpoch + dynasty` — which at the mainnet default (`~886k` bor blocks, i.e. days) is well past any devnet test budget. Setting `dynasty=1` brings the unbond window down to one checkpoint, which at the fast-cadence overrides (`avg_checkpoint_length=8`, `checkpoint_buffer_time=10s`) is ~16-25s. Unblocks the downstream `sPOLController.withdrawPOL(user)` path that consumes those per-validator nonces — lst-api's happy-path claim test currently reverts with `NoNoncesReady(user)` for exactly this reason.
…Value The previous commit called `updateDynasty(uint256)` which does not exist on StakeManager. The actual function is `updateDynastyValue(uint256)` — `onlyGovernance`, sets BOTH `dynasty` (default 886 epochs) and `WITHDRAWAL_DELAY` (default 2^13 epochs) to the new value in one call. Both fields gate the claim delay, so a single `updateDynastyValue(1)` call achieves what the previous commit message described.
# Conflicts: # src/config/input_parser.star # src/config/sanity_check.star # static_files/contracts/deploy-l1-contracts.sh # static_files/contracts/deploy-l2-contracts.sh # static_files/contracts/l1/deploy-plasma-bridge.sh # static_files/contracts/l2/deploy-plasma-bridge.sh # static_files/contracts/pos/deploy-l1-contracts.sh # static_files/contracts/pos/deploy-l2-contracts.sh
… image
Folds the standalone spol-contract-deployer image into pos-contract-deployer
alongside pos-contracts and pos-portal. The bundled image now ships all three
contract sets under /opt/{pos-contracts,pos-portal,spol-contracts}; the LST
deploy step pulls it from setup_images.contract_deployer like every other
deployer, removing the dedicated spol_contract_deployer_image entry.
The Dockerfile gains a third clone+build block (full clone, soldeer install,
forge build) and a runtime COPY block for the ~21MB sPOL surface forge needs
at script time (foundry.toml, lockfiles, remappings, src/, script/,
dependencies/, out/, cache/). test/, broadcast/, audits/ are skipped.
publish-images.yaml drops the standalone spol-contract-deployer job and
extends pos-contract-deployer with SPOL_CONTRACTS_{BRANCH,COMMIT_SHA}
build-args + matching com.polygon.spol-contracts.{branch,commit} OCI labels.
POS_CONTRACT_DEPLOYER_VERSION bumped to 0.0.3 to reflect the new bundled
contents.
…n + RootChainManager Daryll's branch shipped MockPolygonMigration and MockRootChainManager because neither contract existed on a fresh kurtosis PoS devnet. After PR #538 (migrate-matic-to-pol + pos-bridge), both are deployed for real: - PolygonMigration → .root.tokens.PolygonMigration (from migrate-matic-to-pol-token.sh) - RootChainManagerProxy → .root.posBridge.RootChainManagerProxy (from deploy-pos-bridge.sh L1) sPOL passes both addresses to constructors only — no method calls during deploy — so the real contracts are interface-compatible with what the mocks provided. deploy-lst-contracts.sh now reads both from the accumulated contractAddresses.json artifact instead of running a separate DeployMocks step. The DeployMocks.s.sol / MockPolygonMigration.sol / MockRootChainManager.sol files are deleted. Renames docker/spol-mocks/ → docker/spol-kurtosis/ since "mocks" no longer fits — only SetupInitialValidatorsKurtosis.s.sol remains, and it's a kurtosis-specific validator-id-1 bootstrap rather than a mock. Container path updates to match (script/mock/ → script/kurtosis/).
SetupInitialValidatorsKurtosis hardcoded validator id 1 with 100% share, so the kurtosis devnet's sPOL deployment only worked when participants count was exactly 1. Multi-validator devnets (POLYGON_POS_PARTICIPANT.count >= 2) had N validators staked in StakeManager but only id 1 registered in sPOLController. The script now reads VALIDATOR_COUNT from env, loops 1..N adding each (pos-contracts assigns validator ids sequentially starting at 1, so this matches on-chain state), and distributes shares equally with any integer-division remainder going to validator 1 so shares always sum to 100. Capped at 100 since shares are uint8. main.star and lst_deployer.star pass len(validator_accounts) through as VALIDATOR_COUNT.
…e, move LST scripts to l1/ Two changes: 1. SetupInitialValidatorsKurtosis is now a Mustache template rendered at devnet-launch time via plan.render_templates(), with VALIDATOR_COUNT baked in as a compile-time constant. The bash wrapper copies the rendered .sol into /opt/spol-contracts/script/kurtosis/ at runtime so its relative `import "../../src/sPOLController.sol"` resolves. This drops the env-var dance, lets solc constant-fold the loop bounds (3422 → 3040 bytes of code), and keeps the pattern consistent with how every other deployer ships its scripts — through static_files/, not baked into the image. 2. Moves the LST deploy bash + template into static_files/contracts/l1/ since the script is L1-anchored: forge runs with --rpc-url L1_RPC_URL, the sPOLController canonical state lives on L1, validator setup is L1-only. (sPOL.s.Deploy uses vm.createSelectFork to also deploy on L2 within the same forge invocation, but that's an internal mechanic — the entrypoint is L1.) Drops the now-empty contracts/lst/ subdir and the docker/spol-kurtosis/ bake-time directory entirely.
…buySPOL rationale The LST deploy now runs unconditionally whenever should_deploy_matic_contracts is true (the default). The deploy_lst_contracts opt-in flag and the lst_deployer_params dict are both removed from the user-facing config — the sPOLController parameters (reward_fee, fee_receiver, max_divergence) are now hardcoded constants in lst_deployer.star with the same values that used to be the documented defaults. Devnet usage doesn't benefit from tuning these. Knock-on cleanups: - main.star gates LST on should_deploy_matic_contracts (the same gate that guarantees the accumulated contractAddresses.json contains PolygonMigration and RootChainManagerProxy with the expected shape). - Drops DEV_ARGS.deploy_lst_contracts, DEV_ARGS.lst_deployer_params, the lst_deployer_params merge logic in input_parser, the LST_DEPLOYER_PARAMS allowlist + sub-validator in sanity_check, and the three matching tests. - Drops the LST Deployer doc section from configuration/reference.md (only one row remains in the dev table; should_deploy_matic_contracts now also governs LST). - Removes the params-spol-test.yml example file from the repo root — out of place there (every other config sits under .github/configs/) and no longer meaningful with LST always-on. - README's numbered deploy list now mentions plasma bridge, MATIC→POL, pos bridge, and sPOL/LST. The LST line in "Optional features" is gone. Also fixes the rationale in setupInitialValidators.s.sol for skipping buySPOL. Daryll's original comment blamed delegationDepositPOL; the actual incompatibility is that sPOLController._buySharesFromValidator calls validatorContract.restakeAndStakePOL — a fused function not present in the pinned pos-contracts ValidatorShare (which exposes restakePOL and buyVoucherPOL separately). Adds a clarifying comment near the forge-script invocation in deploy-lst-contracts.sh to explain why L2_RPC_URL is read but not used directly (consumed inside Deploy.s.sol via vm.createSelectFork).
…op redundant gate) - jq -re for all artifact reads in deploy-lst-contracts.sh so set -e catches missing/null keys at the read site instead of letting them propagate as "null" strings into forge script and revert deep in the deploy. Carryover from a since-dropped review PR. Scoped to deploy-lst-contracts.sh only; applying the same to the other deploy scripts is a separate cleanup. - README links: plasma bridge → 0xPolygon/pos-contracts, MATIC→POL migration → 0xPolygon/pol-token (previously unlinked). - docs: drop the redundant "— gates the entire flow" trailer from the should_deploy_matic_contracts row in configuration/reference.md. - main.star: drop the should_deploy_matic_contracts guard and its long comment from the LST call site. The L2 plasma-bridge and pos-bridge deploys above it run unconditionally; LST now matches that pattern. jq -re makes a missing-key failure mode explicit if a user opts out of contract deploy and supplies a partial addresses file.
…int cadence by default Three small fixes carried over from review: - el_chain_id was incorrectly resurrected during the merge of feat/spol-lst-deployer into main: Daryll's branch had added it to DEV_ARGS.network_params and POLYGON_POS_PARAMS, but main had since removed it (it lives in constants.EL_CHAIN_ID and shouldn't be per-devnet overridable). lst_deployer was the only consumer reading network_params.el_chain_id; switched to constants.EL_CHAIN_ID directly, removed the field from defaults + sanity check. - Default checkpoint cadence is now the fast values from the deleted params-spol-test.yml (cl_avg_checkpoint_length=8, cl_checkpoint_buffer_time=10s) instead of the mainnet-conservative 128/120s. The slower values are useless for a kurtosis devnet — sPOL e2e tests need ~15-20s burn→checkpoint→exit round-trips, and no in-tree config relied on the slow defaults. Comment dropped. - Stripped the pseudo-justification comment from REWARD_FEE/FEE_RECEIVER/MAX_DIVERGENCE in lst_deployer.star — the unit comments on each line are sufficient.
…CTS_CONFIG_FILE_PATH
Daryll's branch had renamed static_files/contracts/{deploy-l*-contracts.sh}
to static_files/contracts/pos/* and updated this path to match. The merge
of main accepted main's flat layout (l1/ + l2/ subfolders for the
plasma-bridge / pos-bridge / matic-to-pol scripts) and dropped Daryll's
duplicate pos/ files, but the .star file still pointed at the deleted
pos/ path. Surfaced as "/static_files/contracts/pos doesn't exist" on
the first kurtosis run.
…m-package geth
sPOL's Deploy.s.sol uses Solidity's `new X{salt: y}` syntax for deterministic
CREATE2 addresses. solc compiles that to a CALL into the canonical EIP-2470
deployer at 0x4e59b44847b379578588920cA78FbF26c0B4956C. The L2 Bor genesis
already pre-allocates this contract (see static_files/el/genesis/builder.sh),
but the ethereum-package L1 geth genesis does not — so the LST deploy was
failing with `missing CREATE2 deployer` once it forked to L1.
Mirrors the L2 prealloc by extending account_util with a sibling helper
to_ethereum_pkg_preallocated_contract (returns {balance, code} entries) and
adding the CREATE2 deployer to the L1 prefunded_accounts list passed to
ethereum-package. The genesis-generator's el_premine_addrs handler accepts
arbitrary {balance, code} objects, so the entry passes through to the L1
genesis alloc untouched.
The standard EIP-2470 runtime bootstrap (fund one-shot signer, broadcast
pre-signed legacy tx) can't rescue this — eip155Block: 0 on the L1 geth
rejects legacy txs without chain-id replay protection.
…main.star
main.star was orchestrating six contract deploys directly (plasma L1 +
matic-to-pol + pos-bridge L1 + plasma L2 + pos-bridge L2 + LST). Pulls
that orchestration into a single src/contracts/contracts.star module
exposing two primitives:
- deploy_l1_contracts(...) — plasma L1 + matic-to-pol + pos-bridge L1,
returns (addresses, validator_config)
- deploy_l2_contracts(...) — plasma L2 + pos-bridge L2 + LST, returns
the final addresses artifact
main.star drops four imports (the individual deployers) for one
(contracts), and the two orchestration blocks shrink from ~30 lines each
to a single call. Net main.star size: 232 → 184 lines.
deploy_l{1,2}_contracts (vs the shorter deploy_l{1,2}) reads better at
the call site — `contracts.deploy_l1` would be ambiguous next to
`l1_launcher.launch`.
Also drops the now-redundant docstring on lst_deployer.deploy_lst_contracts;
peer deployers in this directory have no docstring, so matching that
style keeps the orchestrator and its callees consistent.
Defaults are no longer mainnet-conservative; the fast values that the comment described as test-only overrides are now baseline (see commit 8580fa4).
…rator Reusing 'l1_addresses' / 'addresses' across the chained deploy calls hid the fact that each step actually produces a distinct kurtosis artifact (plasma-bridge-l1-addresses → plasma-bridge-l1-addresses-with-pol → pos-bridge-l1-addresses on L1; plasma-bridge-addresses → pos-bridge-addresses on L2). Naming variables after the artifact each step produces makes the threading explicit.
The module-level REWARD_FEE/FEE_RECEIVER/MAX_DIVERGENCE constants were each referenced exactly once. Inlining them into the env_vars dict drops a layer of indirection while keeping the unit/intent comments next to the values.
…tract-addresses artifact LST was the only deploy step that wrote a separate kurtosis artifact instead of accumulating onto the chain. Folds it in: the bash wrapper now reads the upstream pos-bridge addresses, runs forge as before, then merges script/deployment.json's sPOL_L1 / sPOL_L2 keys under .root.spol / .child.spol in a single contractAddresses.json. The orchestrator returns the merged artifact as the final pos_contract_addresses, so downstream consumers get one file covering plasma + matic-to-pol + pos-bridge + sPOL/LST instead of fetching two. Renames the artifact from "lst-contract-addresses" / "spol-addresses" to "pos-contract-addresses" — describes what's in it (the full PoS contract suite) rather than the last step that touched it. Per-step artifact names (plasma-bridge-l1-addresses, plasma-bridge-l1-addresses-with-pol, etc.) keep their what-was-just-added pattern; the consumer-facing final artifact gets a what's-inside name.
…bility Sweep over all kurtosis artifact names produced by the contract deploy chain so each name describes what it is (or where it ran), without lineage cruft. Five intermediate renames + doc/CI updates pointing at the final accumulated artifact: | Step | Before | After | | ------------------- | ----------------------------------- | -------------------------- | | matic→pol migration | plasma-bridge-l1-addresses-with-pol | pol-migration-addresses | | plasma L2 | plasma-bridge-addresses | plasma-bridge-l2-addresses | | pos-bridge L2 | pos-bridge-addresses | pos-bridge-l2-addresses | | LST run service | lst-contracts-deployer | lst-deployer | | LST template script | lst-setup-validators-script | lst-validator-setup-script | Plus, updates docs/, .claude/, .github/ references that previously fetched the (now-superseded) `pos-bridge-addresses` to use `pos-contract-addresses` — the merged final artifact that includes plasma + matic-to-pol + pos-bridge + sPOL/LST. Renames the on-disk JSON filename in the partial-redeploy flow to match.
The pos-e2e companion branch tracks the pos-bridge-addresses → pos-contract-addresses artifact rename done in this PR. Pin the e2e action defaults to that branch so CI on this branch picks up the matching e2e helpers; otherwise the e2e setup.bash would fail looking for the old artifact name. Branch-name pin (rather than SHA) is intentional during the PR cycle — allows additional commits on the e2e branch to flow through without needing to bump the SHA here. Flip back to a SHA on main once both PRs have merged.
Member
Author
|
Test run: https://github.com/0xPolygon/kurtosis-pos/actions/runs/25060704919/job/73413595528 Some failures:
|
Two CI scenarios that bypass kurtosis-pos's L1 launcher were failing:
1. run-with-external-l1 launches ethereum-package directly via
--args-file external-l1/ethereum.yml, skipping main.star's L1 path
and our _merge_l1_prefunded_accounts helper. The CREATE2 deployer
prealloc never ran on L1, so sPOL's `new X{salt: y}` calls failed
with "missing CREATE2 deployer". Fix: prealloc the same EIP-2470
bytecode in ethereum.yml's prefunded_accounts directly.
2. run-with-cl-el-genesis is the partial-redeploy flow — it captures
pos-contract-addresses from a prior run, then re-launches with
should_deploy_matic_contracts: false to reuse the prior L1's
contracts. LST is the only L2 deploy step that re-touches L1 (via
vm.createSelectFork), so re-running it against an L1 that already
has sPOL deployed at the same salt-derived addresses fails with
CREATE2 CreateCollision. Fix: gate LST on should_deploy_matic_contracts
in contracts.deploy_l2_contracts. When the user supplies pre-deployed
addresses, sPOL was deployed by the prior run too — return the
pos-bridge artifact as the final result.
plasma_bridge L2 and pos_bridge L2 keep running unconditionally
because they use plain CREATE (no salt collisions) and are needed
to wire the fresh L2 chain to the existing L1.
…rvative Commit 8580fa4 dropped cl_avg_checkpoint_length 128→8 and cl_checkpoint_buffer_time 120s→10s, hoping to shorten devnet round-trips. That broke every plasma withdraw test in pos-e2e: with checkpoints landing every ~10s, a new checkpoint can land on L1 between polycli's heimdall lookup and the exit submission, shifting RootChainProxy.currentHeaderBlock and triggering WITHDRAW_BLOCK_NOT_A_PART_OF_SUBMITTED_HEADER reverts on WithdrawManager.startExitWithBurntTokens. Restore 128/120s defaults and document the failure mode in a comment so nobody repeats the change. Test profiles that explicitly need fast checkpointing (sPOL e2e, etc.) can keep overriding via params file.
…et-conservative" This reverts 7433710. The fast cadence (8 / 10s) is fine for devnet defaults — pos-e2e's wait_for_new_checkpoint will be updated to wait for one extra checkpoint past the burn-covering one, which lets polycli build proofs against a reorg-stable L2 view. See companion change in pos-e2e: core/helpers/scripts/pos-bridge.bash adds checkpoint_settle_lookahead=2.
v0.1.111 includes the merkle-proof power-of-2 padding fix from polygon-cli's fix/pos-exit-proof-non-power-of-2-merkle, which makes 'pos exit-proof' work against checkpoint ranges that aren't powers of 2 (the case our fast-cadence devnets hit). Bumps the runtime fetch in run-e2e-tests/action.yml and the build-arg in publish-images.yaml so both the pos-contract-deployer image and the e2e test runner ship with the fix.
The branch-name pin was a transient stopgap during PR review. Now that chore/rename-kurtosis-pos-artifact has merged to pos-e2e main as 036860c, flip both action defaults to that SHA so CI runs against an immutable ref instead of a feature branch that's about to be deleted.
Member
Author
|
Waiting for:
|
The cl-el-genesis test path runs `kurtosis run` twice: a first deploy that extracts the L2 EL/CL genesis + contract addresses, then a second deploy against the same L1 with `should_deploy_matic_contracts=false`. `contracts.deploy_l2_contracts` runs unconditionally on both deploys, so `deploy-pos-bridge.sh` re-executed its L1 cross-chain wiring on the redeploy. That wiring issues 7× `RootChainManager.mapToken`, each of which calls `StateSender.syncState` and bumps the L1 state-sync counter. With L1 preserved from the first deploy (counter already > 0) and bor's volumes wiped (lastStateId reset to 0), the post-redeploy state-sync counter sat ahead of bor — so when the bats deposit fired event #N, bor saw it as `stateID=N` against `Last state id was 0` and rejected it forever. Gate the L1 wiring section on `SKIP_L1_WIRING`, derived from `should_deploy_matic_contracts`. The L2 child contracts still redeploy at the same deterministic addresses, and the L1 wiring from the prior deploy is preserved untouched. Fixes the `bridge POL from L1 to L2 via plasma bridge` regression on `run-with-cl-el-genesis` introduced by #538.
After volume wipe + redeploy, bor's chaindata is fresh (lastStateId=0)
but L1's StateSender already counts 7 events from the first deploy's
mapToken wiring. Heimdall returns the next state-sync event with id=N
to bor, which rejects it as non-contiguous: bor expects last+1=1, sees
the embedded id=N, and retries forever — same fingerprint we saw in
the failing run. Pre-fill the StateReceiver precompile's slot 0
(`uint256 public lastStateId`) in the saved EL genesis so bor restarts
already in lockstep with L1.
Combined with the prior commit (skip pos-bridge L1 wiring on redeploy),
the cl-el-genesis bats `bridge POL from L1 to L2 via plasma bridge`
test now passes end-to-end.
Also routes the extracted artifacts via tmp/cl-el-genesis/ rather than
.github/configs/nightly/cl-el-genesis/. Kurtosis 1.18+ strips leading
dots from top-level dirs when packing the package archive (mholt/archives
regression in kurtosis #2947), so read_file(".github/...") fails to
resolve files under hidden dirs locally. CI's pinned 1.15.2 is
unaffected, but this keeps local and CI behaviour identical and unblocks
local reproduction.
Verified locally:
- L1 StateSender.counter: 7 (post first deploy + 7 mapToken events)
- Pre-fill applied: alloc[0x...1001].storage[0x0..0] = 0x...07
- Post redeploy: bor lastStateId() = 7 (read from genesis pre-fill)
- Post bats deposit: L1=8, L2=8 (state-sync flows end-to-end)
- bats: ok 1 bridge POL from L1 to L2 via plasma bridge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.