Skip to content

Conversation

@rayyan224
Copy link

@rayyan224 rayyan224 commented Dec 1, 2025

Description πŸ‘¨β€πŸ’» ;

This PR adds support for passing in JSON files that define contracts you want predeployed at genesis. For the opstack recipe, we sometimes need to inject contract bytecode + metadata directly into the L2 alloc, and this gives us a simple, configurable way to do that.

We now have a generic JSON-driven predeploy flow, and the existing EntryPoint v0.7 JSON is wired through this new path. Anyone who needs things like ERC-4337 EntryPoint, paymasters, or other system contracts can just pass in JSON paths and have them included deterministically during genesis.

Copilot AI review requested due to automatic review settings December 1, 2025 16:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a configurable mechanism for deploying contracts at genesis via JSON files for the opstack recipe. It replaces the previous --enable-entrypoint flag with a more generic and extensible --l2-predeploy-json flag that accepts JSON file paths describing L2 predeploy accounts.

Key changes:

  • Introduces a JSON-driven predeploy mechanism allowing users to inject custom contracts into L2 genesis
  • Adds PredeployAlloc struct and loadPredeployAlloc() function to parse and validate JSON predeploy configurations
  • Wires the existing ERC-4337 EntryPoint v0.7 JSON through this new mechanism

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
playground/utils/entrypoint_v0.7.json Adds JSON configuration for ERC-4337 EntryPoint v0.7 contract to be deployed at genesis
playground/artifacts.go Implements PredeployAlloc type, loadPredeployAlloc() parser, and integrates predeploy JSON files into L2 genesis alloc
playground/recipe_opstack.go Adds --l2-predeploy-json flag and wires it to the artifacts builder
README.md Documents the new --l2-predeploy-json flag with usage examples

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

}
if p.Address == "" {
return "", nil, fmt.Errorf("predeploy JSON missing address")
}
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The address field should be validated to ensure it's a valid Ethereum address. Currently, any string can be passed and will be directly inserted into the genesis alloc, which could lead to invalid genesis configuration if an improperly formatted address is provided.

Consider adding validation:

if !gethcommon.IsHexAddress(p.Address) {
    return "", nil, fmt.Errorf("predeploy JSON has invalid address format: %q", p.Address)
}
Suggested change
}
}
if !gethcommon.IsHexAddress(p.Address) {
return "", nil, fmt.Errorf("predeploy JSON has invalid address format: %q", p.Address)
}

