From 774f053cc2b40f2230b1ebff99f579b506f2b3e7 Mon Sep 17 00:00:00 2001 From: zimpha Date: Wed, 22 May 2024 20:20:32 +0800 Subject: [PATCH 1/2] update L1GasPriceOracle for Curie fork --- .../src/L2/predeploys/IL1GasPriceOracle.sol | 32 +++- .../src/L2/predeploys/L1GasPriceOracle.sol | 178 +++++++++++++++--- contracts/src/test/L1GasPriceOracle.t.sol | 122 +++++++++++- 3 files changed, 297 insertions(+), 35 deletions(-) diff --git a/contracts/src/L2/predeploys/IL1GasPriceOracle.sol b/contracts/src/L2/predeploys/IL1GasPriceOracle.sol index 9d67595d98..850a178a66 100644 --- a/contracts/src/L2/predeploys/IL1GasPriceOracle.sol +++ b/contracts/src/L2/predeploys/IL1GasPriceOracle.sol @@ -15,10 +15,22 @@ interface IL1GasPriceOracle { /// @param scalar The current fee scalar updated. event ScalarUpdated(uint256 scalar); + /// @notice Emitted when current commit fee scalar is updated. + /// @param scalar The current commit fee scalar updated. + event CommitScalarUpdated(uint256 scalar); + + /// @notice Emitted when current blob fee scalar is updated. + /// @param scalar The current blob fee scalar updated. + event BlobScalarUpdated(uint256 scalar); + /// @notice Emitted when current l1 base fee is updated. /// @param l1BaseFee The current l1 base fee updated. event L1BaseFeeUpdated(uint256 l1BaseFee); + /// @notice Emitted when current l1 blob base fee is updated. + /// @param l1BlobBaseFee The current l1 blob base fee updated. + event L1BlobBaseFeeUpdated(uint256 l1BlobBaseFee); + /************************* * Public View Functions * *************************/ @@ -26,15 +38,24 @@ interface IL1GasPriceOracle { /// @notice Return the current l1 fee overhead. function overhead() external view returns (uint256); - /// @notice Return the current l1 fee scalar. + /// @notice Return the current l1 fee scalar before Curie fork. function scalar() external view returns (uint256); + /// @notice Return the current l1 commit fee scalar. + function commitScalar() external view returns (uint256); + + /// @notice Return the current l1 blob fee scalar. + function blobScalar() external view returns (uint256); + /// @notice Return the latest known l1 base fee. function l1BaseFee() external view returns (uint256); + /// @notice Return the latest known l1 blob base fee. + function l1BlobBaseFee() external view returns (uint256); + /// @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. + /// @param data Signed 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); @@ -42,7 +63,7 @@ interface IL1GasPriceOracle { /// represents the per-transaction gas overhead of posting the transaction and state /// roots to L1. Adds 74 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. + /// @param data Signed 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) external view returns (uint256); @@ -53,4 +74,9 @@ interface IL1GasPriceOracle { /// @notice Allows whitelisted caller to modify the l1 base fee. /// @param _l1BaseFee New l1 base fee. function setL1BaseFee(uint256 _l1BaseFee) external; + + /// @notice Allows whitelisted caller to modify the l1 base fee. + /// @param _l1BaseFee New l1 base fee. + /// @param _l1BlobBaseFee New l1 blob base fee. + function setL1BaseFeeAndBlobBaseFee(uint256 _l1BaseFee, uint256 _l1BlobBaseFee) external; } diff --git a/contracts/src/L2/predeploys/L1GasPriceOracle.sol b/contracts/src/L2/predeploys/L1GasPriceOracle.sol index 8e8086130d..4052cb6e48 100644 --- a/contracts/src/L2/predeploys/L1GasPriceOracle.sol +++ b/contracts/src/L2/predeploys/L1GasPriceOracle.sol @@ -17,6 +17,28 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// @param _newWhitelist The address of new whitelist contract. event UpdateWhitelist(address _oldWhitelist, address _newWhitelist); + /********** + * Errors * + **********/ + + /// @dev Thrown when the blob fee scalar exceed `MAX_BLOB_SCALAR`. + error ErrExceedMaxBlobScalar(); + + /// @dev Thrown when the commit fee scalar exceed `MAX_COMMIT_SCALAR`. + error ErrExceedMaxCommitScalar(); + + /// @dev Thrown when the l1 fee overhead exceed `MAX_OVERHEAD`. + error ErrExceedMaxOverhead(); + + /// @dev Thrown when the l1 fee scalar exceed `MAX_SCALAR`. + error ErrExceedMaxScalar(); + + /// @dev Thrown when the caller is not whitelisted. + error ErrCallerNotWhitelisted(); + + /// @dev Thrown when we enable Curie fork in Curie fork. + error ErrAlreadyInCurieFork(); + /************* * Constants * *************/ @@ -28,9 +50,25 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// Computed based on current l1 block gas limit. uint256 private constant MAX_OVERHEAD = 30000000 / 16; - /// @dev The maximum possible l1 fee scale. + /// @dev The maximum possible l1 fee scale before Curie. /// x1000 should be enough. - uint256 private constant MAX_SCALE = 1000 * PRECISION; + uint256 private constant MAX_SCALAR = 1000 * PRECISION; + + /// @dev The maximum possible l1 commit fee scalar after Curie. + /// We derive the commit scalar by + /// ``` + /// commit_scalar = commit_gas_per_tx * fluctuation_multiplier * 1e9 + /// ``` + /// So, the value should not exceed 10^18 * 1e9 normally. + uint256 private constant MAX_COMMIT_SCALAR = 10 ** 18 * PRECISION; + + /// @dev The maximum possible l1 blob fee scalar after Curie. + /// We derive the blob scalar by + /// ``` + /// blob_scalar = fluctuation_multiplier / compression_ratio / blob_util_ratio * 1e9 + /// ``` + /// So, the value should not exceed 10^18 * 1e9 normally. + uint256 private constant MAX_BLOB_SCALAR = 10 ** 18 * PRECISION; /************* * Variables * @@ -48,6 +86,27 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// @notice The address of whitelist contract. IWhitelist public whitelist; + /// @inheritdoc IL1GasPriceOracle + uint256 public override l1BlobBaseFee; + + /// @inheritdoc IL1GasPriceOracle + uint256 public override commitScalar; + + /// @inheritdoc IL1GasPriceOracle + uint256 public override blobScalar; + + /// @notice Indicates whether the network has gone through the Curie upgrade. + bool public isCurie; + + /************* + * Modifiers * + *************/ + + modifier onlyWhitelistedSender() { + if (!whitelist.isSenderAllowed(msg.sender)) revert ErrCallerNotWhitelisted(); + _; + } + /*************** * Constructor * ***************/ @@ -62,26 +121,20 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// @inheritdoc IL1GasPriceOracle function getL1Fee(bytes memory _data) external view override returns (uint256) { - uint256 _l1GasUsed = getL1GasUsed(_data); - uint256 _l1Fee = _l1GasUsed * l1BaseFee; - return (_l1Fee * scalar) / PRECISION; + if (isCurie) { + return _getL1FeeCurie(_data); + } else { + return _getL1FeeBeforeCurie(_data); + } } /// @inheritdoc IL1GasPriceOracle - /// @dev The `_data` is the RLP-encoded transaction with signature. And we also reserve additional - /// 4 bytes in the non-zero bytes to store the number of bytes in the RLP-encoded transaction. function getL1GasUsed(bytes memory _data) public view override returns (uint256) { - uint256 _total = 0; - uint256 _length = _data.length; - unchecked { - for (uint256 i = 0; i < _length; i++) { - if (_data[i] == 0) { - _total += 4; - } else { - _total += 16; - } - } - return _total + overhead + (4 * 16); + if (isCurie) { + // It is near zero since we put all transactions to blob. + return 0; + } else { + return _getL1GasUsedBeforeCurie(_data); } } @@ -90,12 +143,22 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { *****************************/ /// @inheritdoc IL1GasPriceOracle - function setL1BaseFee(uint256 _l1BaseFee) external override { - require(whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender"); + function setL1BaseFee(uint256 _l1BaseFee) external override onlyWhitelistedSender { + l1BaseFee = _l1BaseFee; + emit L1BaseFeeUpdated(_l1BaseFee); + } + + /// @inheritdoc IL1GasPriceOracle + function setL1BaseFeeAndBlobBaseFee( + uint256 _l1BaseFee, + uint256 _l1BlobBaseFee + ) external override onlyWhitelistedSender { l1BaseFee = _l1BaseFee; + l1BlobBaseFee = _l1BlobBaseFee; emit L1BaseFeeUpdated(_l1BaseFee); + emit L1BlobBaseFeeUpdated(_l1BlobBaseFee); } /************************ @@ -105,7 +168,7 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// @notice Allows the owner to modify the overhead. /// @param _overhead New overhead function setOverhead(uint256 _overhead) external onlyOwner { - require(_overhead <= MAX_OVERHEAD, "exceed maximum overhead"); + if (_overhead > MAX_OVERHEAD) revert ErrExceedMaxOverhead(); overhead = _overhead; emit OverheadUpdated(_overhead); @@ -114,12 +177,30 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// Allows the owner to modify the scalar. /// @param _scalar New scalar function setScalar(uint256 _scalar) external onlyOwner { - require(_scalar <= MAX_SCALE, "exceed maximum scale"); + if (_scalar > MAX_SCALAR) revert ErrExceedMaxScalar(); scalar = _scalar; emit ScalarUpdated(_scalar); } + /// Allows the owner to modify the commit scalar. + /// @param _scalar New scalar + function setCommitScalar(uint256 _scalar) external onlyOwner { + if (_scalar > MAX_COMMIT_SCALAR) revert ErrExceedMaxCommitScalar(); + + commitScalar = _scalar; + emit CommitScalarUpdated(_scalar); + } + + /// Allows the owner to modify the blob scalar. + /// @param _scalar New scalar + function setBlobScalar(uint256 _scalar) external onlyOwner { + if (_scalar > MAX_BLOB_SCALAR) revert ErrExceedMaxBlobScalar(); + + blobScalar = _scalar; + emit BlobScalarUpdated(_scalar); + } + /// @notice Update whitelist contract. /// @dev This function can only called by contract owner. /// @param _newWhitelist The address of new whitelist contract. @@ -129,4 +210,57 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { whitelist = IWhitelist(_newWhitelist); emit UpdateWhitelist(_oldWhitelist, _newWhitelist); } + + /// @notice Enable the Curie fork (callable by contract owner). + /// + /// @dev Since this is a predeploy contract, we will directly set the slot while hard fork + /// to avoid external owner operations. + /// The reason that we keep this function is for easy unit testing. + function enableCurie() external onlyOwner { + if (isCurie) revert ErrAlreadyInCurieFork(); + isCurie = true; + } + + /********************** + * Internal Functions * + **********************/ + + /// @dev Internal function to computes the amount of L1 gas used for a transaction before Curie fork. + /// The `_data` is the RLP-encoded transaction with signature. And we also reserve additional + /// 4 bytes in the non-zero bytes to store the number of bytes in the RLP-encoded transaction. + /// @param _data Signed fully RLP-encoded transaction to get the L1 gas for. + /// @return Amount of L1 gas used to publish the transaction. + function _getL1GasUsedBeforeCurie(bytes memory _data) private view returns (uint256) { + uint256 _total = 0; + uint256 _length = _data.length; + unchecked { + for (uint256 i = 0; i < _length; i++) { + if (_data[i] == 0) { + _total += 4; + } else { + _total += 16; + } + } + return _total + overhead + (4 * 16); + } + } + + /// @dev Internal function to compute 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, before Curie fork. + /// @param _data Signed fully RLP-encoded transaction to get the L1 fee for. + /// @return L1 fee that should be paid for the tx + function _getL1FeeBeforeCurie(bytes memory _data) private view returns (uint256) { + uint256 _l1GasUsed = _getL1GasUsedBeforeCurie(_data); + uint256 _l1Fee = _l1GasUsed * l1BaseFee; + return (_l1Fee * scalar) / PRECISION; + } + + /// @dev Internal function to compute 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, after Curie fork. + /// @param _data Signed fully RLP-encoded transaction to get the L1 fee for. + /// @return L1 fee that should be paid for the tx + function _getL1FeeCurie(bytes memory _data) private view returns (uint256) { + // We have bounded the value of `commitScalar` and `blobScalar`, the whole expression won't overflow. + return (commitScalar * l1BaseFee + blobScalar * _data.length * l1BlobBaseFee) / PRECISION; + } } diff --git a/contracts/src/test/L1GasPriceOracle.t.sol b/contracts/src/test/L1GasPriceOracle.t.sol index 5c77ce6c75..102add5d54 100644 --- a/contracts/src/test/L1GasPriceOracle.t.sol +++ b/contracts/src/test/L1GasPriceOracle.t.sol @@ -4,14 +4,15 @@ pragma solidity =0.8.24; import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; -import {L1BlockContainer} from "../L2/predeploys/L1BlockContainer.sol"; import {L1GasPriceOracle} from "../L2/predeploys/L1GasPriceOracle.sol"; import {Whitelist} from "../L2/predeploys/Whitelist.sol"; contract L1GasPriceOracleTest is DSTestPlus { uint256 private constant PRECISION = 1e9; uint256 private constant MAX_OVERHEAD = 30000000 / 16; - uint256 private constant MAX_SCALE = 1000 * PRECISION; + uint256 private constant MAX_SCALAR = 1000 * PRECISION; + uint256 private constant MAX_COMMIT_SCALAR = 10 ** 18 * PRECISION; + uint256 private constant MAX_BLOB_SCALAR = 10 ** 18 * PRECISION; L1GasPriceOracle private oracle; Whitelist private whitelist; @@ -36,7 +37,7 @@ contract L1GasPriceOracleTest is DSTestPlus { hevm.stopPrank(); // overhead is too large - hevm.expectRevert("exceed maximum overhead"); + hevm.expectRevert(L1GasPriceOracle.ErrExceedMaxOverhead.selector); oracle.setOverhead(MAX_OVERHEAD + 1); // call by owner, should succeed @@ -46,7 +47,7 @@ contract L1GasPriceOracleTest is DSTestPlus { } function testSetScalar(uint256 _scalar) external { - _scalar = bound(_scalar, 0, MAX_SCALE); + _scalar = bound(_scalar, 0, MAX_SCALAR); // call by non-owner, should revert hevm.startPrank(address(1)); @@ -55,8 +56,8 @@ contract L1GasPriceOracleTest is DSTestPlus { hevm.stopPrank(); // scale is too large - hevm.expectRevert("exceed maximum scale"); - oracle.setScalar(MAX_SCALE + 1); + hevm.expectRevert(L1GasPriceOracle.ErrExceedMaxScalar.selector); + oracle.setScalar(MAX_SCALAR + 1); // call by owner, should succeed assertEq(oracle.scalar(), 0); @@ -64,6 +65,44 @@ contract L1GasPriceOracleTest is DSTestPlus { assertEq(oracle.scalar(), _scalar); } + function testSetCommitScalar(uint256 _scalar) external { + _scalar = bound(_scalar, 0, MAX_COMMIT_SCALAR); + + // call by non-owner, should revert + hevm.startPrank(address(1)); + hevm.expectRevert("caller is not the owner"); + oracle.setCommitScalar(_scalar); + hevm.stopPrank(); + + // scale is too large + hevm.expectRevert(L1GasPriceOracle.ErrExceedMaxCommitScalar.selector); + oracle.setCommitScalar(MAX_COMMIT_SCALAR + 1); + + // call by owner, should succeed + assertEq(oracle.commitScalar(), 0); + oracle.setCommitScalar(_scalar); + assertEq(oracle.commitScalar(), _scalar); + } + + function testSetBlobScalar(uint256 _scalar) external { + _scalar = bound(_scalar, 0, MAX_BLOB_SCALAR); + + // call by non-owner, should revert + hevm.startPrank(address(1)); + hevm.expectRevert("caller is not the owner"); + oracle.setBlobScalar(_scalar); + hevm.stopPrank(); + + // scale is too large + hevm.expectRevert(L1GasPriceOracle.ErrExceedMaxBlobScalar.selector); + oracle.setBlobScalar(MAX_COMMIT_SCALAR + 1); + + // call by owner, should succeed + assertEq(oracle.blobScalar(), 0); + oracle.setBlobScalar(_scalar); + assertEq(oracle.blobScalar(), _scalar); + } + function testUpdateWhitelist(address _newWhitelist) external { hevm.assume(_newWhitelist != address(whitelist)); @@ -79,12 +118,29 @@ contract L1GasPriceOracleTest is DSTestPlus { assertEq(address(oracle.whitelist()), _newWhitelist); } + function testEnableCurie() external { + // call by non-owner, should revert + hevm.startPrank(address(1)); + hevm.expectRevert("caller is not the owner"); + oracle.enableCurie(); + hevm.stopPrank(); + + // call by owner, should succeed + assertBoolEq(oracle.isCurie(), false); + oracle.enableCurie(); + assertBoolEq(oracle.isCurie(), true); + + // enable twice, should revert + hevm.expectRevert(L1GasPriceOracle.ErrAlreadyInCurieFork.selector); + oracle.enableCurie(); + } + function testSetL1BaseFee(uint256 _baseFee) external { _baseFee = bound(_baseFee, 0, 1e9 * 20000); // max 20k gwei // call by non-owner, should revert hevm.startPrank(address(1)); - hevm.expectRevert("Not whitelisted sender"); + hevm.expectRevert(L1GasPriceOracle.ErrCallerNotWhitelisted.selector); oracle.setL1BaseFee(_baseFee); hevm.stopPrank(); @@ -94,7 +150,25 @@ contract L1GasPriceOracleTest is DSTestPlus { assertEq(oracle.l1BaseFee(), _baseFee); } - function testGetL1GasUsed(uint256 _overhead, bytes memory _data) external { + function testSetL1BaseFeeAndBlobBaseFee(uint256 _baseFee, uint256 _blobBaseFee) external { + _baseFee = bound(_baseFee, 0, 1e9 * 20000); // max 20k gwei + _blobBaseFee = bound(_blobBaseFee, 0, 1e9 * 20000); // max 20k gwei + + // call by non-owner, should revert + hevm.startPrank(address(1)); + hevm.expectRevert(L1GasPriceOracle.ErrCallerNotWhitelisted.selector); + oracle.setL1BaseFeeAndBlobBaseFee(_baseFee, _blobBaseFee); + hevm.stopPrank(); + + // call by owner, should succeed + assertEq(oracle.l1BaseFee(), 0); + assertEq(oracle.l1BlobBaseFee(), 0); + oracle.setL1BaseFeeAndBlobBaseFee(_baseFee, _blobBaseFee); + assertEq(oracle.l1BaseFee(), _baseFee); + assertEq(oracle.l1BlobBaseFee(), _blobBaseFee); + } + + function testGetL1GasUsedBeforeCurie(uint256 _overhead, bytes memory _data) external { _overhead = bound(_overhead, 0, MAX_OVERHEAD); oracle.setOverhead(_overhead); @@ -108,14 +182,14 @@ contract L1GasPriceOracleTest is DSTestPlus { assertEq(oracle.getL1GasUsed(_data), _gasUsed); } - function testGetL1Fee( + function testGetL1FeeBeforeCurie( uint256 _baseFee, uint256 _overhead, uint256 _scalar, bytes memory _data ) external { _overhead = bound(_overhead, 0, MAX_OVERHEAD); - _scalar = bound(_scalar, 0, MAX_SCALE); + _scalar = bound(_scalar, 0, MAX_SCALAR); _baseFee = bound(_baseFee, 0, 1e9 * 20000); // max 20k gwei oracle.setOverhead(_overhead); @@ -130,4 +204,32 @@ contract L1GasPriceOracleTest is DSTestPlus { assertEq(oracle.getL1Fee(_data), (_gasUsed * _baseFee * _scalar) / PRECISION); } + + function testGetL1GasUsedCurie(bytes memory _data) external { + oracle.enableCurie(); + assertEq(oracle.getL1GasUsed(_data), 0); + } + + function testGetL1FeeCurie( + uint256 _baseFee, + uint256 _blobBaseFee, + uint256 _commitScalar, + uint256 _blobScalar, + bytes memory _data + ) external { + _baseFee = bound(_baseFee, 0, 1e9 * 20000); // max 20k gwei + _blobBaseFee = bound(_blobBaseFee, 0, 1e9 * 20000); // max 20k gwei + _commitScalar = bound(_commitScalar, 0, MAX_COMMIT_SCALAR); + _blobScalar = bound(_blobScalar, 0, MAX_BLOB_SCALAR); + + oracle.enableCurie(); + oracle.setCommitScalar(_commitScalar); + oracle.setBlobScalar(_blobScalar); + oracle.setL1BaseFeeAndBlobBaseFee(_baseFee, _blobBaseFee); + + assertEq( + oracle.getL1Fee(_data), + (_commitScalar * _baseFee + _blobScalar * _blobBaseFee * _data.length) / PRECISION + ); + } } From 3aa241eb30e1fc2cf188ffea147faf5489026b37 Mon Sep 17 00:00:00 2001 From: Xi Lin Date: Fri, 24 May 2024 14:48:16 +0800 Subject: [PATCH 2/2] fix comments Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com> --- contracts/src/L2/predeploys/L1GasPriceOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/L2/predeploys/L1GasPriceOracle.sol b/contracts/src/L2/predeploys/L1GasPriceOracle.sol index 4052cb6e48..24ffc2ab53 100644 --- a/contracts/src/L2/predeploys/L1GasPriceOracle.sol +++ b/contracts/src/L2/predeploys/L1GasPriceOracle.sol @@ -36,7 +36,7 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle { /// @dev Thrown when the caller is not whitelisted. error ErrCallerNotWhitelisted(); - /// @dev Thrown when we enable Curie fork in Curie fork. + /// @dev Thrown when we enable Curie fork after Curie fork. error ErrAlreadyInCurieFork(); /*************