Skip to content

feat(op-deployer): add prepare command to predict L1 addresses#21560

Open
0xiamflux wants to merge 19 commits into
developfrom
feat/pcd-predict-l1
Open

feat(op-deployer): add prepare command to predict L1 addresses#21560
0xiamflux wants to merge 19 commits into
developfrom
feat/pcd-predict-l1

Conversation

@0xiamflux

@0xiamflux 0xiamflux commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the prepare command to op-deployer. prepare predicts a chain's L1 contract addresses without broadcasting anything.

What it does

For each chain in the intent, prepare:

  1. Reads intent.toml and state.json from the workdir.
  2. Forks the live L1 (--l1-rpc-url) and dry-runs OPCM.deploy() through the DeployOPChain script, using a NoopBroadcaster so nothing is written to L1.
  3. Records the predicted addresses into state.json.

The dry-run runs as the address derived from --private-key. Predicted addresses are deterministic in the sender and salt mixer, so the prediction matches the real deployment.

State changes

ChainState gains a Deployed bool to distinguish predicted addresses (prepare) from broadcast addresses (apply/continue). shouldDeployOPChain and contract verification now skip predicted-only chains, so running apply over a prepared state deploys the chain rather than treating the prediction as finished.

Test Plan

  • Test config validation rejects a missing private key, a malformed private key, and a missing L1 RPC URL.
  • Test the prediction input holds the correct values derived from the state.
  • Test prepare fails when the intent is missing the OPCM address or superchain config proxy.
  • Test predicted addresses are updated in the state with the flag Deployed=false.

Closes #20908

@0xiamflux 0xiamflux changed the title feat(pcd): Bootstrap prepare command and predict L1 addresses feat(op-deployer): add prepare command to predict L1 addresses Jun 25, 2026
Comment thread op-deployer/pkg/deployer/pipeline/opchain.go Outdated
Comment thread op-deployer/pkg/deployer/prepare.go
Comment thread op-deployer/pkg/deployer/prepare.go Outdated
Comment thread op-deployer/pkg/deployer/state/state.go Outdated
Comment thread op-deployer/pkg/deployer/prepare.go
Comment thread op-deployer/pkg/deployer/pipeline/opchain.go Outdated
@0xiamflux 0xiamflux marked this pull request as ready for review July 2, 2026 13:55
@0xiamflux 0xiamflux requested review from a team as code owners July 2, 2026 13:55
Comment thread op-deployer/pkg/deployer/state/state.go Outdated
Comment on lines +163 to +164
// predictChains predicts the L1 addresses for each undeployed chain in the intent
// and records them as not deployed. Chains that have been deployed are skipped so a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we have more than 1 chain defined in the intent?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's possible.

Comment thread op-deployer/pkg/cli/app.go Outdated
},
{
Name: "prepare",
Usage: "prepares a chain deployment by generating the genesis artifacts",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we also mention that prepare should be called after init and that it should not be called with apply?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, passing a prepared state/intent through the apply pipeline can create unsupported scenarios so we check for those to error out clearly on apply. So I think it's worth to callout this in the Usage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Enforced that here and documented it here

}
}

func Prepare(ctx context.Context, cfg PrepareConfig) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what happens if someone runs init -> prepare -> apply ? would our state.json be overwritten? maybe we should have some way of preventing apply if we have already called prepare or do you think this is not an issue?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same question but what happens if init -> prepare -> init?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed here by checking for Prepared flag in apply pipeline, we error out if it's there, it's backward compatible as any state missing the flag is assumed to be produced by an older pipeline and defaults to false.

Comment thread op-deployer/pkg/deployer/state/state.go Outdated
// pinned during the prepare dry-run, keeping the predicted L1 addresses valid
// across the relevant stages of the permissionless pipeline. A nil L1PredictSenderAddress
// means no sender was pinned, so any deployer is accepted (older pipeline).
func (s *State) CheckL1PredictSender(deployer common.Address) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i think we should have something similar like we check the sender but for the opcm aswell, basically making sure that we use the same opcm for both prepare to calculate the addresses but also we use the same opcm for the actual deployment (continue)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed here. Similar shape as the deployer's address check.

Comment on lines +96 to +108
intent, err := pipeline.ReadIntent(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read intent: %w", err)
}

st, err := pipeline.ReadState(cfg.Workdir)
if err != nil {
return fmt.Errorf("failed to read state: %w", err)
}

if len(intent.Chains) == 0 {
return fmt.Errorf("intent has no chains to prepare")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

noticed the apply pipeline enforces a few more checks like if the intent is valid, or the state version and chain id. If we end up using similar checks later in continue then maybe we should enforce them from prepare to make sure we can later actually call continue without issues, but wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed here

Comment thread op-deployer/pkg/deployer/state/state.go Outdated

// L1PredictSenderAddress is the address that performed the L1 deploy dry-run.
// It is used to verify that the same deployer address is used for the relevant stages of the permissionless pipeline.
L1PredictSenderAddress *common.Address `json:"deployerAddress,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This json key is inconsistent. Should we change it to predictSenderAddress to align with predictOpcmAddress below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed here

// The OPCM, superchain config and salt mixer are taken from the committed intent
// and state so the prediction matches the eventual broadcast. Role addresses are set to
// placeholders since they are not relevant for the prediction.
func makePredictionInput(intent *state.Intent, st *state.State, chain *state.ChainIntent) (opcm.DeployOPChainInput, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function duplicates much of makeDCI, and the two have already drifted: gas-limit defaulting here vs. raw thisIntent.GasLimit there, and Opcm/SuperchainConfig sourced from the intent here vs. from state there.

Could you modify the approach by introducing a helper buildDeployOPChainInput(proofParams, roles, opcm, superchainCfg, l2ChainID, saltMixer, gasLimit, chain) that makeDCI, makePredictionInput, and the future continue code all call, with each caller supplying its own roles/OPCM/superchain provenance? I think that would help ensure the prediction and actual deployment stay in-sync

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed here

Comment thread op-deployer/pkg/deployer/prepare.go Outdated
GasLimit: gasLimit,

DisputeGameType: proofParams.DisputeGameType,
DisputeAbsolutePrestate: proofParams.DisputeAbsolutePrestate,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Once #21588 lands, DeployOPChainInput will have a required StartingAnchorRoot field, and DeployOPChain.checkInput reverts with "startingAnchorRoot not set" when it's zero. So this code will need to be updated to include that new field.

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.

[PCD] Predict L1 addresses via dry-run

5 participants