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
2 changes: 1 addition & 1 deletion bindings/bin/rollup_deployed.hex

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/bin/zkevmverifierv1_deployed.hex

Large diffs are not rendered by default.

85 changes: 83 additions & 2 deletions bindings/bindings/rollup.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions bindings/bindings/rollup_more.go

Large diffs are not rendered by default.

35 changes: 33 additions & 2 deletions bindings/bindings/zkevmverifierv1.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/bindings/zkevmverifierv1_more.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions contracts/contracts/l1/rollup/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ interface IRollup {
BatchSignatureInput calldata batchSignatureInput
) external payable;

/// @notice Commit batch state when blob hash is already stored (recommit after revert without blob).
/// @dev Requires batchBlobVersionedHashes[nextBatchIndex] != 0.
function commitState(
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput
) external;

/// @notice Commit a batch with ZKP proof for permissionless submission.
/// @dev This function allows anyone to submit batches when the sequencer is offline or censoring.
///
Expand Down
62 changes: 54 additions & 8 deletions contracts/contracts/l1/rollup/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {IL1Staking} from "../staking/IL1Staking.sol";

/// @title Rollup
/// @notice This contract maintains data for the Morph rollup.
/// +----------------------+--------+---------+--------+--------------------------------------------------+
contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
/*************
* Constants *
Expand Down Expand Up @@ -104,6 +105,10 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
/// @dev After this period, anyone can submit batches if sequencers are offline or censoring.
uint256 public rollupDelayPeriod;

/// @notice Store blob versioned hash per batch index. Preserved across revertBatch so recommit can reuse.
/// @dev Placed after rollupDelayPeriod for upgrade-safe storage layout (forward compatibility).
mapping(uint256 batchIndex => bytes32 blobVersionedHash) public batchBlobVersionedHashes;

/**********************
* Function Modifiers *
**********************/
Expand All @@ -116,13 +121,13 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {

/// @notice Only challenger allowed.
modifier onlyChallenger() {
require(isChallenger[_msgSender()], "caller challenger allowed");
require(isChallenger[_msgSender()], "only challenger allowed");
_;
}

/// @notice Modifier to ensure that there is no pending revert request.
modifier nonReqRevert() {
require(revertReqIndex == 0, "need revert");
require(revertReqIndex == 0, "pending revert request");
_;
}

Expand Down Expand Up @@ -217,6 +222,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {

committedBatches[_batchIndex] = _batchHash;
batchDataStore[_batchIndex] = BatchData(block.timestamp, block.timestamp, 0, 0);
batchBlobVersionedHashes[_batchIndex] = BatchHeaderCodecV0.getBlobVersionedHash(memPtr);

committedStateRoots[_batchIndex] = _postStateRoot;
finalizedStateRoots[_batchIndex] = _postStateRoot;
Expand All @@ -232,20 +238,49 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput
) external payable override onlyActiveStaker nonReqRevert whenNotPaused {
// check l1msg delay - sequencer must process L1 messages when delayed
// check if the next batch has a stored blob hash
(uint256 _batchPtr, ) = _loadBatchHeader(batchDataInput.parentBatchHeader);
uint256 _nextBatchIndex = BatchHeaderCodecV0.getBatchIndex(_batchPtr) + 1;
require(batchBlobVersionedHashes[_nextBatchIndex] == bytes32(0), "commitBatch requires no stored blob hash");
if (
IL1MessageQueue(messageQueue).getFirstUnfinalizedMessageEnqueueTime() + rollupDelayPeriod < block.timestamp
) {
require(batchDataInput.numL1Messages > 0, "l1msg delay");
}
uint256 submitterBitmap = IL1Staking(l1StakingContract).getStakerBitmap(_msgSender());
bytes32 _blobVersionedHash = (blobhash(0) == bytes32(0)) ? ZERO_VERSIONED_HASH : blobhash(0);
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, submitterBitmap, _blobVersionedHash);
}

