Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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;
Comment thread
Kukoomomo marked this conversation as resolved.
Comment thread
Kukoomomo marked this conversation as resolved.

/**********************
* 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