Skip to content

Commit

Permalink
contracts: update OVM_GasPriceOracle
Browse files Browse the repository at this point in the history
Add a setter for L1 base fee. The trusted oracle can update it over
time. Eventually this will become trustless, but this is a quick and
easy approach for now.

Also add a setter/getter for the per batch overhead and the scalar.

Also emit events for when the values are updated. This will make it much
easier to track historical gas prices over time.

Add tests for new functionality. L2 geth will consume the new value in
the `OVM_GasPriceOracle`

Add getters that make it easy for users to know the L1 costs.
- `getL1Fee` returns the L1 fee given the current L1 base fee known by
  the L2 node
- `getL1GasUsed` counts the bytes and creates a sum for the gas cost of
  submitting the data to L1
  • Loading branch information
tynes authored and smartcontracts committed Oct 1, 2021
1 parent cd9e62c commit dfdbe3c
Show file tree
Hide file tree
Showing 6 changed files with 487 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-llamas-kneel.md
@@ -0,0 +1,5 @@
---
'@eth-optimism/contracts': patch
---

Add getter and setter to `OVM_GasPriceOracle` for the l1 base fee
86 changes: 75 additions & 11 deletions packages/contracts/bin/take-dump.ts
Expand Up @@ -3,31 +3,95 @@ import * as fs from 'fs'
import * as path from 'path'
import * as mkdirp from 'mkdirp'

const ensure = (value, key) => {
if (typeof value === 'undefined' || value === null || Number.isNaN(value)) {
throw new Error(`${key} is undefined, null or NaN`)
}
}

