Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions contracts-bedrock/.gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
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)
Expand Down
158 changes: 158 additions & 0 deletions contracts-bedrock/contracts/L2/GasPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// 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);
}
}
170 changes: 170 additions & 0 deletions contracts-bedrock/contracts/test/GasPriceOracle.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//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);
}
}