diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index a14bd8945473..dc3c16565bca 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -37,7 +37,6 @@ import { Slot, Epoch, Timestamp, - Errors, CommitteeAttestations, RollupOperationsExtLib, ValidatorOperationsExtLib, @@ -321,19 +320,9 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { } function getBlock(uint256 _blockNumber) external view override(IRollup) returns (BlockLog memory) { - RollupStore storage rollupStore = STFLib.getStorage(); - uint256 pendingBlockNumber = rollupStore.tips.getPendingBlockNumber(); - require(_blockNumber <= pendingBlockNumber, Errors.Rollup__InvalidBlockNumber(pendingBlockNumber, _blockNumber)); - - // If the block is outside of the temp stored, will return default values (0) - // for all that would have been in temp. - TempBlockLog memory tempBlockLog; - if (!STFLib.isTempStale(_blockNumber)) { - tempBlockLog = STFLib.getTempBlockLog(_blockNumber); - } - + TempBlockLog memory tempBlockLog = STFLib.getTempBlockLog(_blockNumber); return BlockLog({ - archive: rollupStore.archives[_blockNumber], + archive: STFLib.getStorage().archives[_blockNumber], headerHash: tempBlockLog.headerHash, blobCommitmentsHash: tempBlockLog.blobCommitmentsHash, attestationsHash: tempBlockLog.attestationsHash, diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index dc6f9618c8c5..a9b5ba415035 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -48,7 +48,9 @@ library Errors { error Outbox__AlreadyNullified(uint256 l2BlockNumber, uint256 leafIndex); // 0xfd71c2d4 error Outbox__NothingToConsumeAtBlock(uint256 l2BlockNumber); // 0xa4508f22 error Outbox__BlockNotProven(uint256 l2BlockNumber); // 0x0e194a6d + error Outbox__BlockAlreadyProven(uint256 l2BlockNumber); error Outbox__PathTooLong(); + error Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength); // Rollup error Rollup__InsufficientBondAmount(uint256 minimum, uint256 provided); // 0xa165f276 @@ -62,6 +64,7 @@ library Errors { error Rollup__InvalidTimestamp(Timestamp expected, Timestamp actual); // 0x3132e895 error Rollup__InvalidAttestations(); error Rollup__AttestationsAreValid(); + error Rollup__InvalidAttestationIndex(); error Rollup__BlockAlreadyProven(); error Rollup__BlockNotInPendingChain(); error Rollup__InvalidBlobHash(bytes32 expected, bytes32 actual); // 0x13031e6a @@ -81,7 +84,6 @@ library Errors { error Rollup__StartIsNotFirstBlockOfEpoch(); // 0x4ef11e0d error Rollup__StartIsNotBuildingOnProven(); // 0x4a59f42e error Rollup__TooManyBlocksInEpoch(uint256 expected, uint256 actual); // 0x7d5b1408 - error Rollup__AlreadyClaimed(address prover, Epoch epoch); error Rollup__NotPastDeadline(Epoch deadline, Epoch currentEpoch); error Rollup__PastDeadline(Epoch deadline, Epoch currentEpoch); error Rollup__ProverHaveAlreadySubmitted(address prover, Epoch epoch); @@ -90,7 +92,7 @@ library Errors { error Rollup__RewardsNotClaimable(); error Rollup__InvalidFirstEpochProof(); error Rollup__InvalidCoinbase(); - error Rollup__StaleTempBlockLog(uint256 blockNumber, uint256 pendingBlockNumber, uint256 size); + error Rollup__UnavailableTempBlockLog(uint256 blockNumber, uint256 pendingBlockNumber, uint256 upperLimit); error Rollup__NoBlobsInBlock(); // ProposedHeaderLib diff --git a/l1-contracts/src/core/libraries/compressed-data/Tips.sol b/l1-contracts/src/core/libraries/compressed-data/Tips.sol index 44075ca3c494..2e47f3efc729 100644 --- a/l1-contracts/src/core/libraries/compressed-data/Tips.sol +++ b/l1-contracts/src/core/libraries/compressed-data/Tips.sol @@ -32,7 +32,7 @@ library ChainTipsLib { returns (CompressedChainTips) { uint256 value = CompressedChainTips.unwrap(_compressedChainTips) & ~PENDING_BLOCK_NUMBER_MASK; - return CompressedChainTips.wrap(value | (_pendingBlockNumber << 128)); + return CompressedChainTips.wrap(value | (uint256(_pendingBlockNumber.toUint128()) << 128)); } function updateProvenBlockNumber(CompressedChainTips _compressedChainTips, uint256 _provenBlockNumber) @@ -41,7 +41,7 @@ library ChainTipsLib { returns (CompressedChainTips) { uint256 value = CompressedChainTips.unwrap(_compressedChainTips) & ~PROVEN_BLOCK_NUMBER_MASK; - return CompressedChainTips.wrap(value | _provenBlockNumber); + return CompressedChainTips.wrap(value | _provenBlockNumber.toUint128()); } function compress(ChainTips memory _chainTips) internal pure returns (CompressedChainTips) { diff --git a/l1-contracts/src/core/libraries/crypto/SampleLib.sol b/l1-contracts/src/core/libraries/crypto/SampleLib.sol index f0121267cabd..108ea653b45f 100644 --- a/l1-contracts/src/core/libraries/crypto/SampleLib.sol +++ b/l1-contracts/src/core/libraries/crypto/SampleLib.sol @@ -98,8 +98,8 @@ library SampleLib { * @return shuffledIndex - The shuffled index */ function computeSampleIndex(uint256 _index, uint256 _indexCount, uint256 _seed) internal pure returns (uint256) { - // Cannot modulo by 0 - if (_indexCount == 0) { + // Cannot modulo by 0 and if 1, then only acceptable value is 0 + if (_indexCount <= 1) { return 0; } diff --git a/l1-contracts/src/core/libraries/rollup/InvalidateLib.sol b/l1-contracts/src/core/libraries/rollup/InvalidateLib.sol index 3e7e08573dfb..0d2bac751633 100644 --- a/l1-contracts/src/core/libraries/rollup/InvalidateLib.sol +++ b/l1-contracts/src/core/libraries/rollup/InvalidateLib.sol @@ -81,6 +81,7 @@ library InvalidateLib { * @custom:reverts Errors.Rollup__BlockAlreadyProven If block number is already proven * @custom:reverts Errors.Rollup__InvalidAttestations If provided attestations don't match stored hash * @custom:reverts Errors.ValidatorSelection__InvalidCommitteeCommitment If committee doesn't match stored commitment + * @custom:reverts Rollup__InvalidAttestationIndex if the _invalidIndex is beyond the committee * @custom:reverts Errors.Rollup__AttestationsAreValid If the attestation at invalidIndex is actually valid */ function invalidateBadAttestation( @@ -89,7 +90,8 @@ library InvalidateLib { address[] memory _committee, uint256 _invalidIndex ) internal { - (bytes32 digest,) = _validateInvalidationInputs(_blockNumber, _attestations, _committee); + (bytes32 digest, uint256 committeeSize) = _validateInvalidationInputs(_blockNumber, _attestations, _committee); + require(_invalidIndex < committeeSize, Errors.Rollup__InvalidAttestationIndex()); address recovered; diff --git a/l1-contracts/src/core/libraries/rollup/RewardLib.sol b/l1-contracts/src/core/libraries/rollup/RewardLib.sol index 2cbd25d5efb3..946ccce24ad4 100644 --- a/l1-contracts/src/core/libraries/rollup/RewardLib.sol +++ b/l1-contracts/src/core/libraries/rollup/RewardLib.sol @@ -109,10 +109,9 @@ library RewardLib { Errors.Rollup__NotPastDeadline(_epochs[i].toDeadlineEpoch(), currentEpoch) ); - require( - !rewardStorage.proverClaimed[_prover].get(Epoch.unwrap(_epochs[i])), - Errors.Rollup__AlreadyClaimed(_prover, _epochs[i]) - ); + if (rewardStorage.proverClaimed[_prover].get(Epoch.unwrap(_epochs[i]))) { + continue; + } rewardStorage.proverClaimed[_prover].set(Epoch.unwrap(_epochs[i])); EpochRewards storage e = rewardStorage.epochRewards[_epochs[i]]; @@ -123,7 +122,9 @@ library RewardLib { } } - rollupStore.config.feeAsset.safeTransfer(_prover, accumulatedRewards); + if (accumulatedRewards > 0) { + rollupStore.config.feeAsset.safeTransfer(_prover, accumulatedRewards); + } return accumulatedRewards; } diff --git a/l1-contracts/src/core/libraries/rollup/STFLib.sol b/l1-contracts/src/core/libraries/rollup/STFLib.sol index ea8d71c4601f..20704c5f40b9 100644 --- a/l1-contracts/src/core/libraries/rollup/STFLib.sol +++ b/l1-contracts/src/core/libraries/rollup/STFLib.sol @@ -179,7 +179,11 @@ library STFLib { } /** - * @notice Determines if a temporary block log is stale (no longer accessible in circular storage) + * @notice Returns a storage reference to a compressed temporary block log + * @dev Provides direct access to the compressed block log in storage without decompression. + * Reverts if the block number is stale (no longer accessible in circular storage) or if + * the block have not happened yet. + * * @dev A temporary block log is stale if it can no longer be accessed in the circular storage buffer. * The staleness is determined by the relationship between the block number, current pending * block, and the buffer size. @@ -187,28 +191,20 @@ library STFLib { * Example with roundabout size 5 and pending block 7: * Circular buffer state: [block5, block6, block7, block3, block4] * - * Block 2 and below are stale because: - * - blockNumber + size <= pending - * - 2 + 5 <= 7 (stale) - * - 3 + 5 <= 7 (not stale) + * A block is available if: + * - blockNumber <= pending (it is not in the future) + * - pending < blockNumber + size (the override is in the future) + * Together as a span: + * - blockNumber <= pending < blockNumber + size + * + * For example, block 2 is unavailable since the override has happened: + * - 2 <= 7 (true) && 7 < 2 + 5 (false) + * But block 3 is available as it in the past, but not overridden yet + * - 3 <= 7 (true) && 7 < 3 + 5 (true) * * This ensures that only blocks within the current "window" of the circular buffer * are considered valid and accessible. * - * @param _blockNumber The block number to check for staleness - * @return isStale True if the block is stale and no longer accessible - */ - function isTempStale(uint256 _blockNumber) internal view returns (bool) { - uint256 pending = getStorage().tips.getPendingBlockNumber(); - uint256 size = roundaboutSize(); - - return _blockNumber + size <= pending; - } - - /** - * @notice Returns a storage reference to a compressed temporary block log - * @dev Provides direct access to the compressed block log in storage without decompression. - * Reverts if the block number is stale. * @param _blockNumber The block number to get the storage reference for * @return A storage reference to the compressed temporary block log */ @@ -216,9 +212,9 @@ library STFLib { uint256 pending = getStorage().tips.getPendingBlockNumber(); uint256 size = roundaboutSize(); - bool isStale = _blockNumber + size <= pending; - - require(!isStale, Errors.Rollup__StaleTempBlockLog(_blockNumber, pending, size)); + uint256 upperLimit = _blockNumber + size; + bool available = _blockNumber <= pending && pending < upperLimit; + require(available, Errors.Rollup__UnavailableTempBlockLog(_blockNumber, pending, upperLimit)); return getStorage().tempBlockLogs[_blockNumber % size]; } diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol index 6ebe853fc366..ac3c5a4c4b55 100644 --- a/l1-contracts/src/core/messagebridge/Outbox.sol +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -46,6 +46,7 @@ contract Outbox is IOutbox { */ function insert(uint256 _l2BlockNumber, bytes32 _root) external override(IOutbox) { require(msg.sender == address(ROLLUP), Errors.Outbox__Unauthorized()); + require(_l2BlockNumber > ROLLUP.getProvenBlockNumber(), Errors.Outbox__BlockAlreadyProven(_l2BlockNumber)); roots[_l2BlockNumber].root = _root; @@ -72,6 +73,7 @@ contract Outbox is IOutbox { bytes32[] calldata _path ) external override(IOutbox) { require(_path.length < 256, Errors.Outbox__PathTooLong()); + require(_leafIndex < (1 << _path.length), Errors.Outbox__LeafIndexOutOfBounds(_leafIndex, _path.length)); require(_l2BlockNumber <= ROLLUP.getProvenBlockNumber(), Errors.Outbox__BlockNotProven(_l2BlockNumber)); require(_message.sender.version == VERSION, Errors.Outbox__VersionMismatch(_message.sender.version, VERSION)); diff --git a/l1-contracts/src/governance/GSE.sol b/l1-contracts/src/governance/GSE.sol index 32a1a1020423..7cab9f995712 100644 --- a/l1-contracts/src/governance/GSE.sol +++ b/l1-contracts/src/governance/GSE.sol @@ -196,7 +196,7 @@ contract GSECore is IGSECore, Ownable { // Global attester information mapping(address attester => AttesterConfig config) internal configOf; // Mapping from the hashed public key in G1 of BN254 to the keys are registered. - mapping(bytes32 hashedPK1 => bool isRegistered) internal ownedPKs; + mapping(bytes32 hashedPK1 => bool isRegistered) public ownedPKs; /** * Contains state for: diff --git a/l1-contracts/test/MultiProof.t.sol b/l1-contracts/test/MultiProof.t.sol index 0e082291c842..1e67124524a1 100644 --- a/l1-contracts/test/MultiProof.t.sol +++ b/l1-contracts/test/MultiProof.t.sol @@ -188,9 +188,12 @@ contract MultiProofTest is RollupBase { assertEq(bobRewardsClaimed, bobRewards, "Bob rewards not claimed"); assertEq(rollup.getSpecificProverRewardsForEpoch(Epoch.wrap(0), bob), 0, "Bob rewards not zeroed"); + vm.record(); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__AlreadyClaimed.selector, bob, Epoch.wrap(0))); rollup.claimProverRewards(bob, epochs); + (, bytes32[] memory writes) = vm.accesses(address(rollup)); + // Ensure that there was no writes! We are just doing no-ops if they were already claimed. + assertEq(writes.length, 0); } } diff --git a/l1-contracts/test/Outbox.t.sol b/l1-contracts/test/Outbox.t.sol index 12fed33c445d..25f60a17d675 100644 --- a/l1-contracts/test/Outbox.t.sol +++ b/l1-contracts/test/Outbox.t.sol @@ -33,7 +33,6 @@ contract OutboxTest is Test { function setUp() public { ROLLUP_CONTRACT = address(new FakeRollup()); - FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); outbox = new Outbox(ROLLUP_CONTRACT, AZTEC_VERSION); zeroedTree = new NaiveMerkle(DEFAULT_TREE_HEIGHT); @@ -60,11 +59,12 @@ contract OutboxTest is Test { outbox.insert(1, root); } - function testRevertIfInsertingDuplicate() public { + function testRevertIfInsertingBlockAlreadyProven() public { bytes32 root = zeroedTree.computeRoot(); vm.prank(ROLLUP_CONTRACT); - outbox.insert(1, root); + vm.expectRevert(abi.encodeWithSelector(Errors.Outbox__BlockAlreadyProven.selector, 0)); + outbox.insert(0, root); } function testRevertIfPathTooLong() public { @@ -74,6 +74,14 @@ contract OutboxTest is Test { outbox.consume(fakeMessage, 1, 0, path); } + function testRevertIfLeafIndexOutOfBounds(uint256 _leafIndex) public { + DataStructures.L2ToL1Msg memory fakeMessage = _fakeMessage(address(this), 123); + bytes32[] memory path = new bytes32[](4); + uint256 leafIndex = bound(_leafIndex, 1 << path.length, type(uint256).max); + vm.expectRevert(abi.encodeWithSelector(Errors.Outbox__LeafIndexOutOfBounds.selector, leafIndex, path.length)); + outbox.consume(fakeMessage, 1, leafIndex, path); + } + // This function tests the insertion of random arrays of L2 to L1 messages // We make a naive tree with a computed height, insert the leafs into it, and compute a root. We then add the root as // the root of the @@ -96,11 +104,14 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(1, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + bytes32 actualRoot = outbox.getRootData(1); assertEq(root, actualRoot); } function testRevertIfConsumingMessageBelongingToOther() public { + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); DataStructures.L2ToL1Msg memory fakeMessage = _fakeMessage(address(this), 123); (bytes32[] memory path,) = zeroedTree.computeSiblingPath(0); @@ -111,6 +122,7 @@ contract OutboxTest is Test { } function testRevertIfConsumingMessageWithInvalidChainId() public { + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); DataStructures.L2ToL1Msg memory fakeMessage = _fakeMessage(address(this), 123); (bytes32[] memory path,) = zeroedTree.computeSiblingPath(0); @@ -122,6 +134,8 @@ contract OutboxTest is Test { } function testRevertIfVersionMismatch() public { + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + DataStructures.L2ToL1Msg memory message = _fakeMessage(address(this), 123); (bytes32[] memory path,) = zeroedTree.computeSiblingPath(0); @@ -133,6 +147,7 @@ contract OutboxTest is Test { } function testRevertIfNothingInsertedAtBlockNumber() public { + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); uint256 blockNumber = 1; DataStructures.L2ToL1Msg memory fakeMessage = _fakeMessage(address(this), 123); @@ -153,6 +168,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(1, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + uint256 leafIndex = 0; uint256 leafId = 2 ** DEFAULT_TREE_HEIGHT + leafIndex; (bytes32[] memory path,) = tree.computeSiblingPath(leafIndex); @@ -173,6 +190,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(1, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + NaiveMerkle smallerTree = new NaiveMerkle(DEFAULT_TREE_HEIGHT - 1); smallerTree.insertLeaf(leaf); bytes32 smallerTreeRoot = smallerTree.computeRoot(); @@ -199,6 +218,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(1, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + (bytes32[] memory path,) = modifiedTree.computeSiblingPath(0); vm.expectRevert(abi.encodeWithSelector(Errors.MerkleLib__InvalidRoot.selector, root, modifiedRoot, modifiedLeaf, 0)); @@ -235,6 +256,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(1, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + uint256 leafIndex = 0; uint256 leafId = 2 ** DEFAULT_TREE_HEIGHT + leafIndex; (bytes32[] memory path,) = tree.computeSiblingPath(leafIndex); @@ -258,7 +281,6 @@ contract OutboxTest is Test { uint8 _size ) public { uint256 blockNumber = bound(_blockNumber, 1, 256); - FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(blockNumber); uint256 numberOfMessages = bound(_size, 1, _recipients.length); DataStructures.L2ToL1Msg[] memory messages = new DataStructures.L2ToL1Msg[](numberOfMessages); @@ -280,6 +302,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(blockNumber, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(blockNumber); + for (uint256 i = 0; i < numberOfMessages; i++) { (bytes32[] memory path, bytes32 leaf) = tree.computeSiblingPath(i); uint256 leafId = 2 ** treeHeight + i; @@ -304,6 +328,8 @@ contract OutboxTest is Test { outbox.insert(2, root); vm.stopPrank(); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + { bytes32 actualRoot = outbox.getRootData(1); assertEq(root, actualRoot); @@ -325,6 +351,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(1, leaf); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(1); + uint256 leafIndex = 0; uint256 leafId = 1; @@ -377,6 +405,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(blockNumber, root); + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(blockNumber); + // Consume the message of tx0. { uint256 msgIndex = 0; @@ -502,6 +532,8 @@ contract OutboxTest is Test { vm.prank(ROLLUP_CONTRACT); outbox.insert(blockNumber, root); + + FakeRollup(ROLLUP_CONTRACT).setProvenBlockNum(blockNumber); } // Consume messages[0] in tx0. diff --git a/l1-contracts/test/compression/Tips.t.sol b/l1-contracts/test/compression/Tips.t.sol index 606f1b4c3143..d0f2ed64c1ee 100644 --- a/l1-contracts/test/compression/Tips.t.sol +++ b/l1-contracts/test/compression/Tips.t.sol @@ -4,11 +4,34 @@ pragma solidity >=0.8.27; import {Test} from "forge-std/Test.sol"; import {ChainTips, CompressedChainTips, ChainTipsLib} from "@aztec/core/libraries/compressed-data/Tips.sol"; +import {SafeCast} from "@oz/utils/math/SafeCast.sol"; + +contract TipsWrapper { + using ChainTipsLib for CompressedChainTips; + + function updatePendingBlockNumber(CompressedChainTips _compressedChainTips, uint256 _pendingBlockNumber) + public + pure + returns (CompressedChainTips) + { + return _compressedChainTips.updatePendingBlockNumber(_pendingBlockNumber); + } + + function updateProvenBlockNumber(CompressedChainTips _compressedChainTips, uint256 _provenBlockNumber) + public + pure + returns (CompressedChainTips) + { + return _compressedChainTips.updateProvenBlockNumber(_provenBlockNumber); + } +} contract TipsTest is Test { using ChainTipsLib for CompressedChainTips; using ChainTipsLib for ChainTips; + TipsWrapper public tipsWrapper = new TipsWrapper(); + function test_compress_uncompress(uint128 _pendingBlockNumber, uint128 _provenBlockNumber) public pure { ChainTips memory chainTips = ChainTips({pendingBlockNumber: _pendingBlockNumber, provenBlockNumber: _provenBlockNumber}); @@ -37,6 +60,15 @@ contract TipsTest is Test { ); } + function test_updatePendingBlockNumberOversized(uint256 _pendingBlockNumber, uint128 _provenBlockNumber) public { + ChainTips memory a = ChainTips({pendingBlockNumber: 0, provenBlockNumber: _provenBlockNumber}); + uint256 pendingBlockNumber = bound(_pendingBlockNumber, uint256(type(uint128).max) + 1, type(uint256).max); + + CompressedChainTips b = a.compress(); + vm.expectRevert(abi.encodeWithSelector(SafeCast.SafeCastOverflowedUintDowncast.selector, 128, pendingBlockNumber)); + tipsWrapper.updatePendingBlockNumber(b, pendingBlockNumber); + } + function test_updateProvenBlockNumber(uint128 _pendingBlockNumber, uint128 _provenBlockNumber) public pure { uint256 provenBlockNumber = bound(_provenBlockNumber, 0, type(uint128).max - 1); ChainTips memory a = ChainTips({pendingBlockNumber: _pendingBlockNumber, provenBlockNumber: provenBlockNumber}); @@ -50,4 +82,13 @@ contract TipsTest is Test { c.getProvenBlockNumber(), b.getProvenBlockNumber() + 1, "c.getProvenBlockNumber != b.getProvenBlockNumber + 1" ); } + + function test_updateProvenBlockNumberOversized(uint128 _pendingBlockNumber, uint256 _provenBlockNumber) public { + ChainTips memory a = ChainTips({pendingBlockNumber: _pendingBlockNumber, provenBlockNumber: 0}); + uint256 provenBlockNumber = bound(_provenBlockNumber, uint256(type(uint128).max) + 1, type(uint256).max); + + CompressedChainTips b = a.compress(); + vm.expectRevert(abi.encodeWithSelector(SafeCast.SafeCastOverflowedUintDowncast.selector, 128, provenBlockNumber)); + tipsWrapper.updateProvenBlockNumber(b, provenBlockNumber); + } } diff --git a/l1-contracts/test/outbox/tmnt205.t.sol b/l1-contracts/test/outbox/tmnt205.t.sol index b2c7de6f8cb7..e9041d3c78b2 100644 --- a/l1-contracts/test/outbox/tmnt205.t.sol +++ b/l1-contracts/test/outbox/tmnt205.t.sol @@ -9,6 +9,7 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; +import {MerkleLibHelper} from "../merkle/helpers/MerkleLibHelper.sol"; contract FakeRollup { uint256 public getProvenBlockNumber = 0; @@ -28,7 +29,7 @@ contract Tmnt205Test is Test { FakeRollup internal rollup; Outbox internal outbox; - + MerkleLibHelper internal merkleLibHelper = new MerkleLibHelper(); DataStructures.L2ToL1Msg[] internal $msgs; bytes32[] internal $txOutHashes; @@ -92,7 +93,15 @@ contract Tmnt205Test is Test { bytes32[] memory a_path = new bytes32[](2); a_path[0] = $txOutHashes[1]; a_path[1] = $txOutHashes[2]; + + bytes32 messageHash = a_message.sha256ToField(); + + // The merkle lib itself should throw as index is out of bounds. vm.expectRevert(Errors.MerkleLib__InvalidIndexForPathLength.selector); + merkleLibHelper.verifyMembership(a_path, messageHash, a_leafIndex, $root); + + // The outbox should revert earlier to that due to the index beyond boundary + vm.expectRevert(abi.encodeWithSelector(Errors.Outbox__LeafIndexOutOfBounds.selector, a_leafIndex, a_path.length)); outbox.consume(a_message, BLOCK_NUMBER, a_leafIndex, a_path); // Real message diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index 565971e508d4..1ce0865f5550 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -76,10 +76,6 @@ contract TokenPortalTest is Test { tokenPortal = new TokenPortal(); tokenPortal.initialize(address(registry), address(testERC20), l2TokenAddress); - // Modify the proven block count - stdstore.enable_packed_slots().target(address(rollup)).sig("getProvenBlockNumber()").checked_write(l2BlockNumber); - assertEq(rollup.getProvenBlockNumber(), l2BlockNumber); - vm.deal(address(this), 100 ether); } @@ -211,6 +207,10 @@ contract TokenPortalTest is Test { vm.prank(address(rollup)); outbox.insert(_l2BlockNumber, treeRoot); + // Modify the proven block count + stdstore.enable_packed_slots().target(address(rollup)).sig("getProvenBlockNumber()").checked_write(l2BlockNumber); + assertEq(rollup.getProvenBlockNumber(), l2BlockNumber); + return (l2ToL1Message, siblingPath, treeRoot); } diff --git a/l1-contracts/test/tmnt419.t.sol b/l1-contracts/test/tmnt419.t.sol new file mode 100644 index 000000000000..163311e13d6e --- /dev/null +++ b/l1-contracts/test/tmnt419.t.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {DecoderBase} from "./base/DecoderBase.sol"; + +import {Registry} from "@aztec/governance/Registry.sol"; +import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {TestConstants} from "./harnesses/TestConstants.sol"; +import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; +import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol"; + +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; + +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {ProposeArgs, ProposePayload, OracleInput, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol"; + +import {RollupBase, IInstance} from "./base/RollupBase.sol"; +import {RollupBuilder} from "./builder/RollupBuilder.sol"; +import {TimeCheater} from "./staking/TimeCheater.sol"; +import {Bps, BpsLib} from "@aztec/core/libraries/rollup/RewardLib.sol"; +import { + AttestationLib, + Signature, + CommitteeAttestation, + CommitteeAttestations +} from "@aztec/core/libraries/rollup/AttestationLib.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol"; +import {SafeCast} from "@oz/utils/math/SafeCast.sol"; +import {AttestationLibHelper} from "@test/helper_libraries/AttestationLibHelper.sol"; +import {Ownable} from "@oz/access/Ownable.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {Signature} from "@aztec/shared/libraries/SignatureLib.sol"; +import {BlockLog} from "@aztec/core/libraries/compressed-data/BlockLog.sol"; +import {stdStorage, StdStorage} from "forge-std/StdStorage.sol"; +// solhint-disable comprehensive-interface + +struct Block { + ProposeArgs proposeArgs; + bytes blobInputs; + CommitteeAttestation[] attestations; + address[] signers; + Signature attestationsAndSignersSignature; +} + +contract Tmnt419Test is RollupBase { + using ProposeLib for ProposeArgs; + using TimeLib for Timestamp; + using TimeLib for Slot; + using TimeLib for Epoch; + using stdStorage for StdStorage; + + Registry internal registry; + TestERC20 internal testERC20; + FeeJuicePortal internal feeJuicePortal; + RewardDistributor internal rewardDistributor; + TimeCheater internal timeCheater; + + uint256 internal SLOT_DURATION; + uint256 internal EPOCH_DURATION; + uint256 internal PROOF_SUBMISSION_EPOCHS; + uint256 internal MANA_TARGET = 0; + + address internal sequencer = address(bytes20("sequencer")); + + DecoderBase.Full internal full; + + /** + * @notice Set up the contracts needed for the tests with time aligned to the provided block name + */ + modifier setUpFor(string memory _name) { + { + full = load(_name); + Slot slotNumber = full.block.header.slotNumber; + uint256 initialTime = Timestamp.unwrap(full.block.header.timestamp) - Slot.unwrap(slotNumber) * SLOT_DURATION; + vm.warp(initialTime); + } + + TimeLib.initialize( + block.timestamp, + TestConstants.AZTEC_SLOT_DURATION, + TestConstants.AZTEC_EPOCH_DURATION, + TestConstants.AZTEC_PROOF_SUBMISSION_EPOCHS + ); + SLOT_DURATION = TestConstants.AZTEC_SLOT_DURATION; + EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION; + PROOF_SUBMISSION_EPOCHS = TestConstants.AZTEC_PROOF_SUBMISSION_EPOCHS; + timeCheater = + new TimeCheater(address(this), block.timestamp, SLOT_DURATION, EPOCH_DURATION, PROOF_SUBMISSION_EPOCHS); + + RollupBuilder builder = new RollupBuilder(address(this)).setManaTarget(MANA_TARGET).setTargetCommitteeSize(0); + builder.deploy(); + + rollup = IInstance(address(builder.getConfig().rollup)); + testERC20 = builder.getConfig().testERC20; + registry = builder.getConfig().registry; + + feeJuicePortal = FeeJuicePortal(address(rollup.getFeeAssetPortal())); + rewardDistributor = RewardDistributor(address(registry.getRewardDistributor())); + + _; + } + + function test_getStorageTempBlockLog() public setUpFor("empty_block_1") { + skipBlobCheck(address(rollup)); + timeCheater.cheat__progressSlot(); + + for (uint256 i = 0; i < 100; i++) { + Block memory l2Block = getBlock(); + rollup.propose( + l2Block.proposeArgs, + AttestationLibHelper.packAttestations(l2Block.attestations), + l2Block.signers, + l2Block.attestationsAndSignersSignature, + l2Block.blobInputs + ); + timeCheater.cheat__progressSlot(); + + stdstore.enable_packed_slots().target(address(rollup)).sig("getProvenBlockNumber()").checked_write( + rollup.getPendingBlockNumber() + ); + } + + assertEq(rollup.getProvenBlockNumber(), 100); + + // Read something so old that it should be stale + vm.expectRevert( + abi.encodeWithSelector( + Errors.Rollup__UnavailableTempBlockLog.selector, 1, 100, 1 + 1 + TestConstants.AZTEC_EPOCH_DURATION * 2 + ) + ); + rollup.getBlock(1); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.Rollup__UnavailableTempBlockLog.selector, 100 - (1 + TestConstants.AZTEC_EPOCH_DURATION * 2), 100, 100 + ) + ); + rollup.getBlock(100 - (1 + TestConstants.AZTEC_EPOCH_DURATION * 2)); + + // Read something current + rollup.getBlock(rollup.getPendingBlockNumber()); + + // Try to read into the future see a failure + vm.expectRevert( + abi.encodeWithSelector( + Errors.Rollup__UnavailableTempBlockLog.selector, 101, 100, 101 + 1 + TestConstants.AZTEC_EPOCH_DURATION * 2 + ) + ); + rollup.getBlock(101); + } + + function getBlock() internal view returns (Block memory) { + // We will be using the genesis for both before and after. This will be impossible + // to prove, but we don't need to prove anything here. + bytes32 archiveRoot = bytes32(Constants.GENESIS_ARCHIVE_ROOT); + + ProposedHeader memory header = full.block.header; + + Slot slotNumber = rollup.getCurrentSlot(); + Timestamp ts = rollup.getTimestampForSlot(slotNumber); + + // Updating the header with important information! + header.lastArchiveRoot = archiveRoot; + header.slotNumber = slotNumber; + header.timestamp = ts; + header.coinbase = address(bytes20("coinbase")); + header.feeRecipient = bytes32(0); + header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true)); + if (MANA_TARGET > 0) { + header.totalManaUsed = MANA_TARGET; + } else { + header.totalManaUsed = 0; + } + + ProposeArgs memory proposeArgs = ProposeArgs({ + header: header, + archive: archiveRoot, + stateReference: EMPTY_STATE_REFERENCE, + oracleInput: OracleInput({feeAssetPriceModifier: 0}) + }); + + CommitteeAttestation[] memory attestations = new CommitteeAttestation[](0); + address[] memory signers = new address[](0); + + return Block({ + proposeArgs: proposeArgs, + blobInputs: full.block.blobCommitments, + attestations: attestations, + signers: signers, + attestationsAndSignersSignature: Signature({v: 0, r: 0, s: 0}) + }); + } +} diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index d62027a4d852..7cb04e6344d3 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -364,6 +364,12 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { ); } + function testInvalidAttestationIndex(uint256 _invalidIndex) public setup(4, 4) progressEpochs(2) { + ProposeTestData memory ree = _testBlock("mixed_block_1", NO_REVERT, 3, 4, TestFlagsLib.empty()); + uint256 invalidIndex = bound(_invalidIndex, ree.committee.length, type(uint256).max); + _invalidateByAttestationSig(ree, invalidIndex, Errors.Rollup__InvalidAttestationIndex.selector); + } + function testInvalidAttestationSigner() public setup(4, 4) progressEpochs(2) { ProposeTestData memory ree = _testBlock("mixed_block_1", NO_REVERT, 3, 4, TestFlagsLib.empty().invalidateAttestationSigner());