diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 4026433b84dd..d934fea24a13 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -91,6 +91,7 @@ library Errors { error Rollup__InvalidFirstEpochProof(); error Rollup__InvalidCoinbase(); error Rollup__StaleTempBlockLog(uint256 blockNumber, uint256 pendingBlockNumber, uint256 size); + error Rollup__NoBlobsInBlock(); // ProposedHeaderLib error HeaderLib__InvalidHeaderSize(uint256 expected, uint256 actual); // 0xf3ccb247 @@ -176,6 +177,8 @@ library Errors { // RewardBooster error RewardBooster__OnlyRollup(address caller); + error RewardLib__InvalidSequencerBps(); + // TallySlashingProposer error TallySlashingProposer__InvalidSignature(); error TallySlashingProposer__InvalidVoteLength(uint256 expected, uint256 actual); diff --git a/l1-contracts/src/core/libraries/crypto/MerkleLib.sol b/l1-contracts/src/core/libraries/crypto/MerkleLib.sol index 36dbd28f5816..035f1c3470ef 100644 --- a/l1-contracts/src/core/libraries/crypto/MerkleLib.sol +++ b/l1-contracts/src/core/libraries/crypto/MerkleLib.sol @@ -53,64 +53,4 @@ library MerkleLib { require(indexAtHeight == 0, Errors.MerkleLib__InvalidIndexForPathLength()); require(subtreeRoot == _expectedRoot, Errors.MerkleLib__InvalidRoot(_expectedRoot, subtreeRoot, _leaf, _index)); } - - /** - * @notice Computes the root for a binary Merkle-tree given the leafs. - * @dev Uses sha256. - * @param _leafs - The 32 bytes leafs to build the tree of. - * @return The root of the Merkle tree. - */ - function computeRoot(bytes32[] memory _leafs) internal pure returns (bytes32) { - // @todo Must pad the tree - uint256 treeDepth = 0; - while (2 ** treeDepth < _leafs.length) { - treeDepth++; - } - uint256 treeSize = 2 ** treeDepth; - assembly { - mstore(_leafs, treeSize) - } - - for (uint256 i = 0; i < treeDepth; i++) { - for (uint256 j = 0; j < treeSize; j += 2) { - _leafs[j / 2] = Hash.sha256ToField(bytes.concat(_leafs[j], _leafs[j + 1])); - } - treeSize /= 2; - } - - return _leafs[0]; - } - - /** - * @notice Computes the root for a binary unbalanced Merkle-tree given the leaves. - * @dev Filled in greedily with subtrees. Useful for outHash tree. - * @param _leaves - The 32 bytes leafs to build the tree of. - * @return The root of the Merkle tree. - */ - function computeUnbalancedRoot(bytes32[] memory _leaves) internal pure returns (bytes32) { - // e.g. an unbalanced tree of 7 txs will contain subtrees of 4, 2, and 1 tx(s) = 111 - // e.g. an unbalanced tree of 9 txs will contain subtrees of 8 and 1 tx(s) = 1001 - // We collect the roots of each subtree - bytes32 root; - uint256 currentSubtreeSize = 1; - uint256 numTxs = _leaves.length; - // We must calculate the smaller rightmost subtrees first, hence starting at 1 - while (numTxs != 0) { - // If size & txs == 0, the subtree doesn't exist for this number of txs - if (currentSubtreeSize & numTxs == 0) { - currentSubtreeSize <<= 1; - continue; - } - bytes32[] memory leavesInSubtree = new bytes32[](currentSubtreeSize); - uint256 start = numTxs - currentSubtreeSize; - for (uint256 i = start; i < numTxs; i++) { - leavesInSubtree[i - start] = _leaves[i]; - } - bytes32 subtreeRoot = computeRoot(leavesInSubtree); - root = numTxs == _leaves.length ? subtreeRoot : Hash.sha256ToField(bytes.concat(subtreeRoot, root)); - numTxs -= currentSubtreeSize; - currentSubtreeSize <<= 1; - } - return root; - } } diff --git a/l1-contracts/src/core/libraries/rollup/BlobLib.sol b/l1-contracts/src/core/libraries/rollup/BlobLib.sol index d49affbcdd9c..9f2e882f50e0 100644 --- a/l1-contracts/src/core/libraries/rollup/BlobLib.sol +++ b/l1-contracts/src/core/libraries/rollup/BlobLib.sol @@ -105,6 +105,7 @@ library BlobLib { // We cannot input the incorrect number of blobs below, as the blobsHash // and epoch proof verification will fail. uint8 numBlobs = uint8(_blobsInput[0]); + require(numBlobs > 0, Errors.Rollup__NoBlobsInBlock()); blobHashes = new bytes32[](numBlobs); blobCommitments = new bytes[](numBlobs); bytes32 blobHash; diff --git a/l1-contracts/src/core/libraries/rollup/RewardLib.sol b/l1-contracts/src/core/libraries/rollup/RewardLib.sol index 865fcbc76e19..2cbd25d5efb3 100644 --- a/l1-contracts/src/core/libraries/rollup/RewardLib.sol +++ b/l1-contracts/src/core/libraries/rollup/RewardLib.sol @@ -77,6 +77,7 @@ library RewardLib { address public constant BURN_ADDRESS = address(bytes20("CUAUHXICALLI")); function setConfig(RewardConfig memory _config) internal { + require(Bps.unwrap(_config.sequencerBps) <= 10_000, Errors.RewardLib__InvalidSequencerBps()); RewardStorage storage rewardStorage = getStorage(); rewardStorage.config = _config; } @@ -86,8 +87,11 @@ library RewardLib { RollupStore storage rollupStore = STFLib.getStorage(); uint256 amount = rewardStorage.sequencerRewards[_sequencer]; - rewardStorage.sequencerRewards[_sequencer] = 0; - rollupStore.config.feeAsset.transfer(_sequencer, amount); + + if (amount > 0) { + rewardStorage.sequencerRewards[_sequencer] = 0; + rollupStore.config.feeAsset.safeTransfer(_sequencer, amount); + } return amount; } @@ -119,7 +123,7 @@ library RewardLib { } } - rollupStore.config.feeAsset.transfer(_prover, accumulatedRewards); + rollupStore.config.feeAsset.safeTransfer(_prover, accumulatedRewards); return accumulatedRewards; } @@ -215,7 +219,7 @@ library RewardLib { } if (t.totalBurn > 0) { - rollupStore.config.feeAsset.transfer(BURN_ADDRESS, t.totalBurn); + rollupStore.config.feeAsset.safeTransfer(BURN_ADDRESS, t.totalBurn); } } } diff --git a/l1-contracts/src/core/libraries/rollup/StakingLib.sol b/l1-contracts/src/core/libraries/rollup/StakingLib.sol index 683f352be8d2..1dba6e2439a4 100644 --- a/l1-contracts/src/core/libraries/rollup/StakingLib.sol +++ b/l1-contracts/src/core/libraries/rollup/StakingLib.sol @@ -193,7 +193,7 @@ library StakingLib { delete store.exits[_attester]; store.gse.finalizeWithdraw(exit.withdrawalId); - store.stakingAsset.transfer(exit.recipientOrWithdrawer, exit.amount); + store.stakingAsset.safeTransfer(exit.recipientOrWithdrawer, exit.amount); emit IStakingCore.WithdrawFinalized(_attester, exit.recipientOrWithdrawer, exit.amount); } @@ -297,7 +297,7 @@ library StakingLib { require(!store.exits[_attester].exists, Errors.Staking__AlreadyExiting(_attester)); uint256 amount = store.gse.ACTIVATION_THRESHOLD(); - store.stakingAsset.transferFrom(msg.sender, address(this), amount); + store.stakingAsset.safeTransferFrom(msg.sender, address(this), amount); store.entryQueue.enqueue( _attester, _withdrawer, _publicKeyInG1, _publicKeyInG2, _proofOfPossession, _moveWithLatestRollup ); @@ -369,7 +369,7 @@ library StakingLib { // where someone could drain the queue without making any deposits. // We can safely assume data.length == 0 means out of gas since we only call trusted GSE contract. require(data.length > 0, Errors.Staking__DepositOutOfGas()); - store.stakingAsset.transfer(args.withdrawer, amount); + store.stakingAsset.safeTransfer(args.withdrawer, amount); emit IStakingCore.FailedDeposit( args.attester, args.withdrawer, args.publicKeyInG1, args.publicKeyInG2, args.proofOfPossession ); diff --git a/l1-contracts/src/governance/GSE.sol b/l1-contracts/src/governance/GSE.sol index 745b973f2401..f190c172e569 100644 --- a/l1-contracts/src/governance/GSE.sol +++ b/l1-contracts/src/governance/GSE.sol @@ -14,6 +14,7 @@ import {BN254Lib, G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol"; import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol"; import {Ownable} from "@oz/access/Ownable.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@oz/utils/math/SafeCast.sol"; import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; @@ -121,6 +122,7 @@ contract GSECore is IGSECore, Ownable { using SafeCast for uint224; using Checkpoints for Checkpoints.Trace224; using DepositDelegationLib for DepositAndDelegationAccounting; + using SafeERC20 for IERC20; /** * Create a special "bonus" address for use by the latest rollup. @@ -336,7 +338,7 @@ contract GSECore is IGSECore, Ownable { delegation.delegate(recipientInstance, _attester, recipientInstance); delegation.increaseBalance(recipientInstance, _attester, ACTIVATION_THRESHOLD); - ASSET.transferFrom(msg.sender, address(this), ACTIVATION_THRESHOLD); + ASSET.safeTransferFrom(msg.sender, address(this), ACTIVATION_THRESHOLD); Governance gov = getGovernance(); ASSET.approve(address(gov), ACTIVATION_THRESHOLD); @@ -469,7 +471,7 @@ contract GSECore is IGSECore, Ownable { Governance gov = getGovernance(); uint256 amount = gov.getConfiguration().proposeConfig.lockAmount; - ASSET.transferFrom(msg.sender, address(this), amount); + ASSET.safeTransferFrom(msg.sender, address(this), amount); ASSET.approve(address(gov), amount); gov.deposit(address(this), amount); diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index cdd251f85fac..65baf5829688 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -681,6 +681,7 @@ contract RollupTest is RollupBase { function testRevertInvalidTimestamp() public setUpFor("empty_block_1") { DecoderBase.Data memory data = load("empty_block_1").block; ProposedHeader memory header = data.header; + vm.blobhashes(this.getBlobHashes(data.blobCommitments)); bytes32 archive = data.archive; Timestamp realTs = header.timestamp; @@ -704,7 +705,7 @@ contract RollupTest is RollupBase { AttestationLibHelper.packAttestations(attestations), signers, attestationsAndSignersSignature, - new bytes(144) + data.blobCommitments ); } @@ -720,6 +721,8 @@ contract RollupTest is RollupBase { // Tweak the coinbase. header.coinbase = address(0); + bytes32[] memory blobHashes = this.getBlobHashes(data.blobCommitments); + vm.blobhashes(blobHashes); skipBlobCheck(address(rollup)); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidCoinbase.selector)); ProposeArgs memory args = ProposeArgs({ @@ -733,7 +736,7 @@ contract RollupTest is RollupBase { AttestationLibHelper.packAttestations(attestations), signers, attestationsAndSignersSignature, - new bytes(144) + data.blobCommitments ); } @@ -777,6 +780,33 @@ contract RollupTest is RollupBase { _submitEpochProof(1, 1, blockLog.archive, data.archive, blobProofInputs, address(0)); } + function testNoBlob() public setUpFor("empty_block_1") { + DecoderBase.Data memory data = load("empty_block_1").block; + ProposedHeader memory header = data.header; + bytes32 archive = data.archive; + + Timestamp realTs = header.timestamp; + + vm.warp(max(block.timestamp, Timestamp.unwrap(realTs))); + + header.gasFees.feePerL2Gas = uint128(rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true)); + + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoBlobsInBlock.selector)); + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + stateReference: EMPTY_STATE_REFERENCE, + oracleInput: OracleInput(0) + }); + rollup.propose( + args, + AttestationLibHelper.packAttestations(attestations), + signers, + attestationsAndSignersSignature, + new bytes(144) + ); + } + function testTooManyBlocks() public setUpFor("mixed_block_1") { _proposeBlock("mixed_block_1", 1); DecoderBase.Data memory data = load("mixed_block_1").block; diff --git a/l1-contracts/test/merkle/helpers/MerkleLibHelper.sol b/l1-contracts/test/merkle/helpers/MerkleLibHelper.sol index 54bcc774557f..c949ea382554 100644 --- a/l1-contracts/test/merkle/helpers/MerkleLibHelper.sol +++ b/l1-contracts/test/merkle/helpers/MerkleLibHelper.sol @@ -2,6 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {MerkleLib} from "@aztec/core/libraries/crypto/MerkleLib.sol"; // A wrapper used to be able to "call" library functions, instead of "jumping" to them, allowing forge to catch the @@ -55,7 +56,63 @@ contract MerkleLibHelper { return (min, max); } + /** + * @notice Computes the root for a binary unbalanced Merkle-tree given the leaves. + * @dev Filled in greedily with subtrees. Useful for outHash tree. + * @param _leaves - The 32 bytes leafs to build the tree of. + * @return The root of the Merkle tree. + */ function computeUnbalancedRoot(bytes32[] memory _leaves) external pure returns (bytes32) { - return MerkleLib.computeUnbalancedRoot(_leaves); + // e.g. an unbalanced tree of 7 txs will contain subtrees of 4, 2, and 1 tx(s) = 111 + // e.g. an unbalanced tree of 9 txs will contain subtrees of 8 and 1 tx(s) = 1001 + // We collect the roots of each subtree + bytes32 root; + uint256 currentSubtreeSize = 1; + uint256 numTxs = _leaves.length; + // We must calculate the smaller rightmost subtrees first, hence starting at 1 + while (numTxs != 0) { + // If size & txs == 0, the subtree doesn't exist for this number of txs + if (currentSubtreeSize & numTxs == 0) { + currentSubtreeSize <<= 1; + continue; + } + bytes32[] memory leavesInSubtree = new bytes32[](currentSubtreeSize); + uint256 start = numTxs - currentSubtreeSize; + for (uint256 i = start; i < numTxs; i++) { + leavesInSubtree[i - start] = _leaves[i]; + } + bytes32 subtreeRoot = computeRoot(leavesInSubtree); + root = numTxs == _leaves.length ? subtreeRoot : Hash.sha256ToField(bytes.concat(subtreeRoot, root)); + numTxs -= currentSubtreeSize; + currentSubtreeSize <<= 1; + } + return root; + } + + /** + * @notice Computes the root for a binary Merkle-tree given the leafs. + * @dev Uses sha256. + * @param _leafs - The 32 bytes leafs to build the tree of. + * @return The root of the Merkle tree. + */ + function computeRoot(bytes32[] memory _leafs) internal pure returns (bytes32) { + // @todo Must pad the tree + uint256 treeDepth = 0; + while (2 ** treeDepth < _leafs.length) { + treeDepth++; + } + uint256 treeSize = 2 ** treeDepth; + assembly { + mstore(_leafs, treeSize) + } + + for (uint256 i = 0; i < treeDepth; i++) { + for (uint256 j = 0; j < treeSize; j += 2) { + _leafs[j / 2] = Hash.sha256ToField(bytes.concat(_leafs[j], _leafs[j + 1])); + } + treeSize /= 2; + } + + return _leafs[0]; } }