From e5a324035406d35b7367fa0a724c75f742411f67 Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Tue, 24 Sep 2024 15:05:00 -0700 Subject: [PATCH] Holocene extensions to L1Block.sol --- packages/contracts-bedrock/.gas-snapshot | 4 +- packages/contracts-bedrock/semver-lock.json | 6 +- .../snapshots/abi/L1Block.json | 33 ++++++ .../snapshots/abi/L1BlockIsthmus.json | 33 ++++++ .../snapshots/storageLayout/L1Block.json | 14 +++ .../storageLayout/L1BlockIsthmus.json | 16 ++- packages/contracts-bedrock/src/L2/L1Block.sol | 63 +++++++++- .../src/L2/interfaces/IL1Block.sol | 3 + .../src/libraries/Encoding.sol | 46 ++++++++ .../contracts-bedrock/test/L2/L1Block.t.sol | 110 ++++++++++++++++++ 10 files changed, 320 insertions(+), 8 deletions(-) diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index ecfb713d05a0e..c28b70a53e1b9 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -1,5 +1,5 @@ -GasBenchMark_L1BlockIsthmus_DepositsComplete:test_depositsComplete_benchmark() (gas: 7567) -GasBenchMark_L1BlockIsthmus_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5567) +GasBenchMark_L1BlockIsthmus_DepositsComplete:test_depositsComplete_benchmark() (gas: 7589) +GasBenchMark_L1BlockIsthmus_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5589) GasBenchMark_L1BlockIsthmus_SetValuesIsthmus:test_setL1BlockValuesIsthmus_benchmark() (gas: 175657) GasBenchMark_L1BlockIsthmus_SetValuesIsthmus_Warm:test_setL1BlockValuesIsthmus_benchmark() (gas: 5121) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 91897e737ccff..f967b812e96f5 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -80,11 +80,11 @@ "sourceCodeHash": "0x4f21025d4b5c9c74cf7040db6f8e9ce605b82931e3012fee51d3f5d9fbd7b73f" }, "src/L2/L1Block.sol": { - "initCodeHash": "0xd12353c5bf71c6765cc9292eecf262f216e67f117f4ba6287796a5207dbca00f", - "sourceCodeHash": "0xfe3a9585d9bfca8428e12759cab68a3114374e5c37371cfe08bb1976a9a5a041" + "initCodeHash": "0x48d118de2a69fb0fbf6a8da4603025e12da1360da8fb70a5e56342ba64b3ff5f", + "sourceCodeHash": "0x04d25cbf0c4ea5025b0dd3f79f0a32f6623ddb869cff35649072ab3ad964b310" }, "src/L2/L1BlockIsthmus.sol": { - "initCodeHash": "0xb7a7a113056e4ac44824350b79fed5ea423e880223edcf1220e8f8b3172f50c5", + "initCodeHash": "0x4810ff018c42640a3a050a42ef2c9f39150f6782938a7e5db51c23bb6c75244e", "sourceCodeHash": "0x6be7e7402c4dfc10e1407e070712a3f9f352db45f8a8ab296e8f6bc56a341f47" }, "src/L2/L1FeeVault.sol": { diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 020c9e942c757..6efa216b5bd6a 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1Block.json +++ b/packages/contracts-bedrock/snapshots/abi/L1Block.json @@ -77,6 +77,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "eip1559Denominator", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip1559Elasticity", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingToken", @@ -282,6 +308,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesHolocene", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", diff --git a/packages/contracts-bedrock/snapshots/abi/L1BlockIsthmus.json b/packages/contracts-bedrock/snapshots/abi/L1BlockIsthmus.json index d827b32a9cab2..0c0b00c0657a2 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1BlockIsthmus.json +++ b/packages/contracts-bedrock/snapshots/abi/L1BlockIsthmus.json @@ -97,6 +97,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "eip1559Denominator", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip1559Elasticity", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "gasPayingToken", @@ -352,6 +378,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesHolocene", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "setL1BlockValuesIsthmus", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json index 2928d2147b5c8..5ee7d1e319429 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json @@ -75,5 +75,19 @@ "offset": 0, "slot": "7", "type": "uint256" + }, + { + "bytes": "8", + "label": "eip1559Denominator", + "offset": 0, + "slot": "8", + "type": "uint64" + }, + { + "bytes": "8", + "label": "eip1559Elasticity", + "offset": 8, + "slot": "8", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockIsthmus.json b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockIsthmus.json index 14ee2ff9609a0..4f0eeb0e52d79 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockIsthmus.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockIsthmus.json @@ -76,11 +76,25 @@ "slot": "7", "type": "uint256" }, + { + "bytes": "8", + "label": "eip1559Denominator", + "offset": 0, + "slot": "8", + "type": "uint64" + }, + { + "bytes": "8", + "label": "eip1559Elasticity", + "offset": 8, + "slot": "8", + "type": "uint64" + }, { "bytes": "64", "label": "dependencySet", "offset": 0, - "slot": "8", + "slot": "9", "type": "struct EnumerableSet.UintSet" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index c61f45b836290..feb9f18d1b89a 100644 --- a/packages/contracts-bedrock/src/L2/L1Block.sol +++ b/packages/contracts-bedrock/src/L2/L1Block.sol @@ -57,9 +57,15 @@ contract L1Block is ISemver, IGasToken { /// @notice The latest L1 blob base fee. uint256 public blobBaseFee; - /// @custom:semver 1.5.1-beta.2 + /// @notice The eip-1550 base fee change denominator value. + uint64 public eip1559Denominator; + + /// @notice The eip-1550 base fee change elasticity value. + uint64 public eip1559Elasticity; + + /// @custom:semver 1.5.1-beta.3 function version() public pure virtual returns (string memory) { - return "1.5.1-beta.2"; + return "1.5.1-beta.3"; } /// @notice Returns the gas paying token, its decimals, name and symbol. @@ -168,6 +174,59 @@ contract L1Block is ISemver, IGasToken { } } + /// @notice Updates the L1 block values for a Holocene upgraded chain. + /// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size. + /// Params are expected to be in the following order: + /// 1. _baseFeeScalar L1 base fee scalar + /// 2. _blobBaseFeeScalar L1 blob base fee scalar + /// 3. _sequenceNumber Number of L2 blocks since epoch start. + /// 4. _timestamp L1 timestamp. + /// 5. _number L1 blocknumber. + /// 6. _basefee L1 base fee. + /// 7. _blobBaseFee L1 blob base fee. + /// 8. _hash L1 blockhash. + /// 9. _batcherHash Versioned hash to authenticate batcher by. + /// 10. _eip1559Elasticity EIP-1559 elasticity multiplier value. + /// 11. _eip1559Denominator EIP-1559 base fee change denominator value. + function setL1BlockValuesHolocene() public { + _setL1BlockValuesHolocene(); + } + + /// @notice Updates the L1 block values for a Holocene upgraded chain. + /// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size. + /// Params are expected to be in the following order: + /// 1. _baseFeeScalar L1 base fee scalar + /// 2. _blobBaseFeeScalar L1 blob base fee scalar + /// 3. _sequenceNumber Number of L2 blocks since epoch start. + /// 4. _timestamp L1 timestamp. + /// 5. _number L1 blocknumber. + /// 6. _basefee L1 base fee. + /// 7. _blobBaseFee L1 blob base fee. + /// 8. _hash L1 blockhash. + /// 9. _batcherHash Versioned hash to authenticate batcher by. + /// 10. _eip1559Elasticity EIP-1559 elasticity multiplier value. + /// 11. _eip1559Denominator EIP-1559 base fee change denominator value. + function _setL1BlockValuesHolocene() internal { + address depositor = DEPOSITOR_ACCOUNT(); + assembly { + // Revert if the caller is not the depositor account. + if xor(caller(), depositor) { + mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()" + revert(0x1C, 0x04) // returns the stored 4-byte selector from above + } + // sequencenum (uint64), blobBaseFeeScalar (uint32), baseFeeScalar (uint32) + sstore(sequenceNumber.slot, shr(128, calldataload(4))) + // number (uint64) and timestamp (uint64) + sstore(number.slot, shr(128, calldataload(20))) + sstore(basefee.slot, calldataload(36)) // uint256 + sstore(blobBaseFee.slot, calldataload(68)) // uint256 + sstore(hash.slot, calldataload(100)) // bytes32 + sstore(batcherHash.slot, calldataload(132)) // bytes32 + // eip1559Denominator (uint64) and eip1559Elasticity (uint64) + sstore(eip1559Denominator.slot, shr(128, calldataload(164))) // uint64 + } + } + /// @notice Sets the gas paying token for the L2 system. Can only be called by the special /// depositor account. This function is not called on every L2 block but instead /// only called by specially crafted L1 deposit transactions. diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol b/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol index 6ef4c2984ae40..0b4acdd157595 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol @@ -34,6 +34,9 @@ interface IL1Block { ) external; function setL1BlockValuesEcotone() external; + function setL1BlockValuesHolocene() external; function timestamp() external view returns (uint64); function version() external pure returns (string memory); + function eip1559Denominator() external view returns (uint64); + function eip1559Elasticity() external view returns (uint64); } diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index 7ab1a285841ff..dac6be460c849 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -174,6 +174,52 @@ library Encoding { ); } + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesHolocene + /// @param baseFeeScalar L1 base fee Scalar + /// @param blobBaseFeeScalar L1 blob base fee Scalar + /// @param sequenceNumber Number of L2 blocks since epoch start. + /// @param timestamp L1 timestamp. + /// @param number L1 blocknumber. + /// @param baseFee L1 base fee. + /// @param blobBaseFee L1 blob base fee. + /// @param hash L1 blockhash. + /// @param batcherHash Versioned hash to authenticate batcher by. + /// @param eip1559Elasticity EIP-1559 elasticity parameter + /// @param eip1559Denominator EIP-1559 denominator parameter + function encodeSetL1BlockValuesHolocene( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash, + uint64 eip1559Elasticity, + uint64 eip1559Denominator + ) + internal + pure + returns (bytes memory) + { + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesHolocene()")); + return abi.encodePacked( + functionSignature, + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + eip1559Elasticity, + eip1559Denominator + ); + } + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesInterop /// @param _baseFeeScalar L1 base fee Scalar /// @param _blobBaseFeeScalar L1 blob base fee Scalar diff --git a/packages/contracts-bedrock/test/L2/L1Block.t.sol b/packages/contracts-bedrock/test/L2/L1Block.t.sol index 762553a2ff2f3..06de35f51c1d8 100644 --- a/packages/contracts-bedrock/test/L2/L1Block.t.sol +++ b/packages/contracts-bedrock/test/L2/L1Block.t.sol @@ -165,6 +165,116 @@ contract L1BlockEcotone_Test is L1BlockTest { } } +contract L1BlockHolocene_Test is L1BlockTest { + /// @dev Tests that setL1BlockValuesHolocene updates the values appropriately. + function testFuzz_setL1BlockValuesHolocene_succeeds( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash, + uint64 eip1559Elasticity, + uint64 eip1559Denominator + ) + external + { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesHolocene( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + eip1559Elasticity, + eip1559Denominator + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(functionCallDataPacked); + assertTrue(success, "Function call failed"); + + assertEq(l1Block.baseFeeScalar(), baseFeeScalar); + assertEq(l1Block.blobBaseFeeScalar(), blobBaseFeeScalar); + assertEq(l1Block.sequenceNumber(), sequenceNumber); + assertEq(l1Block.timestamp(), timestamp); + assertEq(l1Block.number(), number); + assertEq(l1Block.basefee(), baseFee); + assertEq(l1Block.blobBaseFee(), blobBaseFee); + assertEq(l1Block.hash(), hash); + assertEq(l1Block.batcherHash(), batcherHash); + assertEq(l1Block.eip1559Denominator(), eip1559Denominator); + assertEq(l1Block.eip1559Elasticity(), eip1559Elasticity); + + // ensure we didn't accidentally pollute the 128 bits of the sequencenum+scalars slot that + // should be empty + bytes32 scalarsSlot = vm.load(address(l1Block), bytes32(uint256(3))); + bytes32 mask128 = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"; + + assertEq(0, scalarsSlot & mask128); + + // ensure we didn't accidentally pollute the 128 bits of the number & timestamp slot that + // should be empty + bytes32 numberTimestampSlot = vm.load(address(l1Block), bytes32(uint256(0))); + assertEq(0, numberTimestampSlot & mask128); + + // ensure we didn't accidentally pollute the 128 bits of the eip-1559 parameters slot that + // should be empty + bytes32 eip1559ParamsSlot = vm.load(address(l1Block), bytes32(uint256(9))); + assertEq(0, eip1559ParamsSlot & mask128); + } + + /// @dev Tests that `setL1BlockValuesHolocene` succeeds if sender address is the depositor + function test_setL1BlockValuesHolocene_isDepositor_succeeds() external { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesHolocene( + type(uint32).max, + type(uint32).max, + type(uint64).max, + type(uint64).max, + type(uint64).max, + type(uint256).max, + type(uint256).max, + bytes32(type(uint256).max), + bytes32(type(uint256).max), + type(uint64).max, + type(uint64).max + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(functionCallDataPacked); + assertTrue(success, "function call failed"); + } + + /// @dev Tests that `setL1BlockValuesEcotone` reverts if sender address is not the depositor + function test_setL1BlockValuesHolocene_notDepositor_reverts() external { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesHolocene( + type(uint32).max, + type(uint32).max, + type(uint64).max, + type(uint64).max, + type(uint64).max, + type(uint256).max, + type(uint256).max, + bytes32(type(uint256).max), + bytes32(type(uint256).max), + type(uint64).max, + type(uint64).max + ); + + (bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked); + assertTrue(!success, "function call should have failed"); + // make sure return value is the expected function selector for "NotDepositor()" + bytes memory expReturn = hex"3cc50b45"; + assertEq(data, expReturn); + } +} + contract L1BlockCustomGasToken_Test is L1BlockTest { function testFuzz_setGasPayingToken_succeeds( address _token,