Copilot uses AI. Check for mistakes.
Comment on lines 403 to 405
account := map[string]interface{}{
"balance": p.Balance,
"nonce": p.Nonce,
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The Balance and Nonce fields are always included in the genesis alloc account, even when they're empty strings. This could potentially lead to invalid genesis data. Consider handling empty values more explicitly:

account := map[string]interface{}{}
if p.Balance != "" {
    account["balance"] = p.Balance
}
if p.Nonce != "" {
    account["nonce"] = p.Nonce
}

Or set default values if these fields should always be present:

account := map[string]interface{}{
    "balance": p.Balance,
    "nonce":   p.Nonce,
}
if p.Balance == "" {
    account["balance"] = "0x0"
}
if p.Nonce == "" {
    account["nonce"] = "0x0"
}
Suggested change
account := map[string]interface{}{
"balance": p.Balance,
"nonce": p.Nonce,
balance := p.Balance
if balance == "" {
balance = "0x0"
}
nonce := p.Nonce
if nonce == "" {
nonce = "0x0"
}
account := map[string]interface{}{
"balance": balance,
"nonce": nonce,

Copilot uses AI. Check for mistakes.
Comment on lines 395 to 414
var p PredeployAlloc
if err := json.Unmarshal(raw, &p); err != nil {
return "", nil, fmt.Errorf("failed to unmarshal predeploy JSON: %w", err)
}
if p.Address == "" {
return "", nil, fmt.Errorf("predeploy JSON missing address")
}

account := map[string]interface{}{
"balance": p.Balance,
"nonce": p.Nonce,
}
if p.Code != "" {
account["code"] = p.Code
}
if len(p.Storage) > 0 {
account["storage"] = p.Storage
}

return p.Address, account, nil
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The hex values (balance, nonce, code) from the JSON are not validated to ensure they're properly formatted hex strings. Invalid hex values would be silently passed through to the genesis alloc, which could cause issues when the genesis is processed.

Consider adding validation for hex string fields:

// After unmarshaling
if p.Balance != "" && !isValidHex(p.Balance) {
    return "", nil, fmt.Errorf("predeploy JSON has invalid balance hex format: %q", p.Balance)
}
if p.Nonce != "" && !isValidHex(p.Nonce) {
    return "", nil, fmt.Errorf("predeploy JSON has invalid nonce hex format: %q", p.Nonce)
}
if p.Code != "" && !isValidHex(p.Code) {
    return "", nil, fmt.Errorf("predeploy JSON has invalid code hex format: %q", p.Code)
}

Where isValidHex checks for "0x" prefix and valid hex characters.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

Nicee

if err != nil {
return nil, fmt.Errorf("failed to load L2 predeploy from %q: %w", path, err)
}

Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

If multiple predeploy JSON files specify the same address, the later file will silently overwrite the earlier one without any warning. This could lead to unexpected behavior where one predeploy contract unintentionally replaces another.

Consider adding a check for duplicate addresses:

for _, path := range b.predeployJSONFiles {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read L2 predeploy JSON %q: %w", path, err)
    }

    addr, account, err := loadPredeployAlloc(data)
    if err != nil {
        return nil, fmt.Errorf("failed to load L2 predeploy from %q: %w", path, err)
    }

    if _, exists := allocs[addr]; exists {
        return nil, fmt.Errorf("duplicate predeploy address %s found in %q", addr, path)
    }

    allocs[addr] = account
}
Suggested change
if _, exists := allocs[addr]; exists {
return nil, fmt.Errorf("duplicate predeploy address %s found in %q", addr, path)
}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,7 @@
{

Choose a reason for hiding this comment

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

Would be great if we could deploy v06 - v09, and either a factory or an account to test on for each.

Copy link
Author

Choose a reason for hiding this comment

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

My thinking is users, can have there own jsons that they pass the path in via params on startup.
WDYT ?

README.md Outdated

- `--external-builder`: URL of an external builder to use (enables rollup-boost)
- `--enable-latest-fork` (int): Enables the latest fork (isthmus) at startup (0) or n blocks after genesis.
- `--l2-predeploy-json` (string[]): One or more paths to JSON files describing L2 predeploy accounts to inject into the L2 genesis (e.g. EntryPoint, paymasters, custom system contracts).

Choose a reason for hiding this comment

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

I think just calling it use-predeploys or something (L2 seems irrelevant)

Copy link
Author

@rayyan224 rayyan224 Dec 1, 2025

Choose a reason for hiding this comment

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

hmm, main thinking was because this adds it to the genesis of the l2 config, not l1. The repo deploys both an L1 and an L2. This mechanism only affects the L2 genesis config

Copy link
Collaborator

Choose a reason for hiding this comment

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

But since this flag is unique for the L2 recipe, that already includes the context that this flag only runs there so it makes more sense to me to call it --predeploy-json only and remove that namespace since it is inferred.

Copy link
Author

Choose a reason for hiding this comment

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

Hmmm ok, sure!


// PredeployAlloc is a generic JSON schema for describing a single L2
// predeploy account to be injected into the L2 genesis alloc.
type PredeployAlloc struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is already an alloc.GeneissAccount object in the go-ethereum repo with this data and the unmarshal/marshal methods. Ideally we can use that instead of this custom format.

Copy link
Author

@rayyan224 rayyan224 Dec 3, 2025

Choose a reason for hiding this comment

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

Ahhh didn't know about that one, nice find catch

Copy link
Author

Choose a reason for hiding this comment

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

Updated!

@rayyan224 rayyan224 requested a review from ferranbt December 3, 2025 14:52
@@ -0,0 +1,9 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this file?

Copy link
Author

Choose a reason for hiding this comment

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

This a config for launching an entry point v07 contract for 4437

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.

3 participants