diff --git a/packages/contracts-bedrock/src/L2/GasPriceOracle.sol b/packages/contracts-bedrock/src/L2/GasPriceOracle.sol index 30ab88c19d24..07b141ebde04 100644 --- a/packages/contracts-bedrock/src/L2/GasPriceOracle.sol +++ b/packages/contracts-bedrock/src/L2/GasPriceOracle.sol @@ -24,20 +24,30 @@ contract GasPriceOracle is ISemver { uint256 public constant DECIMALS = 6; /// @notice Semantic version. - /// @custom:semver 1.1.0 - string public constant version = "1.1.0"; + /// @custom:semver 1.2.0 + string public constant version = "1.2.0"; + + /// @notice Indicates whether the network has gone through the Ecotone upgrade. + bool public isEcotone; /// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input /// transaction, the current L1 base fee, and the various dynamic parameters. /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. /// @return L1 fee that should be paid for the tx function getL1Fee(bytes memory _data) external view returns (uint256) { - uint256 l1GasUsed = getL1GasUsed(_data); - uint256 l1Fee = l1GasUsed * l1BaseFee(); - uint256 divisor = 10 ** DECIMALS; - uint256 unscaled = l1Fee * scalar(); - uint256 scaled = unscaled / divisor; - return scaled; + if (isEcotone) { + return _getL1FeeEcotone(_data); + } + return _getL1FeeBedrock(_data); + } + + /// @notice Set chain to be Ecotone chain (callable by depositor account) + function setEcotone() external { + require( + msg.sender == L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).DEPOSITOR_ACCOUNT(), + "GasPriceOracle: only the depositor account can set isEcotone flag" + ); + isEcotone = true; } /// @notice Retrieves the current gas price (base fee). @@ -52,15 +62,23 @@ contract GasPriceOracle is ISemver { return block.basefee; } + /// @custom:legacy /// @notice Retrieves the current fee overhead. /// @return Current fee overhead. function overhead() public view returns (uint256) { + if (isEcotone) { + revert("GasPriceOracle: overhead() is deprecated"); + } return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeOverhead(); } + /// @custom:legacy /// @notice Retrieves the current fee scalar. /// @return Current fee scalar. function scalar() public view returns (uint256) { + if (isEcotone) { + revert("GasPriceOracle: scalar() is deprecated"); + } return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeScalar(); } @@ -70,6 +88,24 @@ contract GasPriceOracle is ISemver { return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).basefee(); } + /// @notice Retrieves the current blob base fee. + /// @return Current blob base fee. + function blobBasefee() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).blobBasefee(); + } + + /// @notice Retrieves the current base fee scalar. + /// @return Current base fee scalar. + function basefeeScalar() public view returns (uint32) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).basefeeScalar(); + } + + /// @notice Retrieves the current blob base fee scalar. + /// @return Current blob base fee scalar. + function blobBasefeeScalar() public view returns (uint32) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).blobBasefeeScalar(); + } + /// @custom:legacy /// @notice Retrieves the number of decimals used in the scalar. /// @return Number of decimals used in the scalar. @@ -77,13 +113,43 @@ contract GasPriceOracle is ISemver { return DECIMALS; } - /// @notice Computes the amount of L1 gas used for a transaction. Adds the overhead which - /// represents the per-transaction gas overhead of posting the transaction and state - /// roots to L1. Adds 68 bytes of padding to account for the fact that the input does - /// not have a signature. + /// @notice Computes the amount of L1 gas used for a transaction. Adds 68 bytes + /// of padding to account for the fact that the input does not have a signature. /// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for. /// @return Amount of L1 gas used to publish the transaction. function getL1GasUsed(bytes memory _data) public view returns (uint256) { + return _getL1GasUsed(_data); + } + + /// @notice Computation of the L1 portion of the fee for Bedrock. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. + /// @return L1 fee that should be paid for the tx + function _getL1FeeBedrock(bytes memory _data) internal view returns (uint256) { + uint256 l1GasUsed = _getL1GasUsed(_data); + uint256 l1Fee = l1GasUsed * l1BaseFee(); + uint256 unscaled = l1Fee * L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeScalar(); + uint256 divisor = 10 ** DECIMALS; + uint256 scaled = unscaled / divisor; + return scaled; + } + + /// @notice L1 portion of the fee after Ecotone. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. + /// @return L1 fee that should be paid for the tx + function _getL1FeeEcotone(bytes memory _data) internal view returns (uint256) { + uint256 l1GasUsed = _getL1GasUsed(_data); + uint256 scaledBasefee = basefeeScalar() * 16 * l1BaseFee(); + uint256 scaledBlobBasefee = blobBasefeeScalar() * blobBasefee(); + uint256 unscaled = l1GasUsed * (scaledBasefee + scaledBlobBasefee); + uint256 divisor = 16 * 10 ** DECIMALS; + uint256 scaled = unscaled / divisor; + return scaled; + } + + /// @notice L1 gas estimation calculation. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for. + /// @return Amount of L1 gas used to publish the transaction. + function _getL1GasUsed(bytes memory _data) internal view returns (uint256) { uint256 total = 0; uint256 length = _data.length; for (uint256 i = 0; i < length; i++) { @@ -93,7 +159,10 @@ contract GasPriceOracle is ISemver { total += 16; } } - uint256 unsigned = total + overhead(); + uint256 unsigned = total; + if (!isEcotone) { + total += L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeOverhead(); + } return unsigned + (68 * 16); } } diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index 7ed45ce0d4b6..a5b5d0e28e00 100644 --- a/packages/contracts-bedrock/src/L2/L1Block.sol +++ b/packages/contracts-bedrock/src/L2/L1Block.sol @@ -33,14 +33,26 @@ contract L1Block is ISemver { bytes32 public batcherHash; /// @notice The overhead value applied to the L1 portion of the transaction fee. + /// @custom:legacy uint256 public l1FeeOverhead; /// @notice The scalar value applied to the L1 portion of the transaction fee. + /// @custom:legacy uint256 public l1FeeScalar; - /// @custom:semver 1.1.0 - string public constant version = "1.1.0"; + /// @notice The latest L1 blob basefee. + uint256 public blobBasefee; + /// @notice The scalar value applied to the L1 base fee portion of the blob-capable L1 cost func + uint32 public basefeeScalar; + + /// @notice The scalar value applied to the L1 blob base fee portion of the blob-capable L1 cost func + uint32 public blobBasefeeScalar; + + /// @custom:semver 1.2.0 + string public constant version = "1.2.0"; + + /// @custom:legacy /// @notice Updates the L1 block values. /// @param _number L1 blocknumber. /// @param _timestamp L1 timestamp. @@ -73,4 +85,62 @@ contract L1Block is ISemver { l1FeeOverhead = _l1FeeOverhead; l1FeeScalar = _l1FeeScalar; } + + /// @notice Updates the L1 block values for a post-blob activated chain. + /// Params are passed in as part of msg.data in order to compress the calldata. + /// Params should be passed in 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 basefee. + /// 7. _blobBasefee L1 blobBasefee. + /// 8. _hash L1 blockhash. + /// 9. _batcherHash Versioned hash to authenticate batcher by. + function setL1BlockValuesEcotone() external { + require(msg.sender == DEPOSITOR_ACCOUNT, "L1Block: only the depositor account can set L1 block values"); + + uint256 _basefeeScalar; + uint256 _blobBasefeeScalar; + uint256 _sequenceNumber; + uint256 _timestamp; + uint256 _number; + uint256 _basefee; + uint256 _blobBasefee; + bytes32 _hash; + bytes32 _batcherHash; + + assembly { + let offset := 0x4 + _basefeeScalar := shr(224, calldataload(offset)) // uint32 + offset := add(offset, 0x4) + _blobBasefeeScalar := shr(224, calldataload(offset)) // uint32 + offset := add(offset, 0x4) + _sequenceNumber := shr(192, calldataload(offset)) // uint64 + offset := add(offset, 0x8) + _timestamp := shr(192, calldataload(offset)) // uint64 + offset := add(offset, 0x8) + _number := shr(192, calldataload(offset)) // uint64 + offset := add(offset, 0x8) + _basefee := calldataload(offset) // uint256 + offset := add(offset, 0x20) + _blobBasefee := calldataload(offset) // uint256 + offset := add(offset, 0x20) + _hash := calldataload(offset) // bytes32 + offset := add(offset, 0x20) + _batcherHash := calldataload(offset) // bytes32 + offset := add(offset, 0x20) + } + + number = uint64(_number); + timestamp = uint64(_timestamp); + basefee = _basefee; + blobBasefee = _blobBasefee; + hash = _hash; + sequenceNumber = uint64(_sequenceNumber); + batcherHash = _batcherHash; + basefeeScalar = uint32(_basefeeScalar); + blobBasefeeScalar = uint32(_blobBasefeeScalar); + } } diff --git a/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol b/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol index 5e9d71e7baa5..4a67d252f215 100644 --- a/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol +++ b/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol @@ -8,23 +8,32 @@ contract GasPriceOracle_Test is CommonTest { event OverheadUpdated(uint256); event ScalarUpdated(uint256); event DecimalsUpdated(uint256); - address depositor; // The initial L1 context values uint64 constant number = 10; uint64 constant timestamp = 11; uint256 constant basefee = 100; + uint256 constant blobBasefee = 101; bytes32 constant hash = bytes32(uint256(64)); uint64 constant sequenceNumber = 0; bytes32 constant batcherHash = bytes32(uint256(777)); uint256 constant l1FeeOverhead = 310; uint256 constant l1FeeScalar = 10; + uint32 constant blobBasefeeScalar = 15; + uint32 constant basefeeScalar = 20; /// @dev Sets up the test suite. function setUp() public virtual override { super.setUp(); + depositor = l1Block.DEPOSITOR_ACCOUNT(); + } +} +contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { + /// @dev Sets up the test suite. + function setUp() public virtual override { + super.setUp(); depositor = l1Block.DEPOSITOR_ACCOUNT(); vm.prank(depositor); @@ -93,3 +102,111 @@ contract GasPriceOracle_Test is CommonTest { assertEq(returndata, hex""); } } + +contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { + /// @dev Sets up the test suite. + function setUp() public virtual override { + super.setUp(); + + // Define the function signature + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesEcotone()")); + + // Encode the function signature and extra data + bytes memory callDataPacked = abi.encodePacked( + basefeeScalar, + blobBasefeeScalar, + sequenceNumber, + timestamp, + number, + basefee, + blobBasefee, + hash, + batcherHash + ); + bytes memory functionCallData = abi.encodePacked(functionSignature, callDataPacked); + + // Execute the function call + vm.prank(depositor); + (bool success, ) = address(l1Block).call(functionCallData); + require(success, "Function call failed"); + + vm.prank(depositor); + gasPriceOracle.setEcotone(); + } + + /// @dev Tests that `setEcotone` is only callable by the depositor. + function test_setEcotone_wrongCaller_reverts() external { + vm.expectRevert("GasPriceOracle: only the depositor account can set isEcotone flag"); + gasPriceOracle.setEcotone(); + } + + /// @dev Tests that `gasPrice` is set correctly. + function test_gasPrice_succeeds() external { + vm.fee(100); + uint256 gasPrice = gasPriceOracle.gasPrice(); + assertEq(gasPrice, 100); + } + + /// @dev Tests that `baseFee` is set correctly. + function test_baseFee_succeeds() external { + vm.fee(64); + uint256 gasPrice = gasPriceOracle.baseFee(); + assertEq(gasPrice, 64); + } + + /// @dev Tests that `overhead` reverts since it was removed in ecotone. + function test_overhead_legacyFunction_reverts() external { + vm.expectRevert("GasPriceOracle: overhead() is deprecated"); + gasPriceOracle.overhead(); + } + + /// @dev Tests that `scalar` reverts since it was removed in ecotone. + function test_scalar_legacyFunction_reverts() external { + vm.expectRevert("GasPriceOracle: scalar() is deprecated"); + gasPriceOracle.scalar(); + } + + /// @dev Tests that `l1BaseFee` is set correctly. + function test_l1BaseFee_succeeds() external { + assertEq(gasPriceOracle.l1BaseFee(), basefee); + } + + /// @dev Tests that `blobBasefee` is set correctly. + function test_blobBasefee_succeeds() external { + assertEq(gasPriceOracle.blobBasefee(), blobBasefee); + } + + /// @dev Tests that `basefeeScalar` is set correctly. + function test_basefeeScalar_succeeds() external { + assertEq(gasPriceOracle.basefeeScalar(), basefeeScalar); + } + + /// @dev Tests that `blobBasefeeScalar` is set correctly. + function test_blobBasefeeScalar_succeeds() external { + assertEq(gasPriceOracle.blobBasefeeScalar(), blobBasefeeScalar); + } + + /// @dev Tests that `decimals` is set correctly. + function test_decimals_succeeds() external { + assertEq(gasPriceOracle.decimals(), 6); + assertEq(gasPriceOracle.DECIMALS(), 6); + } + + /// @dev Tests that `setGasPrice` reverts since it was removed in bedrock. + function test_setGasPrice_doesNotExist_reverts() external { + (bool success, bytes memory returndata) = + address(gasPriceOracle).call(abi.encodeWithSignature("setGasPrice(uint256)", 1)); + + assertEq(success, false); + assertEq(returndata, hex""); + } + + /// @dev Tests that `setL1BaseFee` reverts since it was removed in bedrock. + function test_setL1BaseFee_doesNotExist_reverts() external { + (bool success, bytes memory returndata) = + address(gasPriceOracle).call(abi.encodeWithSignature("setL1BaseFee(uint256)", 1)); + + assertEq(success, false); + assertEq(returndata, hex""); + } +} \ No newline at end of file diff --git a/packages/contracts-bedrock/test/L2/L1Block.t.sol b/packages/contracts-bedrock/test/L2/L1Block.t.sol index c62a32e446f9..f6f6ed58889e 100644 --- a/packages/contracts-bedrock/test/L2/L1Block.t.sol +++ b/packages/contracts-bedrock/test/L2/L1Block.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Testing utilities import { CommonTest } from "test/setup/CommonTest.sol"; +import "forge-std/console.sol"; // Target contract import { L1Block } from "src/L2/L1Block.sol"; @@ -10,25 +11,45 @@ import { L1Block } from "src/L2/L1Block.sol"; contract L1BlockTest is CommonTest { address depositor; + // The initial L1 context values + uint64 constant number = 123; + uint64 constant timestamp = 456; + uint256 constant basefee = 789; + uint256 constant blobBasefee = 1011; + bytes32 constant hash = bytes32(uint256(1213)); + uint64 constant sequenceNumber = 14; + bytes32 constant batcherHash = bytes32(uint256(1516)); + uint256 constant l1FeeOverhead = 1718; + uint256 constant l1FeeScalar = 1920; + uint32 constant basefeeScalar = 21; + uint32 constant blobBasefeeScalar = 22; + /// @dev Sets up the test suite. function setUp() public virtual override { super.setUp(); - depositor = l1Block.DEPOSITOR_ACCOUNT(); + } +} + +contract L1BlockBedrock_Test is L1BlockTest { + /// @dev Sets up the test suite. + function setUp() public virtual override { + super.setUp(); + vm.prank(depositor); l1Block.setL1BlockValues({ - _number: uint64(1), - _timestamp: uint64(2), - _basefee: 3, - _hash: keccak256(abi.encode(block.number)), - _sequenceNumber: uint64(4), - _batcherHash: bytes32(0), - _l1FeeOverhead: 2, - _l1FeeScalar: 3 + _number: number, + _timestamp: timestamp, + _basefee: basefee, + _hash: hash, + _sequenceNumber: sequenceNumber, + _batcherHash: batcherHash, + _l1FeeOverhead: l1FeeOverhead, + _l1FeeScalar: l1FeeScalar }); } - /// @dev Tests that `setL1BlockValues` updates the values correctly. + // @dev Tests that `setL1BlockValues` updates the values correctly. function testFuzz_updatesValues_succeeds( uint64 n, uint64 t, @@ -55,27 +76,27 @@ contract L1BlockTest is CommonTest { /// @dev Tests that `number` returns the correct value. function test_number_succeeds() external { - assertEq(l1Block.number(), uint64(1)); + assertEq(l1Block.number(), number); } /// @dev Tests that `timestamp` returns the correct value. function test_timestamp_succeeds() external { - assertEq(l1Block.timestamp(), uint64(2)); + assertEq(l1Block.timestamp(), timestamp); } /// @dev Tests that `basefee` returns the correct value. function test_basefee_succeeds() external { - assertEq(l1Block.basefee(), 3); + assertEq(l1Block.basefee(), basefee); } /// @dev Tests that `hash` returns the correct value. function test_hash_succeeds() external { - assertEq(l1Block.hash(), keccak256(abi.encode(block.number))); + assertEq(l1Block.hash(), hash); } /// @dev Tests that `sequenceNumber` returns the correct value. function test_sequenceNumber_succeeds() external { - assertEq(l1Block.sequenceNumber(), uint64(4)); + assertEq(l1Block.sequenceNumber(), sequenceNumber); } /// @dev Tests that `setL1BlockValues` can set max values. @@ -93,3 +114,74 @@ contract L1BlockTest is CommonTest { }); } } + +contract L1BlockEcotone_Test is L1BlockTest { + /// @dev Sets up the test suite. + function setUp() public virtual override { + super.setUp(); + + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesEcotone()")); + bytes memory callDataPacked = abi.encodePacked( + basefeeScalar, + blobBasefeeScalar, + sequenceNumber, + timestamp, + number, + basefee, + blobBasefee, + hash, + batcherHash + ); + + bytes memory functionCallDataPacked = abi.encodePacked(functionSignature, callDataPacked); + + vm.prank(depositor); + (bool success, ) = address(l1Block).call(functionCallDataPacked); + require(success, "Function call failed"); + } + + /// @dev Tests that `number` returns the correct value. + function test_number_succeeds() external { + assertEq(l1Block.number(), number); + } + + /// @dev Tests that `timestamp` returns the correct value. + function test_timestamp_succeeds() external { + assertEq(l1Block.timestamp(), timestamp); + } + + /// @dev Tests that `basefee` returns the correct value. + function test_basefee_succeeds() external { + assertEq(l1Block.basefee(), basefee); + } + + /// @dev Tests that `blobBasefee` returns the correct value. + function test_blobBaseFee_succeeds() external { + assertEq(l1Block.blobBasefee(), blobBasefee); + } + + /// @dev Tests that `hash` returns the correct value. + function test_hash_succeeds() external { + assertEq(l1Block.hash(), hash); + } + + /// @dev Tests that `sequenceNumber` returns the correct value. + function test_sequenceNumber_succeeds() external { + assertEq(l1Block.sequenceNumber(), sequenceNumber); + } + + /// @dev Tests that `batcherHash` returns the correct value. + function test_batcherHash_succeeds() external { + assertEq(l1Block.batcherHash(), batcherHash); + } + + /// @dev Tests that `basefeeScalar` returns the correct value. + function test_baseFeeScalar_succeeds() external { + assertEq(l1Block.basefeeScalar(), basefeeScalar); + } + + /// @dev Tests that `blobBasefeeScalar` returns the correct value. + function test_blobBaseFeeScalar_succeeds() external { + assertEq(l1Block.blobBasefeeScalar(), blobBasefeeScalar); + } +} \ No newline at end of file