/// @inheritdoc IRollup
/// @notice Commit batch state when blob hash is already stored (recommit after revert without blob).
function commitState(
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput
) external override onlyActiveStaker nonReqRevert whenNotPaused {
require(blobhash(0) == bytes32(0), "commitState must not carry blob");
(uint256 _batchPtr, ) = _loadBatchHeader(batchDataInput.parentBatchHeader);
uint256 _nextBatchIndex = BatchHeaderCodecV0.getBatchIndex(_batchPtr) + 1;
require(batchBlobVersionedHashes[_nextBatchIndex] != bytes32(0), "no stored blob hash for this batch");
if (
IL1MessageQueue(messageQueue).getFirstUnfinalizedMessageEnqueueTime() + rollupDelayPeriod < block.timestamp
) {
require(batchDataInput.numL1Messages > 0, "l1msg delay");
}
uint256 submitterBitmap = IL1Staking(l1StakingContract).getStakerBitmap(_msgSender());
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, submitterBitmap);
_commitBatchWithBatchData(
batchDataInput,
batchSignatureInput,
submitterBitmap,
batchBlobVersionedHashes[_nextBatchIndex]
);
}

function _commitBatchWithBatchData(
BatchDataInput calldata batchDataInput,
BatchSignatureInput calldata batchSignatureInput,
uint256 submitterBitmap
uint256 submitterBitmap,
bytes32 blobVersionedHash
) internal {
require(batchDataInput.version == 0 || batchDataInput.version == 1, "invalid version");
require(batchDataInput.prevStateRoot != bytes32(0), "previous state root is zero");
Expand Down Expand Up @@ -284,7 +319,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
assembly {
_batchIndex := add(_batchIndex, 1) // increase batch index
}
bytes32 _blobVersionedHash = (blobhash(0) == bytes32(0)) ? ZERO_VERSIONED_HASH : blobhash(0);
bytes32 _blobVersionedHash = blobVersionedHash;

{
uint256 _headerLength = BatchHeaderCodecV0.BATCH_HEADER_LENGTH;
Expand Down Expand Up @@ -314,6 +349,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
}
committedBatches[_batchIndex] = BatchHeaderCodecV0.computeBatchHash(_batchPtr, _headerLength);
committedStateRoots[_batchIndex] = batchDataInput.postStateRoot;
batchBlobVersionedHashes[_batchIndex] = _blobVersionedHash;
uint256 proveRemainingTime = 0;
if (inChallenge) {
// Make the batch finalize time longer than the time required for the current challenge
Expand Down Expand Up @@ -368,8 +404,17 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {
require(batchDataInput.numL1Messages > 0, "l1msg delay");
}
require(rollupDelay || l1MsgQueueDelayed, "invalid timing");

_commitBatchWithBatchData(batchDataInput, batchSignatureInput,0);
// check if the next batch has a stored blob hash
(uint256 _batchPtr, ) = _loadBatchHeader(batchDataInput.parentBatchHeader);
uint256 _nextBatchIndex = BatchHeaderCodecV0.getBatchIndex(_batchPtr) + 1;
bytes32 _blobVersionedHash = bytes32(0);
if (batchBlobVersionedHashes[_nextBatchIndex] != bytes32(0)) {
require(blobhash(0) == bytes32(0), "must not carry blob when using stored blob hash");
_blobVersionedHash = batchBlobVersionedHashes[_nextBatchIndex];
} else {
_blobVersionedHash = (blobhash(0) == bytes32(0)) ? ZERO_VERSIONED_HASH : blobhash(0);
}
_commitBatchWithBatchData(batchDataInput, batchSignatureInput, 0, _blobVersionedHash);

// get batch data from batch header
(uint256 memPtr, bytes32 _batchHash) = _loadBatchHeader(_batchHeader);
Expand Down Expand Up @@ -617,6 +662,7 @@ contract Rollup is IRollup, OwnableUpgradeable, PausableUpgradeable {

delete batchDataStore[_batchIndex - 1];
delete committedStateRoots[_batchIndex - 1];
delete batchBlobVersionedHashes[_batchIndex - 1];
delete challenges[_batchIndex - 1];

emit FinalizeBatch(
Expand Down
103 changes: 72 additions & 31 deletions contracts/contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ contract RollupCommitBatchWithProofTest is L1MessageBaseTest {
bytes public batchHeader0;
bytes32 public batchHash0;
IRollup.BatchSignatureInput public batchSignatureInput;

// Slot constants for storage manipulation (from forge inspect Rollup storageLayout)
uint256 constant ROLLUP_DELAY_PERIOD_SLOT = 172; // slot for rollupDelayPeriod

// ZERO_VERSIONED_HASH constant from Rollup contract
bytes32 constant ZERO_VERSIONED_HASH = 0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014;


function setUp() public virtual override {
super.setUp();

Expand Down Expand Up @@ -64,19 +58,7 @@ contract RollupCommitBatchWithProofTest is L1MessageBaseTest {
// Set rollupDelayPeriod (e.g., 1 hour) - no prank needed for hevm.store
hevm.store(address(rollup), bytes32(ROLLUP_DELAY_PERIOD_SLOT), bytes32(uint256(3600)));
}

/// @dev Helper to compute dataHash for a batch with no L1 messages
/// dataHash = keccak256(lastBlockNumber || numL1Messages)
function _computeDataHash(uint64 lastBlockNumber, uint16 numL1Messages) internal pure returns (bytes32) {
// Construct the data: 8 bytes lastBlockNumber + 2 bytes numL1Messages
bytes memory data = new bytes(10);
assembly {
mstore(add(data, 0x20), shl(192, lastBlockNumber)) // 8 bytes
mstore(add(data, 0x28), shl(240, numL1Messages)) // 2 bytes at offset 8
}
return keccak256(data);
}


/// @dev Helper to compute sequencerSetVerifyHash from sequencerSets
function _getSequencerSetVerifyHash() internal view returns (bytes32) {
return keccak256(batchSignatureInput.sequencerSets);
Expand Down Expand Up @@ -763,6 +745,8 @@ contract RollupCommitBatchTest is L1MessageBaseTest {
abi.encodeCall(IL1Staking.getStakerBitmap, (address(0))),
abi.encode(2)
);
_setupDelayAndWarpForProof();
_mockVerifierForProof();
hevm.startPrank(address(0));
hevm.expectEmit(true, true, false, true);
emit IRollup.CommitBatch(1, bytes32(0xc1862b08d265f073817a8ce0d7cbb426c16d58a86b93464244ab1d027318642e));
Expand All @@ -775,7 +759,7 @@ contract RollupCommitBatchTest is L1MessageBaseTest {
bytesData1,
bytesData3
);
rollup.commitBatch(batchDataInput, batchSignatureInput);
rollup.commitBatchWithProof(batchDataInput, batchSignatureInput, batchHeader1, hex"deadbeef");
hevm.stopPrank();

assertFalse(rollup.isBatchFinalized(1));
Expand Down Expand Up @@ -847,7 +831,7 @@ contract RollupCommitBatchTest is L1MessageBaseTest {
bytesData1,
bytesData4
);
rollup.commitBatch(batchDataInput, batchSignatureInput);
rollup.commitBatchWithProof(batchDataInput, batchSignatureInput, batchHeader2, hex"deadbeef");

hevm.stopPrank();
assertFalse(rollup.isBatchFinalized(2));
Expand Down Expand Up @@ -991,17 +975,36 @@ contract RollupTest is L1MessageBaseTest {
rollup.commitBatch(batchDataInput, batchSignatureInput);
hevm.stopPrank();

// commit batch with one chunk, no tx, correctly
// commit batch with one chunk, no tx, correctly (commitBatch requires blob when no stored hash; use commitBatchWithProof)
_setupDelayAndWarpForProof();
_mockVerifierForProof();
bytes32 dataHash1 = _computeDataHash(1, 0);
bytes memory batchHeader1ForProof = _createBatchHeaderV0ForProof(
1,
0,
0,
dataHash1,
stateRoot,
stateRoot,
getTreeRoot(),
keccak256(batchSignatureInput.sequencerSets),
rollup.committedBatches(0)
);
hevm.startPrank(alice);
batchDataInput = IRollup.BatchDataInput(0, batchHeader0, 1, 0, stateRoot, stateRoot, getTreeRoot());
hevm.deal(address(0), 10 ether);
rollup.commitBatch(batchDataInput, batchSignatureInput);
rollup.commitBatchWithProof(
batchDataInput,
batchSignatureInput,
batchHeader1ForProof,
hex"deadbeef"
);
hevm.stopPrank();
assertGt(uint256(rollup.committedBatches(1)), 0);

// batch is already committed, revert
// batch is already committed, revert (commitBatch also reverts with "commitBatch requires no stored blob hash" when slot is set)
hevm.startPrank(alice);
hevm.expectRevert("batch already committed");
hevm.expectRevert("commitBatch requires no stored blob hash");
batchDataInput = IRollup.BatchDataInput(0, batchHeader0, 1, 0, stateRoot, stateRoot, getTreeRoot());
rollup.commitBatch(batchDataInput, batchSignatureInput);
hevm.stopPrank();
Expand All @@ -1028,12 +1031,34 @@ contract RollupTest is L1MessageBaseTest {
rollup.importGenesisBatch(batchHeader0);
bytes32 batchHash0 = rollup.committedBatches(0);

// commit one batch
// commit one batch (use commitBatchWithProof: commitBatch requires no stored hash and blob tx)
_setupDelayAndWarpForProof();
_mockMessageQueueNotDelayedForProof();
_mockVerifierForProof();
bytes32 dataHash1 = _computeDataHash(1, 0);
bytes memory batchHeader1ForProof = _createBatchHeaderV0ForProof(
1,
0,
0,
dataHash1,
stateRoot,
stateRoot,
bytes32(uint256(4)),
keccak256(batchSignatureInput.sequencerSets),
batchHash0
);
hevm.startPrank(alice);
batchDataInput = IRollup.BatchDataInput(0, batchHeader0, 1, 0, stateRoot, stateRoot, bytes32(uint256(4)));
rollup.commitBatch(batchDataInput, batchSignatureInput); // first chunk with too many txs
rollup.commitBatchWithProof(
batchDataInput,
batchSignatureInput,
batchHeader1ForProof,
hex"deadbeef"
);
hevm.stopPrank();
assertEq(rollup.committedBatches(1), 0x25c3e4fee90e53de960c1092746c431ab570eacf8513011902fa65f10c814541);
// warp again so second commitBatchWithProof passes rollupDelay (batchDataStore[1].originTimestamp + period < block.timestamp)
hevm.warp(block.timestamp + 3601);
bytes memory batchHeader1 = new bytes(249);
assembly {
mstore(add(batchHeader1, 0x20), 0) // version
Expand All @@ -1053,11 +1078,27 @@ contract RollupTest is L1MessageBaseTest {
mstore(add(batchHeader1, add(0x20, 249)), 0) // bitmap0
}

// commit another batch
// commit another batch (commitBatchWithProof)
bytes32 dataHash2 = _computeDataHash(1, 0);
bytes memory batchHeader2ForProof = _createBatchHeaderV0ForProof(
2,
0,
0,
dataHash2,
stateRoot,
stateRoot,
bytes32(uint256(4)),
keccak256(batchSignatureInput.sequencerSets),
rollup.committedBatches(1)
);
hevm.startPrank(alice);
batchDataInput = IRollup.BatchDataInput(0, batchHeader1, 1, 0, stateRoot, stateRoot, bytes32(uint256(4)));

rollup.commitBatch(batchDataInput, batchSignatureInput); // first chunk with too many txs
rollup.commitBatchWithProof(
batchDataInput,
batchSignatureInput,
batchHeader2ForProof,
hex"deadbeef"
);
hevm.stopPrank();

hevm.startPrank(multisig);
Expand Down
Loading
Loading