From 8e7c9cab0800a853ed374e0305a26098a16c8fb9 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 | 6 +- packages/contracts-bedrock/semver-lock.json | 8 +- .../snapshots/abi/L1Block.json | 33 ++++++ .../snapshots/abi/L1BlockInterop.json | 33 ++++++ .../snapshots/storageLayout/L1Block.json | 14 +++ .../storageLayout/L1BlockInterop.json | 16 ++- packages/contracts-bedrock/src/L2/L1Block.sol | 63 +++++++++- .../src/L2/L1BlockInterop.sol | 4 +- .../src/L2/interfaces/IL1Block.sol | 3 + .../src/L2/interfaces/IL1BlockInterop.sol | 3 + .../src/libraries/Encoding.sol | 46 ++++++++ .../contracts-bedrock/test/L2/L1Block.t.sol | 110 ++++++++++++++++++ 12 files changed, 327 insertions(+), 12 deletions(-) diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index 3564748212d9..d0b6d774bd07 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -1,6 +1,6 @@ -GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7567) -GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5567) -GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchmark() (gas: 175677) +GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7589) +GasBenchMark_L1BlockInterop_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5589) +GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchmark() (gas: 175655) GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 30b546e24ca9..db2732ab8bdc 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -80,12 +80,12 @@ "sourceCodeHash": "0x4f21025d4b5c9c74cf7040db6f8e9ce605b82931e3012fee51d3f5d9fbd7b73f" }, "src/L2/L1Block.sol": { - "initCodeHash": "0xd12353c5bf71c6765cc9292eecf262f216e67f117f4ba6287796a5207dbca00f", - "sourceCodeHash": "0xfe3a9585d9bfca8428e12759cab68a3114374e5c37371cfe08bb1976a9a5a041" + "initCodeHash": "0x48d118de2a69fb0fbf6a8da4603025e12da1360da8fb70a5e56342ba64b3ff5f", + "sourceCodeHash": "0x04d25cbf0c4ea5025b0dd3f79f0a32f6623ddb869cff35649072ab3ad964b310" }, "src/L2/L1BlockInterop.sol": { - "initCodeHash": "0x77b3b2151fe14ea36a640469115a5e4de27f7654a9606a9d0701522c6a4ad887", - "sourceCodeHash": "0x7417677643e1df1ae1782513b94c7821097b9529d3f8626c3bcb8b3a9ae0d180" + "initCodeHash": "0x7f87e0b8be9801cb242c469ec7999eb80221f65063aedd4ca4923a5e0fb0e5a7", + "sourceCodeHash": "0x722071a9d08dcbeda9cdaadeb2dd679a8bc192563e4a0439f4cd74439fa75581" }, "src/L2/L1FeeVault.sol": { "initCodeHash": "0x3bfcd57e25ad54b66c374f63e24e33a6cf107044aa8f5f69ef21202c380b5c5b", diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 020c9e942c75..6efa216b5bd6 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/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json index ab089f0cec55..ba871eb2086a 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.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": "setL1BlockValuesInterop", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json index 2928d2147b5c..5ee7d1e31942 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/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json index 14ee2ff9609a..4f0eeb0e52d7 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.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 c61f45b83629..feb9f18d1b89 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/L1BlockInterop.sol b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol index 15ea67f5e6b3..189e0fe7d7d0 100644 --- a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol +++ b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol @@ -42,9 +42,9 @@ contract L1BlockInterop is L1Block { /// keccak256(abi.encode(uint256(keccak256("l1Block.identifier.isDeposit")) - 1)) & ~bytes32(uint256(0xff)) uint256 internal constant IS_DEPOSIT_SLOT = 0x921bd3a089295c6e5540e8fba8195448d253efd6f2e3e495b499b627dc36a300; - /// @custom:semver +interop + /// @custom:semver +interop-beta.1 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop"); + return string.concat(super.version(), "+interop-beta.1"); } /// @notice Returns whether the call was triggered from a a deposit or not. diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol b/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol index a43b3c7c3963..0eba9a9973f3 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL1Block.sol @@ -34,8 +34,11 @@ 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); function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol b/packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol index dd72e3fa6f89..31943804b961 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IL1BlockInterop.sol @@ -52,9 +52,12 @@ interface IL1BlockInterop { ) external; function setL1BlockValuesEcotone() external; + function setL1BlockValuesHolocene() external; function setL1BlockValuesInterop() 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); function __constructor__() external; } diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index edcdd4ed75e2..896e60e5bae2 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -213,4 +213,50 @@ library Encoding { _batcherHash ); } + + /// @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 + ); + } } diff --git a/packages/contracts-bedrock/test/L2/L1Block.t.sol b/packages/contracts-bedrock/test/L2/L1Block.t.sol index 762553a2ff2f..06de35f51c1d 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,