/* Internal Imports */
import { makeL2GenesisFile } from '../src/make-genesis'
;(async () => {
const outdir = path.resolve(__dirname, '../dist/dumps')
const outfile = path.join(outdir, 'state-dump.latest.json')
mkdirp.sync(outdir)

const env = process.env

// An account that represents the owner of the whitelist
const whitelistOwner = env.WHITELIST_OWNER
// The gas price oracle owner, can update values is GasPriceOracle L2 predeploy
const gasPriceOracleOwner = env.GAS_PRICE_ORACLE_OWNER
// The initial overhead value for the GasPriceOracle
const gasPriceOracleOverhead = parseInt(
env.GAS_PRICE_ORACLE_OVERHEAD || '2750',
10
)
// The initial scalar value for the GasPriceOracle. The actual
// scalar is scaled downwards by the number of decimals
const gasPriceOracleScalar = parseInt(
env.GAS_PRICE_ORACLE_SCALAR || '1500000',
10
)
// The initial decimals that scale down the scalar in the GasPriceOracle
const gasPriceOracleDecimals = parseInt(
env.GAS_PRICE_ORACLE_DECIMALS || '6',
10
)
// The initial L1 base fee in the GasPriceOracle. This determines how
// expensive the L1 portion of the transaction fee is.
const gasPriceOracleL1BaseFee = parseInt(
env.GAS_PRICE_ORACLE_L1_BASE_FEE || '1',
10
)
// The initial L2 gas price set in the GasPriceOracle
const gasPriceOracleGasPrice = parseInt(
env.GAS_PRICE_ORACLE_GAS_PRICE || '1',
10
)
// The L2 block gas limit, used in the L2 block headers as well to limit
// the amount of execution for a single block.
const l2BlockGasLimit = parseInt(env.L2_BLOCK_GAS_LIMIT, 10)
// The L2 chain id, added to the chain config
const l2ChainId = parseInt(env.L2_CHAIN_ID, 10)
// The block signer address, added to the block extradata for clique consensus
const blockSignerAddress = env.BLOCK_SIGNER_ADDRESS
// The L1 standard bridge address for cross domain messaging
const l1StandardBridgeAddress = env.L1_STANDARD_BRIDGE_ADDRESS
// The L1 fee wallet address, used to restrict the account that fees on L2 can
// be withdrawn to on L1
const l1FeeWalletAddress = env.L1_FEE_WALLET_ADDRESS
// The L1 cross domain messenger address, used for cross domain messaging
const l1CrossDomainMessengerAddress = env.L1_CROSS_DOMAIN_MESSENGER_ADDRESS

ensure(l2BlockGasLimit, 'L2_BLOCK_GAS_LIMIT')
ensure(l2ChainId, 'L2_CHAIN_ID')
ensure(blockSignerAddress, 'BLOCK_SIGNER_ADDRESS')
ensure(l1StandardBridgeAddress, 'L1_STANDARD_BRIDGE_ADDRESS')
ensure(l1FeeWalletAddress, 'L1_FEE_WALLET_ADDRESS')
ensure(l1CrossDomainMessengerAddress, 'L1_CROSS_DOMAIN_MESSENGER_ADDRESS')

// Basic warning so users know that the whitelist will be disabled if the owner is the zero address.
if (process.env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) {
if (env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) {
console.log(
'WARNING: whitelist owner is address(0), whitelist will be disabled'
)
}

const genesis = await makeL2GenesisFile({
whitelistOwner: process.env.WHITELIST_OWNER,
gasPriceOracleOwner: process.env.GAS_PRICE_ORACLE_OWNER,
initialGasPrice: 0,
l2BlockGasLimit: parseInt(process.env.L2_BLOCK_GAS_LIMIT, 10),
l2ChainId: parseInt(process.env.L2_CHAIN_ID, 10),
blockSignerAddress: process.env.BLOCK_SIGNER_ADDRESS,
l1StandardBridgeAddress: process.env.L1_STANDARD_BRIDGE_ADDRESS,
l1FeeWalletAddress: process.env.L1_FEE_WALLET_ADDRESS,
l1CrossDomainMessengerAddress:
process.env.L1_CROSS_DOMAIN_MESSENGER_ADDRESS,
whitelistOwner,
gasPriceOracleOwner,
gasPriceOracleOverhead,
gasPriceOracleScalar,
gasPriceOracleL1BaseFee,
gasPriceOracleGasPrice,
gasPriceOracleDecimals,
l2BlockGasLimit,
l2ChainId,
blockSignerAddress,
l1StandardBridgeAddress,
l1FeeWalletAddress,
l1CrossDomainMessengerAddress,
})

fs.writeFileSync(outfile, JSON.stringify(genesis, null, 4))
Expand Down
152 changes: 147 additions & 5 deletions packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol
@@ -1,24 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;

/* Internal Imports */
import { iOVM_GasPriceOracle } from "./iOVM_GasPriceOracle.sol";

/* External Imports */
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";

/**
* @title OVM_GasPriceOracle
* @dev This contract exposes the current l2 gas price, a measure of how congested the network
* currently is. This measure is used by the Sequencer to determine what fee to charge for
* transactions. When the system is more congested, the l2 gas price will increase and fees
* will also increase as a result.
*
* All public variables are set while generating the initial L2 state. The
* constructor doesn't run in practice as the L2 state generation script uses
* the deployed bytecode instead of running the initcode.
*/
contract OVM_GasPriceOracle is Ownable {
contract OVM_GasPriceOracle is Ownable, iOVM_GasPriceOracle {

/*************
* Variables *
*************/

// Current l2 gas price

// Current L2 gas price
uint256 public gasPrice;
// Current L1 base fee
uint256 public l1BaseFee;
// 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 *
Expand All @@ -28,12 +45,10 @@ contract OVM_GasPriceOracle is Ownable {
* @param _owner Address that will initially own this contract.
*/
constructor(
address _owner,
uint256 _initialGasPrice
address _owner
)
Ownable()
{
setGasPrice(_initialGasPrice);
transferOwnership(_owner);
}

Expand All @@ -50,8 +65,135 @@ contract OVM_GasPriceOracle is Ownable {
uint256 _gasPrice
)
public
override
onlyOwner
{
gasPrice = _gasPrice;
emit GasPriceUpdated(_gasPrice);
}

/**
* Allows the owner to modify the l1 base fee.
* @param _baseFee New l1 base fee
*/
function setL1BaseFee(
uint256 _baseFee
)
public
override
onlyOwner
{
l1BaseFee = _baseFee;
emit L1BaseFeeUpdated(_baseFee);
}

/**
* Allows the owner to modify the overhead.
* @param _overhead New overhead
*/
function setOverhead(
uint256 _overhead
)
public
override
onlyOwner
{
overhead = _overhead;
emit OverheadUpdated(_overhead);
}

/**
* Allows the owner to modify the scalar.
* @param _scalar New scalar
*/
function setScalar(
uint256 _scalar
)
public
override
onlyOwner
{
scalar = _scalar;
emit ScalarUpdated(_scalar);
}

/**
* Allows the owner to modify the decimals.
* @param _decimals New decimals
*/
function setDecimals(
uint256 _decimals
)
public
override
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
*/
function getL1Fee(bytes memory _data)
public
view
override
returns (
uint256
)
{
uint256 l1GasUsed = getL1GasUsed(_data);
uint256 l1Fee = SafeMath.mul(l1GasUsed, l1BaseFee);
uint256 divisor = 10**decimals;
uint256 unscaled = SafeMath.mul(l1Fee, scalar);
uint256 scaled = SafeMath.div(unscaled, divisor);
return scaled;
}

/**
* 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
*/
function getL1GasUsed(bytes memory _data)
public
view
override
returns (
uint256
)
{
uint256 total = 0;
for (uint256 i = 0; i < _data.length; i++) {
if (_data[i] == 0) {
total += 4;
} else {
total += 16;
}
}
uint256 unsigned = SafeMath.add(total, overhead);
return SafeMath.add(unsigned, 68 * 16);
}
}
30 changes: 30 additions & 0 deletions packages/contracts/contracts/L2/predeploys/iOVM_GasPriceOracle.sol
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;

/**
* @title iOVM_GasPriceOracle
*/
interface iOVM_GasPriceOracle {

/**********
* Events *
**********/

event GasPriceUpdated(uint256);
event L1BaseFeeUpdated(uint256);
event OverheadUpdated(uint256);
event ScalarUpdated(uint256);
event DecimalsUpdated(uint256);

/********************
* Public Functions *
********************/

function setGasPrice(uint256 _gasPrice) external;
function setL1BaseFee(uint256 _baseFee) external;
function setOverhead(uint256 _overhead) external;
function setScalar(uint256 _scalar) external;
function setDecimals(uint256 _decimals) external;
function getL1Fee(bytes memory _data) external returns (uint256);
function getL1GasUsed(bytes memory _data) external returns (uint256);
}
18 changes: 15 additions & 3 deletions packages/contracts/src/make-genesis.ts
Expand Up @@ -14,8 +14,16 @@ export interface RollupDeployConfig {
whitelistOwner: string
// Address that will own the L2 gas price oracle.
gasPriceOracleOwner: string
// Initial value for the L2 gas price.
initialGasPrice: number
// Overhead value of the gas price oracle
gasPriceOracleOverhead: number
// Scalar value of the gas price oracle
gasPriceOracleScalar: number
// L1 base fee of the gas price oracle
gasPriceOracleL1BaseFee: number
// L2 gas price of the gas price oracle
gasPriceOracleGasPrice: number
// Number of decimals of the gas price oracle scalar
gasPriceOracleDecimals: number
// Initial value for the L2 block gas limit.
l2BlockGasLimit: number
// Chain ID to give the L2 network.
Expand Down Expand Up @@ -52,7 +60,11 @@ export const makeL2GenesisFile = async (
},
OVM_GasPriceOracle: {
_owner: cfg.gasPriceOracleOwner,
gasPrice: cfg.initialGasPrice,
gasPrice: cfg.gasPriceOracleGasPrice,
l1BaseFee: cfg.gasPriceOracleL1BaseFee,
overhead: cfg.gasPriceOracleOverhead,
scalar: cfg.gasPriceOracleScalar,
decimals: cfg.gasPriceOracleDecimals,
},
L2StandardBridge: {
l1TokenBridge: cfg.l1StandardBridgeAddress,
Expand Down

0 comments on commit dfdbe3c

Please sign in to comment.