Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ contracts/mainnet.json
.env

# logs
*.log
*.log

# mpt-switch-test (local testing only)
ops/mpt-switch-test
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
################## update dependencies ####################
ETHEREUM_SUBMODULE_COMMIT_OR_TAG := morph-v2.1.0
ETHEREUM_TARGET_VERSION := v1.10.14-0.20251219060125-03910bc750a2
ETHEREUM_SUBMODULE_COMMIT_OR_TAG := morph-v2.1.2
ETHEREUM_TARGET_VERSION := morph-v2.1.2
TENDERMINT_TARGET_VERSION := v0.3.3


ETHEREUM_MODULE_NAME := github.com/morph-l2/go-ethereum
TENDERMINT_MODULE_NAME := github.com/morph-l2/tendermint

Expand Down
820 changes: 820 additions & 0 deletions bindings/bindings/l1sequencer.go

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions bindings/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.24.0

replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3

require github.com/morph-l2/go-ethereum v1.10.14-0.20251219060125-03910bc750a2
require github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141

require (
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
Expand All @@ -27,11 +27,9 @@ require (
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/iden3/go-iden3-crypto v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/rjeczalik/notify v0.9.3 // indirect
github.com/scroll-tech/zktrie v0.8.4 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
Expand Down
13 changes: 6 additions & 7 deletions bindings/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,16 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morph-l2/go-ethereum v1.10.14-0.20251219060125-03910bc750a2 h1:FUv9gtnvF+1AVrkoNGYbVOesi7E+STjdfD2mcqVaEY0=
github.com/morph-l2/go-ethereum v1.10.14-0.20251219060125-03910bc750a2/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M=
github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ=
github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down Expand Up @@ -143,9 +143,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
Expand Down
58 changes: 58 additions & 0 deletions contracts/contracts/l1/L1Sequencer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

/// @title L1Sequencer
/// @notice L1 contract for managing the sequencer address.
/// The sequencer address can be updated by the owner (multisig recommended).
contract L1Sequencer is OwnableUpgradeable {
// ============ Storage ============

/// @notice Current sequencer address
address public sequencer;

// ============ Events ============

/// @notice Emitted when sequencer is updated
event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer);

// ============ Initializer ============

/// @notice Initialize the contract
/// @param _owner Contract owner (multisig recommended)
/// @param _initialSequencer Initial sequencer address (can be address(0) to set later)
function initialize(address _owner, address _initialSequencer) external initializer {
require(_owner != address(0), "invalid owner");

__Ownable_init();
_transferOwnership(_owner);

// Set initial sequencer if provided
if (_initialSequencer != address(0)) {
sequencer = _initialSequencer;
emit SequencerUpdated(address(0), _initialSequencer);
}
}

// ============ Admin Functions ============

/// @notice Update sequencer address (takes effect immediately)
/// @param newSequencer New sequencer address
function updateSequencer(address newSequencer) external onlyOwner {
require(newSequencer != address(0), "invalid sequencer");
require(newSequencer != sequencer, "same sequencer");

address oldSequencer = sequencer;
sequencer = newSequencer;

emit SequencerUpdated(oldSequencer, newSequencer);
}

// ============ View Functions ============

/// @notice Get current sequencer address
function getSequencer() external view returns (address) {
return sequencer;
}
}
11 changes: 11 additions & 0 deletions contracts/contracts/l1/rollup/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,17 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
return batchDataStore[batchIndex].finalizeTimestamp > block.timestamp;
}

/// @dev proveCommittedBatchState verifies the ZK proof for a committed batch.
function proveCommittedBatchState(bytes calldata _batchHeader, bytes calldata _batchProof) public view {
// get batch data from batch header
(uint256 memPtr, bytes32 _batchHash) = _loadBatchHeader(_batchHeader);
// check batch hash
uint256 _batchIndex = BatchHeaderCodecV0.getBatchIndex(memPtr);
require(committedBatches[_batchIndex] == _batchHash, "incorrect batch hash");

_verifyProof(memPtr, _batchProof);
}

/**********************
* Internal Functions *
**********************/
Expand Down
42 changes: 42 additions & 0 deletions contracts/contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,48 @@ contract RollupCommitBatchWithProofTest is L1MessageBaseTest {

assertEq(rollup.lastCommittedBatchIndex(), 1);
}

/// @notice Test: proveCommittedBatchState proves the state of a committed batch.
function test_proveCommittedBatchState() public {
_mockVerifierCall();
_mockMessageQueueStalled();

// Warp time to simulate stall (> rollupDelayPeriod)
hevm.warp(block.timestamp + 7200);

bytes32 prevStateRoot = bytes32(uint256(1));
bytes32 postStateRoot = bytes32(uint256(2));
bytes32 withdrawalRoot = getTreeRoot();

IRollup.BatchDataInput memory batchDataInput = IRollup.BatchDataInput({
version: 0,
parentBatchHeader: batchHeader0,
lastBlockNumber: 1,
numL1Messages: 0,
prevStateRoot: prevStateRoot,
postStateRoot: postStateRoot,
withdrawalRoot: withdrawalRoot
});

bytes memory batchHeader1 = _createMatchingBatchHeader(1, 0, prevStateRoot, postStateRoot, withdrawalRoot);

hevm.prank(alice);
rollup.commitBatchWithProof(
batchDataInput,
batchSignatureInput,
batchHeader1,
hex"deadbeef" // Non-empty proof required
);
// Verify batch was committed (finalization happens separately via finalizeBatch)
assertEq(rollup.lastCommittedBatchIndex(), 1);
// finalizeTimestamp is set to block.timestamp, allowing immediate finalization
// but lastFinalizedBatchIndex is not updated until finalizeBatch is called
assertEq(rollup.lastFinalizedBatchIndex(), 0);

bytes
memory proof = hex"ffea2d2e0a0f124b2bc411325821f76e40978468a0892f57f7de20c99a4fe762dbbdfbc929f5b68b89bdecd40143f36782957e6b818fb761be749d283b16daab432f249b0ddb5537d6b7a8275505a9213eb15db1ddb095bada27a03b6f9c4edb5fd8f7110c7438abff87fed406080f3a103ad45c41fdf89542ff037adfe314a39e5688931da3a1f52f19a3a942cd36dace89340bcff9a18420596f6a06e5d14e5230baba07d1fb0a5bd20beb246875f3ad238dc040c347b10795fff4d6a92ac057c61d672f1d17bbab356c0ade3c419fb1d991f879c2d4fa02b8c4c6ad2d78a8338e9c101e7ea8eb9f1fe61a698598959eed8a33919226a623aa1cd5881d2db0d7f1811921abbb34d035056eea6a70c6079d13de1f67ccf4b2c54398a30cbbf490c2206c111c5e8734d5ef6574d1520f0e68acf482d40f11ac18a8316cb524b3b4a8adb8178e4333eb170bc3a0abfe3d69ced1c5f5b781be739ad2c45b8971455681bcd625348a050a4c5ebc469bf5f8ddb95d541baea4b71e9430452e37c084b2d0ab3c104dfe6dfab22413bf9ea4b2009b5248ec471432ab7464546ffe15033df99dba08077a60ae40633c2a5511faba9e00683497059265ab8114546729007d1e50670dc0a2cecf23cbe58417b2cf51afbf3fd8da63d936d8a92b6e9203c89665a3f40447bc5f08016739567287e5e824c164dc0c7dea8e95eddda27c4966c4d6674516d1cf84241a61b7fadbc432ce6253be085ab86771bf573aa6e506b4c98254d0192a1924297c2e29dfa5b19d99a8ff4ca3975803020f6f46c3200e09d2fb47282f5ced534b0301e5f7501aa56dc77d534c25849d7165efdba546883dda634db60f2d4a9ab608827d63a37020466318f704e30ba4223106a8092f052926714a7a2819a9afb7fb970a9a6d3058cb01ecd4d82e2e20f8996b0995818ae9c3ca815008fd01cdf42f187f723e6965c3c5ee972c9bbef7e7633776cf1af533460565bb256c5c9c6c1c50b63785daabe702d838308659e02c338ba5b47cd0508000eb4426bc76e4760d380bf9d1eb28e1dbcc9cd3a562a6b35ead2d434bce87657ab0ab2cfac2e3c410132c4bef39559dc853bfbf8319447dd365d0a6f52277046b1fdf284f27e626f4f86165eaea9b41bd4bf348325975bf3685a041c4740d300a4222063a6038b5da62c56052c5ddd1d845b51ae2782cec83fcae3966d7f4692d34fe00000000000000000000000000000000000000000000000000000000";
rollup.proveCommittedBatchState(batchHeader1, proof);
}
}

