feat(contracts): update L1GasPriceOracle for Curie fork (#1343)

Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>
This commit is contained in:
Xi Lin
2024-05-28 13:36:56 +08:00
committed by GitHub
parent 07b8ae20ac
commit 7059ad0ed4
3 changed files with 323 additions and 61 deletions

View File

@@ -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;
}

View File

@@ -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 after 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,15 +121,116 @@ 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) {
if (isCurie) {
// It is near zero since we put all transactions to blob.
return 0;
} else {
return _getL1GasUsedBeforeCurie(_data);
}
}
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IL1GasPriceOracle
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);
}
/************************
* Restricted Functions *
************************/
/// @notice Allows the owner to modify the overhead.
/// @param _overhead New overhead
function setOverhead(uint256 _overhead) external onlyOwner {
if (_overhead > MAX_OVERHEAD) revert ErrExceedMaxOverhead();
overhead = _overhead;
emit OverheadUpdated(_overhead);
}
/// Allows the owner to modify the scalar.
/// @param _scalar New scalar
function setScalar(uint256 _scalar) external onlyOwner {
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.
function updateWhitelist(address _newWhitelist) external onlyOwner {
address _oldWhitelist = address(whitelist);
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 {
@@ -85,48 +245,22 @@ contract L1GasPriceOracle is OwnableBase, IL1GasPriceOracle {
}
}
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IL1GasPriceOracle
function setL1BaseFee(uint256 _l1BaseFee) external override {
require(whitelist.isSenderAllowed(msg.sender), "Not whitelisted sender");
l1BaseFee = _l1BaseFee;
emit L1BaseFeeUpdated(_l1BaseFee);
/// @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;
}
/************************
* Restricted Functions *
************************/
/// @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");
overhead = _overhead;
emit OverheadUpdated(_overhead);
}
/// Allows the owner to modify the scalar.
/// @param _scalar New scalar
function setScalar(uint256 _scalar) external onlyOwner {
require(_scalar <= MAX_SCALE, "exceed maximum scale");
scalar = _scalar;
emit ScalarUpdated(_scalar);
}
/// @notice Update whitelist contract.
/// @dev This function can only called by contract owner.
/// @param _newWhitelist The address of new whitelist contract.
function updateWhitelist(address _newWhitelist) external onlyOwner {
address _oldWhitelist = address(whitelist);
whitelist = IWhitelist(_newWhitelist);
emit UpdateWhitelist(_oldWhitelist, _newWhitelist);
/// @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;
}
}

View File

@@ -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
);
}
}