@@ -1262,3 +1262,182 @@ contract RollupTest is L1MessageBaseTest {
12621262 rollup.importGenesisBatch (batchHeader);
12631263 }
12641264}
1265+
1266+ /// @dev Tests for commitState: recommit after revert using stored blob hash, no blob in tx.
1267+ contract RollupCommitStateTest is L1MessageBaseTest {
1268+ bytes32 public stateRoot = bytes32 (uint256 (1 ));
1269+ IRollup.BatchDataInput public batchDataInput;
1270+ IRollup.BatchSignatureInput public batchSignatureInput;
1271+
1272+ function setUp () public virtual override {
1273+ super .setUp ();
1274+ batchSignatureInput = IRollup.BatchSignatureInput (
1275+ uint256 (0 ),
1276+ abi.encode (uint256 (0 ), new address [](0 ), uint256 (0 ), new address [](0 ), uint256 (0 ), new address [](0 )),
1277+ bytes ("0x " )
1278+ );
1279+ hevm.deal (alice, 5 * STAKING_VALUE);
1280+ Types.StakerInfo memory stakerInfo = ffi.generateStakerInfo (alice);
1281+ address [] memory add = new address [](1 );
1282+ add[0 ] = alice;
1283+ hevm.prank (multisig);
1284+ l1Staking.updateWhitelist (add, new address [](0 ));
1285+ hevm.prank (alice);
1286+ l1Staking.register {value: STAKING_VALUE}(stakerInfo.tmKey, stakerInfo.blsKey);
1287+ }
1288+
1289+ function _genesisBatchHeader () internal pure returns (bytes memory batchHeader0 ) {
1290+ batchHeader0 = new bytes (249 );
1291+ bytes32 bytesData1 = bytes32 (uint256 (1 ));
1292+ bytes32 bytesData0 = bytes32 (uint256 (0 ));
1293+ assembly {
1294+ mstore (add (batchHeader0, add (0x20 , 25 )), 1 )
1295+ mstore (add (batchHeader0, add (0x20 , 57 )), 0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014 )
1296+ mstore (add (batchHeader0, add (0x20 , 121 )), bytesData1)
1297+ mstore (add (batchHeader0, add (0x20 , 217 )), bytesData0)
1298+ }
1299+ }
1300+
1301+ function _buildBatchHeader1ForProof (bytes32 parentBatchHash ) internal view returns (bytes memory ) {
1302+ return _createBatchHeaderV0ForProof (
1303+ 1 ,
1304+ 0 ,
1305+ 0 ,
1306+ _computeDataHash (1 , 0 ),
1307+ stateRoot,
1308+ stateRoot,
1309+ bytes32 (uint256 (4 )),
1310+ keccak256 (batchSignatureInput.sequencerSets),
1311+ parentBatchHash
1312+ );
1313+ }
1314+
1315+ function _batchHeader1Bytes (bytes32 parentBatchHash ) internal pure returns (bytes memory batchHeader1 ) {
1316+ bytes32 bytesData1 = bytes32 (uint256 (1 ));
1317+ bytes32 bytesData4 = bytes32 (uint256 (4 ));
1318+ batchHeader1 = new bytes (249 );
1319+ assembly {
1320+ mstore (add (batchHeader1, 0x20 ), 0 )
1321+ mstore (add (batchHeader1, add (0x20 , 1 )), shl (192 , 1 ))
1322+ mstore (add (batchHeader1, add (0x20 , 9 )), 0 )
1323+ mstore (add (batchHeader1, add (0x20 , 17 )), 0 )
1324+ mstore (add (batchHeader1, add (0x20 , 25 )), 0xe979da4b80d60a17ce56fa19278c6f3a7e1b43359fb8a8ea46d0264de7d653ab )
1325+ mstore (add (batchHeader1, add (0x20 , 57 )), 0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014 )
1326+ mstore (add (batchHeader1, add (0x20 , 89 )), bytesData1)
1327+ mstore (add (batchHeader1, add (0x20 , 121 )), bytesData1)
1328+ mstore (add (batchHeader1, add (0x20 , 153 )), bytesData4)
1329+ mstore (add (batchHeader1, add (0x20 , 185 )), 0xf1f58308e98844ec99e2990d88bfb36e1a30f0e6591e62af90ae6f8498a1b067 )
1330+ mstore (add (batchHeader1, add (0x20 , 217 )), parentBatchHash)
1331+ mstore (add (batchHeader1, add (0x20 , 249 )), 0 )
1332+ }
1333+ }
1334+
1335+ /// @dev Setup: genesis + batch 1 committed (via commitBatchWithProof) so batch 1 has stored blob hash.
1336+ function _setupCommitStatePrecondition () internal {
1337+ bytes memory batchHeader0 = _genesisBatchHeader ();
1338+ hevm.prank (multisig);
1339+ rollup.importGenesisBatch (batchHeader0);
1340+ bytes32 batchHash0 = rollup.committedBatches (0 );
1341+ _setupDelayAndWarpForProof ();
1342+ _mockMessageQueueNotDelayedForProof ();
1343+ _mockVerifierForProof ();
1344+ bytes memory batchHeader1ForProof = _buildBatchHeader1ForProof (batchHash0);
1345+ batchDataInput = IRollup.BatchDataInput (0 , batchHeader0, 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1346+ hevm.prank (alice);
1347+ rollup.commitBatchWithProof (
1348+ batchDataInput,
1349+ batchSignatureInput,
1350+ batchHeader1ForProof,
1351+ hex "deadbeef "
1352+ );
1353+ hevm.prank (multisig);
1354+ rollup.revertBatch (_batchHeader1Bytes (batchHash0), 1 );
1355+ }
1356+
1357+ /// @dev commitState succeeds when recommitting after revertBatch (uses stored blob hash).
1358+ function test_commitState_succeeds_after_revertBatch () public {
1359+ bytes memory batchHeader0 = _genesisBatchHeader ();
1360+ hevm.prank (multisig);
1361+ rollup.importGenesisBatch (batchHeader0);
1362+ bytes32 batchHash0 = rollup.committedBatches (0 );
1363+
1364+ // Commit batch 1 via commitBatchWithProof so we have stored blob hash
1365+ _setupDelayAndWarpForProof ();
1366+ _mockMessageQueueNotDelayedForProof ();
1367+ _mockVerifierForProof ();
1368+ bytes memory batchHeader1ForProof = _buildBatchHeader1ForProof (batchHash0);
1369+ batchDataInput = IRollup.BatchDataInput (0 , batchHeader0, 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1370+ hevm.prank (alice);
1371+ rollup.commitBatchWithProof (
1372+ batchDataInput,
1373+ batchSignatureInput,
1374+ batchHeader1ForProof,
1375+ hex "deadbeef "
1376+ );
1377+ assertEq (rollup.committedBatches (1 ), 0x25c3e4fee90e53de960c1092746c431ab570eacf8513011902fa65f10c814541 );
1378+ assertGt (uint256 (rollup.batchBlobVersionedHashes (1 )), 0 );
1379+
1380+ // Revert batch 1 (blob hash is preserved)
1381+ bytes memory batchHeader1 = _batchHeader1Bytes (batchHash0);
1382+ hevm.prank (multisig);
1383+ rollup.revertBatch (batchHeader1, 1 );
1384+ assertEq (uint256 (rollup.committedBatches (1 )), 0 );
1385+ assertEq (uint256 (rollup.lastCommittedBatchIndex ()), 0 );
1386+ assertGt (uint256 (rollup.batchBlobVersionedHashes (1 )), 0 );
1387+
1388+ // Recommit with commitState (no blob in tx; uses stored blob hash)
1389+ batchDataInput = IRollup.BatchDataInput (0 , batchHeader0, 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1390+ hevm.prank (alice);
1391+ rollup.commitState (batchDataInput, batchSignatureInput);
1392+ assertEq (rollup.committedBatches (1 ), 0x25c3e4fee90e53de960c1092746c431ab570eacf8513011902fa65f10c814541 );
1393+ assertEq (uint256 (rollup.lastCommittedBatchIndex ()), 1 );
1394+ }
1395+
1396+ /// @dev commitState must be rejected when tx carries blob (blobhash(0) != 0).
1397+ /// Contract: require(blobhash(0) == bytes32(0), "commitState must not carry blob").
1398+ /// In test env we cannot send a blob tx (blobhash(0) is always 0), so we only assert that without blob commitState succeeds.
1399+ /// Full revert test requires a blob transaction.
1400+ function test_commitState_reverts_when_carrying_blob () public {
1401+ _setupCommitStatePrecondition ();
1402+ batchDataInput = IRollup.BatchDataInput (0 , _genesisBatchHeader (), 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1403+ hevm.prank (alice);
1404+ rollup.commitState (batchDataInput, batchSignatureInput);
1405+ // Without blob in tx, commitState succeeds. Revert "commitState must not carry blob" is hit only when blobhash(0) != 0 (blob tx).
1406+ assertEq (rollup.lastCommittedBatchIndex (), 1 );
1407+ }
1408+
1409+ /// @dev commitState reverts when there is no stored blob hash for the batch.
1410+ function test_commitState_reverts_when_no_stored_blob_hash () public {
1411+ bytes memory batchHeader0 = _genesisBatchHeader ();
1412+ hevm.prank (multisig);
1413+ rollup.importGenesisBatch (batchHeader0);
1414+ // Do NOT set stored blob hash for batch 1
1415+ batchDataInput = IRollup.BatchDataInput (0 , batchHeader0, 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1416+ hevm.prank (alice);
1417+ hevm.expectRevert ("no stored blob hash for this batch " );
1418+ rollup.commitState (batchDataInput, batchSignatureInput);
1419+ }
1420+
1421+ /// @dev commitState can only be called by an active staker.
1422+ function test_commitState_only_active_staker () public {
1423+ _setupCommitStatePrecondition ();
1424+ batchDataInput = IRollup.BatchDataInput (0 , _genesisBatchHeader (), 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1425+ hevm.prank (address (1 ));
1426+ hevm.expectRevert ("only active staker allowed " );
1427+ rollup.commitState (batchDataInput, batchSignatureInput);
1428+ }
1429+
1430+ /// @dev Mutual exclusion: (1) commitBatch reverts when stored blob hash exists; (2) commitState reverts when no stored hash (see test_commitState_reverts_when_no_stored_blob_hash).
1431+ function test_commitState_commitBatch_mutual_exclusion () public {
1432+ bytes memory batchHeader0 = _genesisBatchHeader ();
1433+ hevm.prank (multisig);
1434+ rollup.importGenesisBatch (batchHeader0);
1435+
1436+ // When there IS stored blob hash for batch 1, commitBatch must revert
1437+ _setStoredBlobHash (1 );
1438+ batchDataInput = IRollup.BatchDataInput (0 , batchHeader0, 1 , 0 , stateRoot, stateRoot, bytes32 (uint256 (4 )));
1439+ hevm.prank (alice);
1440+ hevm.expectRevert ("commitBatch requires no stored blob hash " );
1441+ rollup.commitBatch (batchDataInput, batchSignatureInput);
1442+ }
1443+ }
0 commit comments