contract RollupCommitBatchTest is L1MessageBaseTest {
Expand Down
9 changes: 9 additions & 0 deletions contracts/deploy/013-DeployProxys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const deployContractProxies = async (

const RollupProxyStorageName = ProxyStorageName.RollupProxyStorageName
const L1StakingProxyStorageName = ProxyStorageName.L1StakingProxyStorageName
const L1SequencerProxyStorageName = ProxyStorageName.L1SequencerProxyStorageName

const L1GatewayRouterProxyStorageName = ProxyStorageName.L1GatewayRouterProxyStorageName
const L1ETHGatewayProxyStorageName = ProxyStorageName.L1ETHGatewayProxyStorageName
Expand Down Expand Up @@ -112,6 +113,13 @@ export const deployContractProxies = async (
return err
}

// ************************ sequencer contracts deploy ************************
// L1SequencerProxy deploy
err = await deployContractProxyByStorageName(hre, path, deployer, L1SequencerProxyStorageName)
if (err != "") {
return err
}

// ************************ rollup contracts deploy ************************
// RollupProxy deploy
err = await deployContractProxyByStorageName(hre, path, deployer, RollupProxyStorageName)
Expand Down Expand Up @@ -274,6 +282,7 @@ export const deployContractProxiesConcurrently = async (
ProxyStorageName.L1CrossDomainMessengerProxyStorageName,
ProxyStorageName.L1MessageQueueWithGasPriceOracleProxyStorageName,
ProxyStorageName.L1StakingProxyStorageName,
ProxyStorageName.L1SequencerProxyStorageName,
ProxyStorageName.RollupProxyStorageName,
ProxyStorageName.L1GatewayRouterProxyStorageName,
ProxyStorageName.L1ETHGatewayProxyStorageName,
Expand Down
20 changes: 20 additions & 0 deletions contracts/deploy/014-DeployImpls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ export const deployContractImplsConcurrently = async (

deployPromises.push(deployContract(L1StakingFactoryName, StakingImplStorageName, [L1CrossDomainMessengerProxyAddress]))

// L1Sequencer deploy (no constructor args)
const L1SequencerFactoryName = ContractFactoryName.L1Sequencer
const L1SequencerImplStorageName = ImplStorageName.L1SequencerStorageName
deployPromises.push(deployContract(L1SequencerFactoryName, L1SequencerImplStorageName))

const results = await Promise.all(deployPromises)

for (const result of results) {
Expand Down Expand Up @@ -382,6 +387,21 @@ export const deployContractImpls = async (
return err
}

// ************************ sequencer contracts deploy ************************
// L1Sequencer deploy
const L1SequencerFactoryName = ContractFactoryName.L1Sequencer
const L1SequencerImplStorageName = ImplStorageName.L1SequencerStorageName
Factory = await hre.ethers.getContractFactory(L1SequencerFactoryName)
contract = await Factory.deploy()
await contract.deployed()
console.log("%s=%s ; TX_HASH: %s", L1SequencerImplStorageName, contract.address.toLocaleLowerCase(), contract.deployTransaction.hash)
blockNumber = await hre.ethers.provider.getBlockNumber()
console.log("BLOCK_NUMBER: %s", blockNumber)
err = await storage(path, L1SequencerImplStorageName, contract.address.toLocaleLowerCase(), blockNumber || 0)
if (err != '') {
return err
}

// return
return ''
}
Expand Down
9 changes: 9 additions & 0 deletions contracts/deploy/019-AdminTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const AdminTransferConcurrently = async (
ProxyStorageName.L1CrossDomainMessengerProxyStorageName,
ProxyStorageName.L1MessageQueueWithGasPriceOracleProxyStorageName,
ProxyStorageName.L1StakingProxyStorageName,
ProxyStorageName.L1SequencerProxyStorageName, // Added L1Sequencer
ProxyStorageName.RollupProxyStorageName,
ProxyStorageName.L1GatewayRouterProxyStorageName,
ProxyStorageName.L1ETHGatewayProxyStorageName,
Expand Down Expand Up @@ -159,6 +160,7 @@ export const AdminTransfer = async (

const RollupProxyStorageName = ProxyStorageName.RollupProxyStorageName
const L1StakingProxyStorageName = ProxyStorageName.L1StakingProxyStorageName
const L1SequencerProxyStorageName = ProxyStorageName.L1SequencerProxyStorageName

const L1GatewayRouterProxyStorageName = ProxyStorageName.L1GatewayRouterProxyStorageName
const L1ETHGatewayProxyStorageName = ProxyStorageName.L1ETHGatewayProxyStorageName
Expand Down Expand Up @@ -192,6 +194,13 @@ export const AdminTransfer = async (
return err
}

// ************************ sequencer contracts admin change ************************
// L1SequencerProxy admin change
err = await AdminTransferByProxyStorageName(hre, path, deployer, L1SequencerProxyStorageName)
if (err != '') {
return err
}

// ************************ rollup contracts admin change ************************
// RollupProxy admin change
err = await AdminTransferByProxyStorageName(hre, path, deployer, RollupProxyStorageName)
Expand Down
89 changes: 89 additions & 0 deletions contracts/deploy/022-SequencerInit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import "@nomiclabs/hardhat-web3";
import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-waffle";

import {
HardhatRuntimeEnvironment
} from 'hardhat/types';
import { assertContractVariable, getContractAddressByName, awaitCondition } from "../src/deploy-utils";
import { ethers } from 'ethers'

import {
ImplStorageName,
ProxyStorageName,
ContractFactoryName,
} from "../src/types"

export const SequencerInit = async (
hre: HardhatRuntimeEnvironment,
path: string,
deployer: any,
configTmp: any
): Promise<string> => {
// L1Sequencer addresses
const L1SequencerProxyAddress = getContractAddressByName(path, ProxyStorageName.L1SequencerProxyStorageName)
const L1SequencerImplAddress = getContractAddressByName(path, ImplStorageName.L1SequencerStorageName)
const L1SequencerFactory = await hre.ethers.getContractFactory(ContractFactoryName.L1Sequencer)

const IL1SequencerProxy = await hre.ethers.getContractAt(ContractFactoryName.DefaultProxyInterface, L1SequencerProxyAddress, deployer)

if (
(await IL1SequencerProxy.implementation()).toLocaleLowerCase() !== L1SequencerImplAddress.toLocaleLowerCase()
) {
console.log('Upgrading the L1Sequencer proxy...')

// Owner is the deployer (will be transferred to multisig in production)
const owner = await deployer.getAddress()

// Get initial sequencer address from config (first sequencer address)
// Note: l2SequencerAddresses is defined in contracts/src/deploy-config/l1.ts
const initialSequencer = (configTmp.l2SequencerAddresses && configTmp.l2SequencerAddresses.length > 0)
? configTmp.l2SequencerAddresses[0]
: ethers.constants.AddressZero

console.log('Initial sequencer address:', initialSequencer)

// Upgrade and initialize the proxy with owner and initial sequencer
// Note: We set sequencer in initialize() to avoid TransparentUpgradeableProxy admin restriction
await IL1SequencerProxy.upgradeToAndCall(
L1SequencerImplAddress,
L1SequencerFactory.interface.encodeFunctionData('initialize', [owner, initialSequencer])
)

await awaitCondition(
async () => {
return (
(await IL1SequencerProxy.implementation()).toLocaleLowerCase() === L1SequencerImplAddress.toLocaleLowerCase()
)
},
3000,
1000
)

const contractTmp = new ethers.Contract(
L1SequencerProxyAddress,
L1SequencerFactory.interface,
deployer,
)

await assertContractVariable(
contractTmp,
'owner',
owner,
)

if (initialSequencer !== ethers.constants.AddressZero) {
await assertContractVariable(
contractTmp,
'sequencer',
initialSequencer,
)
console.log('L1SequencerProxy upgrade success, initial sequencer set:', initialSequencer)
} else {
console.log('L1SequencerProxy upgrade success (no initial sequencer set)')
}
}
return ''
}

export default SequencerInit
Loading
Loading