Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions specs/protocol/l2-contract-upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# L2 Contract Upgrades

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [Overview](#overview)
- [ConditionalDeployer](#conditionaldeployer)
- [Overview](#overview-1)
- [Definitions](#definitions)
- [CREATE2 Collision](#create2-collision)
- [Deterministic Deployment Proxy](#deterministic-deployment-proxy)
- [Assumptions](#assumptions)
- [a01-001: Deterministic Deployment Proxy is Available and Correct](#a01-001-deterministic-deployment-proxy-is-available-and-correct)
- [Mitigations](#mitigations)
- [a02-002: Initcode is Well-Formed](#a02-002-initcode-is-well-formed)
- [Mitigations](#mitigations-1)
- [Invariants](#invariants)
- [i01-001: Deterministic Address Derivation](#i01-001-deterministic-address-derivation)
- [Impact](#impact)
- [i02-002: Idempotent Deployment Operations](#i02-002-idempotent-deployment-operations)
- [Impact](#impact-1)
- [i03-003: Non-Reverting Collision Handling](#i03-003-non-reverting-collision-handling)
- [Impact](#impact-2)
- [i04-004: Collision Detection Accuracy](#i04-004-collision-detection-accuracy)
- [Impact](#impact-3)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

This specification defines the mechanism for upgrading L2 predeploy contracts through deterministic, hard fork-driven
Network Upgrade Transactions (NUTs). The system enables safe, well-tested upgrades of L2 contracts with both
implementation and upgrade paths written in Solidity, ensuring determinism, verifiability, and testability across all
client implementations.

The upgrade system maintains the existing pattern of injecting Network Upgrade Transactions at specific fork block
heights while improving the development and testing process. Upgrade transactions are defined in JSON bundles that are
tracked in git, generated from Solidity scripts, and executed deterministically at fork activation.

## ConditionalDeployer

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.

ConditionDeployer is gonna be a periphery contract rather than a Predeploy right?

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.

What do you mean by periphery contract, like a pre-install?

My thoughts:

  1. We should also add it to the L2Genesis, as we want it available in future hardforks.
  2. It doesn't need to be upgradable. If we need to replace it we can just deploy a different copy of it using the same method.


### Overview

The ConditionalDeployer contract enables deterministic deployment of contract implementations while maintaining
idempotency across upgrade transactions. It ensures that unchanged contract bytecode always deploys to the same
address, and that attempting to deploy already-deployed bytecode succeeds silently _rather than reverting_.

This component enables upgrade transactions to unconditionally deploy for all implementation contracts without
requiring developers to manually track which contracts have changed between upgrades.

### Definitions

#### CREATE2 Collision

A CREATE2 collision occurs when attempting to deploy contract bytecode to an address where a contract with identical
bytecode already exists. This happens when the same initcode and salt are used in multiple deployment attempts.

Note: when CREATE2 targets an address that already has code, the
[zero address is placed on the stack][create2-spec] (execution specs).

[create2-spec]:
https://github.com/ethereum/execution-specs/blob/4ef381a0f75c96b52da635653ab580e731d3882a/src/ethereum/forks/prague/vm/instructions/system.py#L112

#### Deterministic Deployment Proxy

The canonical deterministic deployment proxy contract at address `0x4e59b44847b379578588920cA78FbF26c0B4956C`,
originally deployed by Nick Johnson (Arachnid). This contract provides CREATE2-based deployment with a fixed deployer
address across all chains.

Note: when the deterministic deployment proxy deploys to an address that already has code, [it will revert with no data](https://github.com/Arachnid/deterministic-deployment-proxy/blob/be3c5974db5028d502537209329ff2e730ed336c/source/deterministic-deployment-proxy.yul#L13).
Otherwise the ConditionalDeployer would not be required.

### Assumptions

#### a01-001: Deterministic Deployment Proxy is Available and Correct

The [Deterministic Deployment Proxy](#deterministic-deployment-proxy) exists at the expected address and correctly
implements CREATE2 deployment semantics. The proxy must deterministically compute deployment addresses and execute
deployments as specified.

##### Mitigations

- The [Deterministic Deployment Proxy](#deterministic-deployment-proxy) is a well-established contract deployed across
all EVM chains using the same keyless deployment transaction
- The proxy's behavior is verifiable by inspecting its bytecode and testing deployment operations
- The proxy contract is immutable and cannot be upgraded or modified

#### a02-002: Initcode is Well-Formed

Callers provide valid EVM initcode that, when executed, will either successfully deploy a contract or revert with a
clear error. Malformed initcode that produces undefined behavior is not considered.

##### Mitigations

- Initcode is generated by the Solidity compiler from verified source code
- The upgrade transaction generation process includes validation of all deployment operations
- Fork-based testing exercises all deployments before inclusion in upgrade bundles

### Invariants

#### i01-001: Deterministic Address Derivation

For any given initcode and salt combination, the ConditionalDeployer MUST always compute the same deployment address,
regardless of whether the contract has been previously deployed. The address calculation MUST match the CREATE2
address that would be computed by the [Deterministic Deployment Proxy](#deterministic-deployment-proxy).

##### Impact

**Severity: Critical**

If address derivation is non-deterministic or inconsistent with CREATE2 semantics, upgrade transactions could deploy
implementations to unexpected addresses. This would break proxy upgrade operations that expect implementations at
specific predetermined addresses, potentially causing proxies to point to non-existent or incorrect implementations.

#### i02-002: Idempotent Deployment Operations

Calling the ConditionalDeployer multiple times with identical initcode and salt MUST produce the same outcome: the
first call deploys the contract, and subsequent calls succeed without modification. No operation should revert due to
a [CREATE2 Collision](#create2-collision).

##### Impact

**Severity: Critical**

If deployments are not idempotent, upgrade transactions that attempt to deploy unchanged implementations would revert
or deploy the implementation to an unexpected address. In the latter case, the proxy would then be upgrade incorrectly,
as we must predict the implementation address in advance in order to correctly generate the NUT bundle.

#### i03-003: Non-Reverting Collision Handling

When a [CREATE2 Collision](#create2-collision) is detected (contract already deployed at the target address), the
ConditionalDeployer MUST return successfully without reverting and without modifying blockchain state.

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.

maybe worth mentioning what address it will return in case of collision

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.

Do you think it should return the deployment address? In some ways it doesn't matter since we can't actually use the return value, as it needs to be pre-computed off-chain (all we can do is inject transactions, we can't read their results).

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 the cleaner approach would be to just not return anything, as you mention for this use case we cannot use the address anyway and returning the address in cases where no contract is deployed (because it was deployed before) feels off.


##### Impact

**Severity: Medium**

If the collisions cause reverts, the following transactions can still proceed, however the presence of reverting
transactions in an upgrade block is likely to lead to confusion.

#### i04-004: Collision Detection Accuracy

The ConditionalDeployer MUST correctly distinguish between addresses where no contract exists (deploy needed) and
addresses where a contract already exists (collision detected). False negatives (failing to detect existing contracts)
and false positives (detecting non-existent contracts) are both prohibited.

##### Impact

**Severity: High**

False negatives would cause the ConditionalDeployer to attempt redeployment to occupied addresses, resulting in
failed deployments and breaking the upgrade process. False positives would prevent legitimate deployments from
occurring, causing proxies to reference non-existent implementation addresses.