Commit 888a0f49 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

contracts: add `GasPriceOracle` contract (#2566)

parent f688a631
CrossDomainHashing_Test:test_l2TransactionHash() (gas: 78639)
GasPriceOracle_Test:test_baseFee() (gas: 11216)
GasPriceOracle_Test:test_gasPrice() (gas: 11205)
GasPriceOracle_Test:test_l1BaseFee() (gas: 10626)
GasPriceOracle_Test:test_onlyOwnerSetDecimals() (gas: 10575)
GasPriceOracle_Test:test_onlyOwnerSetOverhead() (gas: 10599)
GasPriceOracle_Test:test_onlyOwnerSetScalar() (gas: 10640)
GasPriceOracle_Test:test_owner() (gas: 9762)
GasPriceOracle_Test:test_setDecimals() (gas: 36798)
GasPriceOracle_Test:test_setGasPriceReverts() (gas: 11659)
GasPriceOracle_Test:test_setL1BaseFeeReverts() (gas: 11658)
GasPriceOracle_Test:test_setOverhead() (gas: 36767)
GasPriceOracle_Test:test_setScalar() (gas: 36840)
GasPriceOracle_Test:test_storageLayout() (gas: 86683)
L1BlockTest:test_basefee() (gas: 7575)
L1BlockTest:test_hash() (gas: 7552)
L1BlockTest:test_number() (gas: 7651)
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
/* External Imports */
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Lib_BedrockPredeployAddresses } from "../libraries/Lib_BedrockPredeployAddresses.sol";
import { L1Block } from "../L2/L1Block.sol";
/**
* @title GasPriceOracle
* @dev This contract maintains the variables responsible for computing the L1
* portion of the total fee charged on L2. The values stored in the contract
* are looked up as part of the L2 state transition function and used to compute
* the total fee paid by the user.
* The contract exposes an API that is useful for knowing how large the L1
* portion of their transaction fee will be.
* This predeploy is found at 0x420000000000000000000000000000000000000F in the
* L2 state.
* This contract should be behind an upgradable proxy such that when the gas
* prices change, the values can be updated accordingly.
*/
contract GasPriceOracle is Ownable {
/*************
* Variables *
*************/
// backwards compatibility
uint256 internal spacer0;
uint256 internal spacer1;
// Amortized cost of batch submission per transaction
uint256 public overhead;
// Value to scale the fee up by
uint256 public scalar;
// Number of decimals of the scalar
uint256 public decimals;
/***************
* Constructor *
***************/
/**
* @param _owner Address that will initially own this contract.
*/
constructor(address _owner) Ownable() {
transferOwnership(_owner);
}
/**********
* Events *
**********/
event OverheadUpdated(uint256);
event ScalarUpdated(uint256);
event DecimalsUpdated(uint256);
/********************
* Public Functions *
********************/
// legacy backwards compat
function gasPrice() public returns (uint256) {
return block.basefee;
}
function baseFee() public returns (uint256) {
return block.basefee;
}
function l1BaseFee() public view returns (uint256) {
return L1Block(Lib_BedrockPredeployAddresses.L1_BLOCK_ATTRIBUTES).basefee();
}
/**
* Allows the owner to modify the overhead.
* @param _overhead New overhead
*/
// slither-disable-next-line external-function
function setOverhead(uint256 _overhead) public onlyOwner {
overhead = _overhead;
emit OverheadUpdated(_overhead);
}
/**
* Allows the owner to modify the scalar.
* @param _scalar New scalar
*/
// slither-disable-next-line external-function
function setScalar(uint256 _scalar) public onlyOwner {
scalar = _scalar;
emit ScalarUpdated(_scalar);
}
/**
* Allows the owner to modify the decimals.
* @param _decimals New decimals
*/
// slither-disable-next-line external-function
function setDecimals(uint256 _decimals) public onlyOwner {
decimals = _decimals;
emit DecimalsUpdated(_decimals);
}
/**
* Computes the L1 portion of the fee
* based on the size of the RLP encoded tx
* and the current l1BaseFee
* @param _data Unsigned RLP encoded tx, 6 elements
* @return L1 fee that should be paid for the tx
*/
// slither-disable-next-line external-function
function getL1Fee(bytes memory _data) public 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;
}
// solhint-disable max-line-length
/**
* Computes the amount of L1 gas used for a transaction
* The overhead represents the per batch gas overhead of
* posting both transaction and state roots to L1 given larger
* batch sizes.
* 4 gas for 0 byte
* https://github.com/ethereum/go-ethereum/blob/9ada4a2e2c415e6b0b51c50e901336872e028872/params/protocol_params.go#L33
* 16 gas for non zero byte
* https://github.com/ethereum/go-ethereum/blob/9ada4a2e2c415e6b0b51c50e901336872e028872/params/protocol_params.go#L87
* This will need to be updated if calldata gas prices change
* Account for the transaction being unsigned
* Padding is added to account for lack of signature on transaction
* 1 byte for RLP V prefix
* 1 byte for V
* 1 byte for RLP R prefix
* 32 bytes for R
* 1 byte for RLP S prefix
* 32 bytes for S
* Total: 68 bytes of padding
* @param _data Unsigned RLP encoded tx, 6 elements
* @return Amount of L1 gas used for a transaction
*/
// solhint-enable max-line-length
function getL1GasUsed(bytes memory _data) public view returns (uint256) {
uint256 total = 0;
uint256 length = _data.length;
for (uint256 i = 0; i < length; i++) {
if (_data[i] == 0) {
total += 4;
} else {
total += 16;
}
}
uint256 unsigned = total + overhead;
return unsigned + (68 * 16);
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import { CommonTest } from "./CommonTest.t.sol";
import { GasPriceOracle } from "../L2/GasPriceOracle.sol";
import { L1Block } from "../L2/L1Block.sol";
import { Lib_BedrockPredeployAddresses } from "../libraries/Lib_BedrockPredeployAddresses.sol";
import { console } from "forge-std/console.sol";
contract GasPriceOracle_Test is CommonTest {
event OverheadUpdated(uint256);
event ScalarUpdated(uint256);
event DecimalsUpdated(uint256);
GasPriceOracle gasOracle;
L1Block l1Block;
address depositor;
function setUp() external {
// place the L1Block contract at the predeploy address
vm.etch(
Lib_BedrockPredeployAddresses.L1_BLOCK_ATTRIBUTES,
address(new L1Block()).code
);
l1Block = L1Block(Lib_BedrockPredeployAddresses.L1_BLOCK_ATTRIBUTES);
depositor = l1Block.DEPOSITOR_ACCOUNT();
// We are not setting the gas oracle at its predeploy
// address for simplicity purposes. Nothing in this test
// requires it to be at a particular address
gasOracle = new GasPriceOracle(alice);
// set the initial L1 context values
uint64 number = 10;
uint64 timestamp = 11;
uint256 basefee = 100;
bytes32 hash = bytes32(uint256(64));
uint64 sequenceNumber = 0;
vm.prank(depositor);
l1Block.setL1BlockValues(
number,
timestamp,
basefee,
hash,
sequenceNumber
);
}
function test_owner() external {
// alice is passed into the constructor of the gasOracle
assertEq(gasOracle.owner(), alice);
}
function test_storageLayout() external {
// the overhead is at slot 3
vm.prank(gasOracle.owner());
gasOracle.setOverhead(456);
assertEq(
456,
uint256(vm.load(address(gasOracle), bytes32(uint256(3))))
);
// scalar is at slot 4
vm.prank(gasOracle.owner());
gasOracle.setScalar(333);
assertEq(
333,
uint256(vm.load(address(gasOracle), bytes32(uint256(4))))
);
// decimals is at slot 5
vm.prank(gasOracle.owner());
gasOracle.setDecimals(222);
assertEq(
222,
uint256(vm.load(address(gasOracle), bytes32(uint256(5))))
);
}
function test_l1BaseFee() external {
uint256 l1BaseFee = gasOracle.l1BaseFee();
assertEq(l1BaseFee, 100);
}
function test_gasPrice() external {
vm.fee(100);
uint256 gasPrice = gasOracle.gasPrice();
console.log(gasPrice);
assertEq(gasPrice, 100);
}
function test_baseFee() external {
vm.fee(64);
uint256 gasPrice = gasOracle.baseFee();
console.log(gasPrice);
assertEq(gasPrice, 64);
}
function test_setGasPriceReverts() external {
vm.prank(gasOracle.owner());
(bool success, bytes memory returndata) = address(gasOracle).call(
abi.encodeWithSignature(
"setGasPrice(uint256)",
1
)
);
assertEq(success, false);
assertEq(returndata, hex"");
}
function test_setL1BaseFeeReverts() external {
vm.prank(gasOracle.owner());
(bool success, bytes memory returndata) = address(gasOracle).call(
abi.encodeWithSignature(
"setL1BaseFee(uint256)",
1
)
);
assertEq(success, false);
assertEq(returndata, hex"");
}
function test_setOverhead() external {
vm.expectEmit(true, true, true, true);
emit OverheadUpdated(1234);
vm.prank(gasOracle.owner());
gasOracle.setOverhead(1234);
assertEq(gasOracle.overhead(), 1234);
}
function test_onlyOwnerSetOverhead() external {
vm.expectRevert("Ownable: caller is not the owner");
gasOracle.setOverhead(0);
}
function test_setScalar() external {
vm.expectEmit(true, true, true, true);
emit ScalarUpdated(666);
vm.prank(gasOracle.owner());
gasOracle.setScalar(666);
assertEq(gasOracle.scalar(), 666);
}
function test_onlyOwnerSetScalar() external {
vm.expectRevert("Ownable: caller is not the owner");
gasOracle.setScalar(0);
}
function test_setDecimals() external {
vm.expectEmit(true, true, true, true);
emit DecimalsUpdated(18);
vm.prank(gasOracle.owner());
gasOracle.setDecimals(18);
assertEq(gasOracle.decimals(), 18);
}
function test_onlyOwnerSetDecimals() external {
vm.expectRevert("Ownable: caller is not the owner");
gasOracle.setDecimals(0);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment