Commit 7bd88e81 authored by Mark Tyneway's avatar Mark Tyneway Committed by Kelvin Fichter

l2geth: new fee scheme

Previously, the L1 gas price was being fetched from
a remote node and being held in memory. Now the L1
gas price is in a smart contract and the `SyncService`
will periodically update the L1 gas price in an in memory
cache to ensure that users that are sending transactions
are paying enough.

This also reads the overhead from the `OVM_GasPriceOracle`
and rejects transactions with too low of a fee.

The `tx.gasPrice` can now be variable, it no longer
will reject transactions if the gas price isn't exactly
the hardcoded number.

The cache is now updated when the sender of a transaction included
in the chain is from the current gas price oracle owner address.
This depends on the cache being initialized at startup.
parent d89b5005
---
'@eth-optimism/l2geth': patch
---
Use `OVM_GasPriceOracle` based L1 base fee instead of fetching it from remote
......@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
......@@ -61,6 +62,8 @@ type StateTransition struct {
data []byte
state vm.StateDB
evm *vm.EVM
// UsingOVM
l1Fee *big.Int
}
// Message represents a message sent to a contract.
......@@ -122,6 +125,15 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo
// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
l1Fee := new(big.Int)
if rcfg.UsingOVM {
if msg.GasPrice().Cmp(common.Big0) != 0 {
// Compute the L1 fee before the state transition
// so it only has to be read from state one time.
l1Fee, _ = fees.CalculateL1MsgFee(msg, evm.StateDB, nil)
}
}
return &StateTransition{
gp: gp,
evm: evm,
......@@ -130,6 +142,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
value: msg.Value(),
data: msg.Data(),
state: evm.StateDB,
l1Fee: l1Fee,
}
}
......@@ -163,6 +176,15 @@ func (st *StateTransition) useGas(amount uint64) error {
func (st *StateTransition) buyGas() error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if rcfg.UsingOVM {
// Only charge the L1 fee for QueueOrigin sequencer transactions
if st.msg.QueueOrigin() == types.QueueOriginSequencer {
mgval = mgval.Add(mgval, st.l1Fee)
if st.msg.CheckNonce() {
log.Debug("Adding L1 fee", "l1-fee", st.l1Fee)
}
}
}
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
......@@ -243,7 +265,16 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
}
}
st.refundGas()
st.state.AddBalance(evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
if rcfg.UsingOVM {
// The L2 Fee is the same as the fee that is charged in the normal geth
// codepath. Add the L1 fee to the L2 fee for the total fee that is sent
// to the sequencer.
l2Fee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)
fee := new(big.Int).Add(st.l1Fee, l2Fee)
st.state.AddBalance(evm.Coinbase, fee)
} else {
st.state.AddBalance(evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
}
return ret, st.gasUsed(), vmerr != nil, err
}
......
......@@ -565,8 +565,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
if !rcfg.UsingOVM {
// This check is done in SyncService.verifyFee
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
}
// Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul)
......
......@@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
)
//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
......@@ -214,14 +213,12 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return nil
}
func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit }
func (tx *Transaction) L2Gas() uint64 { return fees.DecodeL2GasLimitU64(tx.data.GasLimit) }
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit }
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) SetNonce(nonce uint64) { tx.data.AccountNonce = nonce }
// To returns the recipient address of the transaction.
......
......@@ -6,23 +6,28 @@ import (
"sync"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rollup/fees"
)
// RollupOracle holds the L1 and L2 gas prices for fee calculation
type RollupOracle struct {
l1GasPrice *big.Int
l2GasPrice *big.Int
overhead *big.Int
scalar *big.Float
l1GasPriceLock sync.RWMutex
l2GasPriceLock sync.RWMutex
overheadLock sync.RWMutex
scalarLock sync.RWMutex
}
// NewRollupOracle returns an initialized RollupOracle
func NewRollupOracle() *RollupOracle {
return &RollupOracle{
l1GasPrice: new(big.Int),
l2GasPrice: new(big.Int),
l1GasPriceLock: sync.RWMutex{},
l2GasPriceLock: sync.RWMutex{},
l1GasPrice: new(big.Int),
l2GasPrice: new(big.Int),
overhead: new(big.Int),
scalar: new(big.Float),
}
}
......@@ -59,3 +64,38 @@ func (gpo *RollupOracle) SetL2GasPrice(gasPrice *big.Int) error {
log.Info("Set L2 Gas Price", "gasprice", gpo.l2GasPrice)
return nil
}
// SuggestOverhead returns the cached overhead value from the
// OVM_GasPriceOracle
func (gpo *RollupOracle) SuggestOverhead(ctx context.Context) (*big.Int, error) {
gpo.overheadLock.RLock()
defer gpo.overheadLock.RUnlock()
return gpo.overhead, nil
}
// SetOverhead caches the overhead value that is set in the
// OVM_GasPriceOracle
func (gpo *RollupOracle) SetOverhead(overhead *big.Int) error {
gpo.overheadLock.Lock()
defer gpo.overheadLock.Unlock()
gpo.overhead = overhead
log.Info("Set batch overhead", "overhead", overhead)
return nil
}
// SuggestScalar returns the cached scalar value
func (gpo *RollupOracle) SuggestScalar(ctx context.Context) (*big.Float, error) {
gpo.scalarLock.RLock()
defer gpo.scalarLock.RUnlock()
return gpo.scalar, nil
}
// SetScalar sets the scalar value held in the OVM_GasPriceOracle
func (gpo *RollupOracle) SetScalar(scalar *big.Int, decimals *big.Int) error {
gpo.scalarLock.Lock()
defer gpo.scalarLock.Unlock()
value := fees.ScaleDecimals(scalar, decimals)
gpo.scalar = value
log.Info("Set scalar", "scalar", gpo.scalar)
return nil
}
......@@ -51,10 +51,6 @@ import (
var errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")
// TEMPORARY: Set the gas price to 0 until message passing and ETH value work again.
// Otherwise the integration tests won't pass because the accounts have no ETH.
var bigDefaultGasPrice = new(big.Int).SetUint64(0)
// PublicEthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicEthereumAPI struct {
......@@ -66,9 +62,13 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
return &PublicEthereumAPI{b}
}
// GasPrice always returns 1 gwei. See `DoEstimateGas` below for context.
// GasPrice returns the L2 gas price in the OVM_GasPriceOracle
func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
return (*hexutil.Big)(bigDefaultGasPrice), nil
gasPrice, err := s.b.SuggestL2GasPrice(context.Background())
if err != nil {
return nil, err
}
return (*hexutil.Big)(gasPrice), nil
}
// ProtocolVersion returns the current Ethereum protocol version this node supports
......
......@@ -506,7 +506,7 @@ func (w *worker) mainLoop() {
}
w.pendingMu.Unlock()
} else {
log.Debug("Problem committing transaction: %w", err)
log.Debug("Problem committing transaction", "msg", err)
}
case ev := <-w.txsCh:
......
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package bindings
import (
"math/big"
"strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = abi.U256
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = event.NewSubscription
)
// GasPriceOracleABI is the input ABI used to generate the binding from.
const GasPriceOracleABI = "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"GasPriceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"L1BaseFeeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"OverheadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"ScalarUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasPrice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"getL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"getL1GasUsed\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1BaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_gasPrice\",\"type\":\"uint256\"}],\"name\":\"setGasPrice\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_baseFee\",\"type\":\"uint256\"}],\"name\":\"setL1BaseFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"}],\"name\":\"setOverhead\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"}],\"name\":\"setScalar\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
// GasPriceOracleBin is the compiled bytecode used for deploying new contracts.
var GasPriceOracleBin = "0x6080604052610abe6003556216e360600455600660055534801561002257600080fd5b50604051610be4380380610be48339818101604052602081101561004557600080fd5b50516000610051610098565b600080546001600160a01b0319166001600160a01b038316908117825560405192935091600080516020610bc4833981519152908290a3506100928161009c565b506101ad565b3390565b6100a4610098565b6001600160a01b03166100b561019e565b6001600160a01b031614610110576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b0381166101555760405162461bcd60e51b8152600401808060200182810382526026815260200180610b9e6026913960400191505060405180910390fd5b600080546040516001600160a01b0380851693921691600080516020610bc483398151915291a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031690565b6109e2806101bc6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638da5cb5b1161008c578063de26c4a111610066578063de26c4a114610261578063f2fde38b14610307578063f45e65d81461032d578063fe173b9714610335576100ea565b80638da5cb5b14610203578063bede39b514610227578063bf1fe42014610244576100ea565b806349948e0e116100c857806349948e0e14610130578063519b4bd3146101d657806370465597146101de578063715018a6146101fb576100ea565b80630c18c162146100ef578063313ce567146101095780633577afc514610111575b600080fd5b6100f761033d565b60408051918252519081900360200190f35b6100f7610343565b61012e6004803603602081101561012757600080fd5b5035610349565b005b6100f76004803603602081101561014657600080fd5b81019060208101813564010000000081111561016157600080fd5b82018360208201111561017357600080fd5b8035906020019184600183028401116401000000008311171561019557600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506103e6945050505050565b6100f7610435565b61012e600480360360208110156101f457600080fd5b503561043b565b61012e6104d8565b61020b610584565b604080516001600160a01b039092168252519081900360200190f35b61012e6004803603602081101561023d57600080fd5b5035610593565b61012e6004803603602081101561025a57600080fd5b5035610630565b6100f76004803603602081101561027757600080fd5b81019060208101813564010000000081111561029257600080fd5b8201836020820111156102a457600080fd5b803590602001918460018302840111640100000000831117156102c657600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506106cd945050505050565b61012e6004803603602081101561031d57600080fd5b50356001600160a01b0316610739565b6100f761083b565b6100f7610841565b60035481565b60055481565b610351610847565b6001600160a01b0316610362610584565b6001600160a01b0316146103ab576040805162461bcd60e51b815260206004820181905260248201526000805160206109b6833981519152604482015290519081900360640190fd5b60038190556040805182815290517f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb49181900360200190a150565b6000806103f2836106cd565b905060006104028260025461084b565b90506000600554600a0a9050600061041c8360045461084b565b9050600061042a82846108ad565b979650505050505050565b60025481565b610443610847565b6001600160a01b0316610454610584565b6001600160a01b03161461049d576040805162461bcd60e51b815260206004820181905260248201526000805160206109b6833981519152604482015290519081900360640190fd5b60048190556040805182815290517f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a9181900360200190a150565b6104e0610847565b6001600160a01b03166104f1610584565b6001600160a01b03161461053a576040805162461bcd60e51b815260206004820181905260248201526000805160206109b6833981519152604482015290519081900360640190fd5b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b031690565b61059b610847565b6001600160a01b03166105ac610584565b6001600160a01b0316146105f5576040805162461bcd60e51b815260206004820181905260248201526000805160206109b6833981519152604482015290519081900360640190fd5b60028190556040805182815290517f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c449181900360200190a150565b610638610847565b6001600160a01b0316610649610584565b6001600160a01b031614610692576040805162461bcd60e51b815260206004820181905260248201526000805160206109b6833981519152604482015290519081900360640190fd5b60018190556040805182815290517ffcdccc6074c6c42e4bd578aa9870c697dc976a270968452d2b8c8dc369fae3969181900360200190a150565b600080805b8351811015610714578381815181106106e757fe5b01602001516001600160f81b0319166107055760048201915061070c565b6010820191505b6001016106d2565b50600061072382600354610914565b905061073181610440610914565b949350505050565b610741610847565b6001600160a01b0316610752610584565b6001600160a01b03161461079b576040805162461bcd60e51b815260206004820181905260248201526000805160206109b6833981519152604482015290519081900360640190fd5b6001600160a01b0381166107e05760405162461bcd60e51b815260040180806020018281038252602681526020018061096f6026913960400191505060405180910390fd5b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60045481565b60015481565b3390565b60008261085a575060006108a7565b8282028284828161086757fe5b04146108a45760405162461bcd60e51b81526004018080602001828103825260218152602001806109956021913960400191505060405180910390fd5b90505b92915050565b6000808211610903576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b81838161090c57fe5b049392505050565b6000828201838110156108a4576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fdfe4f776e61626c653a206e6577206f776e657220697320746865207a65726f2061646472657373536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572a164736f6c6343000706000a4f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573738be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0"
// DeployGasPriceOracle deploys a new Ethereum contract, binding an instance of GasPriceOracle to it.
func DeployGasPriceOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _owner common.Address) (common.Address, *types.Transaction, *GasPriceOracle, error) {
parsed, err := abi.JSON(strings.NewReader(GasPriceOracleABI))
if err != nil {
return common.Address{}, nil, nil, err
}
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(GasPriceOracleBin), backend, _owner)
if err != nil {
return common.Address{}, nil, nil, err
}
return address, tx, &GasPriceOracle{GasPriceOracleCaller: GasPriceOracleCaller{contract: contract}, GasPriceOracleTransactor: GasPriceOracleTransactor{contract: contract}, GasPriceOracleFilterer: GasPriceOracleFilterer{contract: contract}}, nil
}
// GasPriceOracle is an auto generated Go binding around an Ethereum contract.
type GasPriceOracle struct {
GasPriceOracleCaller // Read-only binding to the contract
GasPriceOracleTransactor // Write-only binding to the contract
GasPriceOracleFilterer // Log filterer for contract events
}
// GasPriceOracleCaller is an auto generated read-only Go binding around an Ethereum contract.
type GasPriceOracleCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// GasPriceOracleTransactor is an auto generated write-only Go binding around an Ethereum contract.
type GasPriceOracleTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// GasPriceOracleFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type GasPriceOracleFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// GasPriceOracleSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type GasPriceOracleSession struct {
Contract *GasPriceOracle // Generic contract binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// GasPriceOracleCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type GasPriceOracleCallerSession struct {
Contract *GasPriceOracleCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// GasPriceOracleTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type GasPriceOracleTransactorSession struct {
Contract *GasPriceOracleTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// GasPriceOracleRaw is an auto generated low-level Go binding around an Ethereum contract.
type GasPriceOracleRaw struct {
Contract *GasPriceOracle // Generic contract binding to access the raw methods on
}
// GasPriceOracleCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type GasPriceOracleCallerRaw struct {
Contract *GasPriceOracleCaller // Generic read-only contract binding to access the raw methods on
}
// GasPriceOracleTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type GasPriceOracleTransactorRaw struct {
Contract *GasPriceOracleTransactor // Generic write-only contract binding to access the raw methods on
}
// NewGasPriceOracle creates a new instance of GasPriceOracle, bound to a specific deployed contract.
func NewGasPriceOracle(address common.Address, backend bind.ContractBackend) (*GasPriceOracle, error) {
contract, err := bindGasPriceOracle(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &GasPriceOracle{GasPriceOracleCaller: GasPriceOracleCaller{contract: contract}, GasPriceOracleTransactor: GasPriceOracleTransactor{contract: contract}, GasPriceOracleFilterer: GasPriceOracleFilterer{contract: contract}}, nil
}
// NewGasPriceOracleCaller creates a new read-only instance of GasPriceOracle, bound to a specific deployed contract.
func NewGasPriceOracleCaller(address common.Address, caller bind.ContractCaller) (*GasPriceOracleCaller, error) {
contract, err := bindGasPriceOracle(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &GasPriceOracleCaller{contract: contract}, nil
}
// NewGasPriceOracleTransactor creates a new write-only instance of GasPriceOracle, bound to a specific deployed contract.
func NewGasPriceOracleTransactor(address common.Address, transactor bind.ContractTransactor) (*GasPriceOracleTransactor, error) {
contract, err := bindGasPriceOracle(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &GasPriceOracleTransactor{contract: contract}, nil
}
// NewGasPriceOracleFilterer creates a new log filterer instance of GasPriceOracle, bound to a specific deployed contract.
func NewGasPriceOracleFilterer(address common.Address, filterer bind.ContractFilterer) (*GasPriceOracleFilterer, error) {
contract, err := bindGasPriceOracle(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &GasPriceOracleFilterer{contract: contract}, nil
}
// bindGasPriceOracle binds a generic wrapper to an already deployed contract.
func bindGasPriceOracle(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(GasPriceOracleABI))
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_GasPriceOracle *GasPriceOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
return _GasPriceOracle.Contract.GasPriceOracleCaller.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_GasPriceOracle *GasPriceOracleRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _GasPriceOracle.Contract.GasPriceOracleTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_GasPriceOracle *GasPriceOracleRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _GasPriceOracle.Contract.GasPriceOracleTransactor.contract.Transact(opts, method, params...)
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_GasPriceOracle *GasPriceOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
return _GasPriceOracle.Contract.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_GasPriceOracle *GasPriceOracleTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _GasPriceOracle.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_GasPriceOracle *GasPriceOracleTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _GasPriceOracle.Contract.contract.Transact(opts, method, params...)
}
// Decimals is a free data retrieval call binding the contract method 0x313ce567.
//
// Solidity: function decimals() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) Decimals(opts *bind.CallOpts) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "decimals")
return *ret0, err
}
// Decimals is a free data retrieval call binding the contract method 0x313ce567.
//
// Solidity: function decimals() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) Decimals() (*big.Int, error) {
return _GasPriceOracle.Contract.Decimals(&_GasPriceOracle.CallOpts)
}
// Decimals is a free data retrieval call binding the contract method 0x313ce567.
//
// Solidity: function decimals() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) Decimals() (*big.Int, error) {
return _GasPriceOracle.Contract.Decimals(&_GasPriceOracle.CallOpts)
}
// GasPrice is a free data retrieval call binding the contract method 0xfe173b97.
//
// Solidity: function gasPrice() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) GasPrice(opts *bind.CallOpts) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "gasPrice")
return *ret0, err
}
// GasPrice is a free data retrieval call binding the contract method 0xfe173b97.
//
// Solidity: function gasPrice() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) GasPrice() (*big.Int, error) {
return _GasPriceOracle.Contract.GasPrice(&_GasPriceOracle.CallOpts)
}
// GasPrice is a free data retrieval call binding the contract method 0xfe173b97.
//
// Solidity: function gasPrice() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) GasPrice() (*big.Int, error) {
return _GasPriceOracle.Contract.GasPrice(&_GasPriceOracle.CallOpts)
}
// GetL1Fee is a free data retrieval call binding the contract method 0x49948e0e.
//
// Solidity: function getL1Fee(bytes _data) constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) GetL1Fee(opts *bind.CallOpts, _data []byte) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "getL1Fee", _data)
return *ret0, err
}
// GetL1Fee is a free data retrieval call binding the contract method 0x49948e0e.
//
// Solidity: function getL1Fee(bytes _data) constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) GetL1Fee(_data []byte) (*big.Int, error) {
return _GasPriceOracle.Contract.GetL1Fee(&_GasPriceOracle.CallOpts, _data)
}
// GetL1Fee is a free data retrieval call binding the contract method 0x49948e0e.
//
// Solidity: function getL1Fee(bytes _data) constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) GetL1Fee(_data []byte) (*big.Int, error) {
return _GasPriceOracle.Contract.GetL1Fee(&_GasPriceOracle.CallOpts, _data)
}
// GetL1GasUsed is a free data retrieval call binding the contract method 0xde26c4a1.
//
// Solidity: function getL1GasUsed(bytes _data) constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) GetL1GasUsed(opts *bind.CallOpts, _data []byte) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "getL1GasUsed", _data)
return *ret0, err
}
// GetL1GasUsed is a free data retrieval call binding the contract method 0xde26c4a1.
//
// Solidity: function getL1GasUsed(bytes _data) constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) GetL1GasUsed(_data []byte) (*big.Int, error) {
return _GasPriceOracle.Contract.GetL1GasUsed(&_GasPriceOracle.CallOpts, _data)
}
// GetL1GasUsed is a free data retrieval call binding the contract method 0xde26c4a1.
//
// Solidity: function getL1GasUsed(bytes _data) constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) GetL1GasUsed(_data []byte) (*big.Int, error) {
return _GasPriceOracle.Contract.GetL1GasUsed(&_GasPriceOracle.CallOpts, _data)
}
// L1BaseFee is a free data retrieval call binding the contract method 0x519b4bd3.
//
// Solidity: function l1BaseFee() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) L1BaseFee(opts *bind.CallOpts) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "l1BaseFee")
return *ret0, err
}
// L1BaseFee is a free data retrieval call binding the contract method 0x519b4bd3.
//
// Solidity: function l1BaseFee() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) L1BaseFee() (*big.Int, error) {
return _GasPriceOracle.Contract.L1BaseFee(&_GasPriceOracle.CallOpts)
}
// L1BaseFee is a free data retrieval call binding the contract method 0x519b4bd3.
//
// Solidity: function l1BaseFee() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) L1BaseFee() (*big.Int, error) {
return _GasPriceOracle.Contract.L1BaseFee(&_GasPriceOracle.CallOpts)
}
// Overhead is a free data retrieval call binding the contract method 0x0c18c162.
//
// Solidity: function overhead() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) Overhead(opts *bind.CallOpts) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "overhead")
return *ret0, err
}
// Overhead is a free data retrieval call binding the contract method 0x0c18c162.
//
// Solidity: function overhead() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) Overhead() (*big.Int, error) {
return _GasPriceOracle.Contract.Overhead(&_GasPriceOracle.CallOpts)
}
// Overhead is a free data retrieval call binding the contract method 0x0c18c162.
//
// Solidity: function overhead() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) Overhead() (*big.Int, error) {
return _GasPriceOracle.Contract.Overhead(&_GasPriceOracle.CallOpts)
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() constant returns(address)
func (_GasPriceOracle *GasPriceOracleCaller) Owner(opts *bind.CallOpts) (common.Address, error) {
var (
ret0 = new(common.Address)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "owner")
return *ret0, err
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() constant returns(address)
func (_GasPriceOracle *GasPriceOracleSession) Owner() (common.Address, error) {
return _GasPriceOracle.Contract.Owner(&_GasPriceOracle.CallOpts)
}
// Owner is a free data retrieval call binding the contract method 0x8da5cb5b.
//
// Solidity: function owner() constant returns(address)
func (_GasPriceOracle *GasPriceOracleCallerSession) Owner() (common.Address, error) {
return _GasPriceOracle.Contract.Owner(&_GasPriceOracle.CallOpts)
}
// Scalar is a free data retrieval call binding the contract method 0xf45e65d8.
//
// Solidity: function scalar() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCaller) Scalar(opts *bind.CallOpts) (*big.Int, error) {
var (
ret0 = new(*big.Int)
)
out := ret0
err := _GasPriceOracle.contract.Call(opts, out, "scalar")
return *ret0, err
}
// Scalar is a free data retrieval call binding the contract method 0xf45e65d8.
//
// Solidity: function scalar() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleSession) Scalar() (*big.Int, error) {
return _GasPriceOracle.Contract.Scalar(&_GasPriceOracle.CallOpts)
}
// Scalar is a free data retrieval call binding the contract method 0xf45e65d8.
//
// Solidity: function scalar() constant returns(uint256)
func (_GasPriceOracle *GasPriceOracleCallerSession) Scalar() (*big.Int, error) {
return _GasPriceOracle.Contract.Scalar(&_GasPriceOracle.CallOpts)
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_GasPriceOracle *GasPriceOracleTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) {
return _GasPriceOracle.contract.Transact(opts, "renounceOwnership")
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_GasPriceOracle *GasPriceOracleSession) RenounceOwnership() (*types.Transaction, error) {
return _GasPriceOracle.Contract.RenounceOwnership(&_GasPriceOracle.TransactOpts)
}
// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6.
//
// Solidity: function renounceOwnership() returns()
func (_GasPriceOracle *GasPriceOracleTransactorSession) RenounceOwnership() (*types.Transaction, error) {
return _GasPriceOracle.Contract.RenounceOwnership(&_GasPriceOracle.TransactOpts)
}
// SetGasPrice is a paid mutator transaction binding the contract method 0xbf1fe420.
//
// Solidity: function setGasPrice(uint256 _gasPrice) returns()
func (_GasPriceOracle *GasPriceOracleTransactor) SetGasPrice(opts *bind.TransactOpts, _gasPrice *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.contract.Transact(opts, "setGasPrice", _gasPrice)
}
// SetGasPrice is a paid mutator transaction binding the contract method 0xbf1fe420.
//
// Solidity: function setGasPrice(uint256 _gasPrice) returns()
func (_GasPriceOracle *GasPriceOracleSession) SetGasPrice(_gasPrice *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetGasPrice(&_GasPriceOracle.TransactOpts, _gasPrice)
}
// SetGasPrice is a paid mutator transaction binding the contract method 0xbf1fe420.
//
// Solidity: function setGasPrice(uint256 _gasPrice) returns()
func (_GasPriceOracle *GasPriceOracleTransactorSession) SetGasPrice(_gasPrice *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetGasPrice(&_GasPriceOracle.TransactOpts, _gasPrice)
}
// SetL1BaseFee is a paid mutator transaction binding the contract method 0xbede39b5.
//
// Solidity: function setL1BaseFee(uint256 _baseFee) returns()
func (_GasPriceOracle *GasPriceOracleTransactor) SetL1BaseFee(opts *bind.TransactOpts, _baseFee *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.contract.Transact(opts, "setL1BaseFee", _baseFee)
}
// SetL1BaseFee is a paid mutator transaction binding the contract method 0xbede39b5.
//
// Solidity: function setL1BaseFee(uint256 _baseFee) returns()
func (_GasPriceOracle *GasPriceOracleSession) SetL1BaseFee(_baseFee *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetL1BaseFee(&_GasPriceOracle.TransactOpts, _baseFee)
}
// SetL1BaseFee is a paid mutator transaction binding the contract method 0xbede39b5.
//
// Solidity: function setL1BaseFee(uint256 _baseFee) returns()
func (_GasPriceOracle *GasPriceOracleTransactorSession) SetL1BaseFee(_baseFee *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetL1BaseFee(&_GasPriceOracle.TransactOpts, _baseFee)
}
// SetOverhead is a paid mutator transaction binding the contract method 0x3577afc5.
//
// Solidity: function setOverhead(uint256 _overhead) returns()
func (_GasPriceOracle *GasPriceOracleTransactor) SetOverhead(opts *bind.TransactOpts, _overhead *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.contract.Transact(opts, "setOverhead", _overhead)
}
// SetOverhead is a paid mutator transaction binding the contract method 0x3577afc5.
//
// Solidity: function setOverhead(uint256 _overhead) returns()
func (_GasPriceOracle *GasPriceOracleSession) SetOverhead(_overhead *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetOverhead(&_GasPriceOracle.TransactOpts, _overhead)
}
// SetOverhead is a paid mutator transaction binding the contract method 0x3577afc5.
//
// Solidity: function setOverhead(uint256 _overhead) returns()
func (_GasPriceOracle *GasPriceOracleTransactorSession) SetOverhead(_overhead *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetOverhead(&_GasPriceOracle.TransactOpts, _overhead)
}
// SetScalar is a paid mutator transaction binding the contract method 0x70465597.
//
// Solidity: function setScalar(uint256 _scalar) returns()
func (_GasPriceOracle *GasPriceOracleTransactor) SetScalar(opts *bind.TransactOpts, _scalar *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.contract.Transact(opts, "setScalar", _scalar)
}
// SetScalar is a paid mutator transaction binding the contract method 0x70465597.
//
// Solidity: function setScalar(uint256 _scalar) returns()
func (_GasPriceOracle *GasPriceOracleSession) SetScalar(_scalar *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetScalar(&_GasPriceOracle.TransactOpts, _scalar)
}
// SetScalar is a paid mutator transaction binding the contract method 0x70465597.
//
// Solidity: function setScalar(uint256 _scalar) returns()
func (_GasPriceOracle *GasPriceOracleTransactorSession) SetScalar(_scalar *big.Int) (*types.Transaction, error) {
return _GasPriceOracle.Contract.SetScalar(&_GasPriceOracle.TransactOpts, _scalar)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_GasPriceOracle *GasPriceOracleTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) {
return _GasPriceOracle.contract.Transact(opts, "transferOwnership", newOwner)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_GasPriceOracle *GasPriceOracleSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) {
return _GasPriceOracle.Contract.TransferOwnership(&_GasPriceOracle.TransactOpts, newOwner)
}
// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b.
//
// Solidity: function transferOwnership(address newOwner) returns()
func (_GasPriceOracle *GasPriceOracleTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) {
return _GasPriceOracle.Contract.TransferOwnership(&_GasPriceOracle.TransactOpts, newOwner)
}
// GasPriceOracleGasPriceUpdatedIterator is returned from FilterGasPriceUpdated and is used to iterate over the raw logs and unpacked data for GasPriceUpdated events raised by the GasPriceOracle contract.
type GasPriceOracleGasPriceUpdatedIterator struct {
Event *GasPriceOracleGasPriceUpdated // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *GasPriceOracleGasPriceUpdatedIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleGasPriceUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleGasPriceUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *GasPriceOracleGasPriceUpdatedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *GasPriceOracleGasPriceUpdatedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// GasPriceOracleGasPriceUpdated represents a GasPriceUpdated event raised by the GasPriceOracle contract.
type GasPriceOracleGasPriceUpdated struct {
Arg0 *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterGasPriceUpdated is a free log retrieval operation binding the contract event 0xfcdccc6074c6c42e4bd578aa9870c697dc976a270968452d2b8c8dc369fae396.
//
// Solidity: event GasPriceUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) FilterGasPriceUpdated(opts *bind.FilterOpts) (*GasPriceOracleGasPriceUpdatedIterator, error) {
logs, sub, err := _GasPriceOracle.contract.FilterLogs(opts, "GasPriceUpdated")
if err != nil {
return nil, err
}
return &GasPriceOracleGasPriceUpdatedIterator{contract: _GasPriceOracle.contract, event: "GasPriceUpdated", logs: logs, sub: sub}, nil
}
// WatchGasPriceUpdated is a free log subscription operation binding the contract event 0xfcdccc6074c6c42e4bd578aa9870c697dc976a270968452d2b8c8dc369fae396.
//
// Solidity: event GasPriceUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) WatchGasPriceUpdated(opts *bind.WatchOpts, sink chan<- *GasPriceOracleGasPriceUpdated) (event.Subscription, error) {
logs, sub, err := _GasPriceOracle.contract.WatchLogs(opts, "GasPriceUpdated")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(GasPriceOracleGasPriceUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "GasPriceUpdated", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// ParseGasPriceUpdated is a log parse operation binding the contract event 0xfcdccc6074c6c42e4bd578aa9870c697dc976a270968452d2b8c8dc369fae396.
//
// Solidity: event GasPriceUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) ParseGasPriceUpdated(log types.Log) (*GasPriceOracleGasPriceUpdated, error) {
event := new(GasPriceOracleGasPriceUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "GasPriceUpdated", log); err != nil {
return nil, err
}
return event, nil
}
// GasPriceOracleL1BaseFeeUpdatedIterator is returned from FilterL1BaseFeeUpdated and is used to iterate over the raw logs and unpacked data for L1BaseFeeUpdated events raised by the GasPriceOracle contract.
type GasPriceOracleL1BaseFeeUpdatedIterator struct {
Event *GasPriceOracleL1BaseFeeUpdated // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *GasPriceOracleL1BaseFeeUpdatedIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleL1BaseFeeUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleL1BaseFeeUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *GasPriceOracleL1BaseFeeUpdatedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *GasPriceOracleL1BaseFeeUpdatedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// GasPriceOracleL1BaseFeeUpdated represents a L1BaseFeeUpdated event raised by the GasPriceOracle contract.
type GasPriceOracleL1BaseFeeUpdated struct {
Arg0 *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterL1BaseFeeUpdated is a free log retrieval operation binding the contract event 0x351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c44.
//
// Solidity: event L1BaseFeeUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) FilterL1BaseFeeUpdated(opts *bind.FilterOpts) (*GasPriceOracleL1BaseFeeUpdatedIterator, error) {
logs, sub, err := _GasPriceOracle.contract.FilterLogs(opts, "L1BaseFeeUpdated")
if err != nil {
return nil, err
}
return &GasPriceOracleL1BaseFeeUpdatedIterator{contract: _GasPriceOracle.contract, event: "L1BaseFeeUpdated", logs: logs, sub: sub}, nil
}
// WatchL1BaseFeeUpdated is a free log subscription operation binding the contract event 0x351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c44.
//
// Solidity: event L1BaseFeeUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) WatchL1BaseFeeUpdated(opts *bind.WatchOpts, sink chan<- *GasPriceOracleL1BaseFeeUpdated) (event.Subscription, error) {
logs, sub, err := _GasPriceOracle.contract.WatchLogs(opts, "L1BaseFeeUpdated")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(GasPriceOracleL1BaseFeeUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "L1BaseFeeUpdated", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// ParseL1BaseFeeUpdated is a log parse operation binding the contract event 0x351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c44.
//
// Solidity: event L1BaseFeeUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) ParseL1BaseFeeUpdated(log types.Log) (*GasPriceOracleL1BaseFeeUpdated, error) {
event := new(GasPriceOracleL1BaseFeeUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "L1BaseFeeUpdated", log); err != nil {
return nil, err
}
return event, nil
}
// GasPriceOracleOverheadUpdatedIterator is returned from FilterOverheadUpdated and is used to iterate over the raw logs and unpacked data for OverheadUpdated events raised by the GasPriceOracle contract.
type GasPriceOracleOverheadUpdatedIterator struct {
Event *GasPriceOracleOverheadUpdated // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *GasPriceOracleOverheadUpdatedIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleOverheadUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleOverheadUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *GasPriceOracleOverheadUpdatedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *GasPriceOracleOverheadUpdatedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// GasPriceOracleOverheadUpdated represents a OverheadUpdated event raised by the GasPriceOracle contract.
type GasPriceOracleOverheadUpdated struct {
Arg0 *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterOverheadUpdated is a free log retrieval operation binding the contract event 0x32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4.
//
// Solidity: event OverheadUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) FilterOverheadUpdated(opts *bind.FilterOpts) (*GasPriceOracleOverheadUpdatedIterator, error) {
logs, sub, err := _GasPriceOracle.contract.FilterLogs(opts, "OverheadUpdated")
if err != nil {
return nil, err
}
return &GasPriceOracleOverheadUpdatedIterator{contract: _GasPriceOracle.contract, event: "OverheadUpdated", logs: logs, sub: sub}, nil
}
// WatchOverheadUpdated is a free log subscription operation binding the contract event 0x32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4.
//
// Solidity: event OverheadUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) WatchOverheadUpdated(opts *bind.WatchOpts, sink chan<- *GasPriceOracleOverheadUpdated) (event.Subscription, error) {
logs, sub, err := _GasPriceOracle.contract.WatchLogs(opts, "OverheadUpdated")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(GasPriceOracleOverheadUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "OverheadUpdated", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// ParseOverheadUpdated is a log parse operation binding the contract event 0x32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4.
//
// Solidity: event OverheadUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) ParseOverheadUpdated(log types.Log) (*GasPriceOracleOverheadUpdated, error) {
event := new(GasPriceOracleOverheadUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "OverheadUpdated", log); err != nil {
return nil, err
}
return event, nil
}
// GasPriceOracleOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the GasPriceOracle contract.
type GasPriceOracleOwnershipTransferredIterator struct {
Event *GasPriceOracleOwnershipTransferred // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *GasPriceOracleOwnershipTransferredIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleOwnershipTransferred)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleOwnershipTransferred)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *GasPriceOracleOwnershipTransferredIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *GasPriceOracleOwnershipTransferredIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// GasPriceOracleOwnershipTransferred represents a OwnershipTransferred event raised by the GasPriceOracle contract.
type GasPriceOracleOwnershipTransferred struct {
PreviousOwner common.Address
NewOwner common.Address
Raw types.Log // Blockchain specific contextual infos
}
// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0.
//
// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
func (_GasPriceOracle *GasPriceOracleFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*GasPriceOracleOwnershipTransferredIterator, error) {
var previousOwnerRule []interface{}
for _, previousOwnerItem := range previousOwner {
previousOwnerRule = append(previousOwnerRule, previousOwnerItem)
}
var newOwnerRule []interface{}
for _, newOwnerItem := range newOwner {
newOwnerRule = append(newOwnerRule, newOwnerItem)
}
logs, sub, err := _GasPriceOracle.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule)
if err != nil {
return nil, err
}
return &GasPriceOracleOwnershipTransferredIterator{contract: _GasPriceOracle.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil
}
// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0.
//
// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
func (_GasPriceOracle *GasPriceOracleFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *GasPriceOracleOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) {
var previousOwnerRule []interface{}
for _, previousOwnerItem := range previousOwner {
previousOwnerRule = append(previousOwnerRule, previousOwnerItem)
}
var newOwnerRule []interface{}
for _, newOwnerItem := range newOwner {
newOwnerRule = append(newOwnerRule, newOwnerItem)
}
logs, sub, err := _GasPriceOracle.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule)
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(GasPriceOracleOwnershipTransferred)
if err := _GasPriceOracle.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0.
//
// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
func (_GasPriceOracle *GasPriceOracleFilterer) ParseOwnershipTransferred(log types.Log) (*GasPriceOracleOwnershipTransferred, error) {
event := new(GasPriceOracleOwnershipTransferred)
if err := _GasPriceOracle.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil {
return nil, err
}
return event, nil
}
// GasPriceOracleScalarUpdatedIterator is returned from FilterScalarUpdated and is used to iterate over the raw logs and unpacked data for ScalarUpdated events raised by the GasPriceOracle contract.
type GasPriceOracleScalarUpdatedIterator struct {
Event *GasPriceOracleScalarUpdated // Event containing the contract specifics and raw log
contract *bind.BoundContract // Generic contract to use for unpacking event data
event string // Event name to use for unpacking event data
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *GasPriceOracleScalarUpdatedIterator) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleScalarUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
it.Event = new(GasPriceOracleScalarUpdated)
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
it.fail = err
return false
}
it.Event.Raw = log
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *GasPriceOracleScalarUpdatedIterator) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *GasPriceOracleScalarUpdatedIterator) Close() error {
it.sub.Unsubscribe()
return nil
}
// GasPriceOracleScalarUpdated represents a ScalarUpdated event raised by the GasPriceOracle contract.
type GasPriceOracleScalarUpdated struct {
Arg0 *big.Int
Raw types.Log // Blockchain specific contextual infos
}
// FilterScalarUpdated is a free log retrieval operation binding the contract event 0x3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a.
//
// Solidity: event ScalarUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) FilterScalarUpdated(opts *bind.FilterOpts) (*GasPriceOracleScalarUpdatedIterator, error) {
logs, sub, err := _GasPriceOracle.contract.FilterLogs(opts, "ScalarUpdated")
if err != nil {
return nil, err
}
return &GasPriceOracleScalarUpdatedIterator{contract: _GasPriceOracle.contract, event: "ScalarUpdated", logs: logs, sub: sub}, nil
}
// WatchScalarUpdated is a free log subscription operation binding the contract event 0x3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a.
//
// Solidity: event ScalarUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) WatchScalarUpdated(opts *bind.WatchOpts, sink chan<- *GasPriceOracleScalarUpdated) (event.Subscription, error) {
logs, sub, err := _GasPriceOracle.contract.WatchLogs(opts, "ScalarUpdated")
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
event := new(GasPriceOracleScalarUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "ScalarUpdated", log); err != nil {
return err
}
event.Raw = log
select {
case sink <- event:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// ParseScalarUpdated is a log parse operation binding the contract event 0x3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a.
//
// Solidity: event ScalarUpdated(uint256 )
func (_GasPriceOracle *GasPriceOracleFilterer) ParseScalarUpdated(log types.Log) (*GasPriceOracleScalarUpdated, error) {
event := new(GasPriceOracleScalarUpdated)
if err := _GasPriceOracle.contract.UnpackLog(event, "ScalarUpdated", log); err != nil {
return nil, err
}
return event, nil
}
package bindings
import (
"bytes"
"context"
"crypto/ecdsa"
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rollup/fees"
)
// Test that the fee calculation is the same in both go and solidity
func TestCalculateFee(t *testing.T) {
key, _ := crypto.GenerateKey()
sim, _ := newSimulatedBackend(key)
chain := sim.Blockchain()
opts, _ := NewKeyedTransactor(key)
addr, _, gpo, err := DeployGasPriceOracle(opts, sim, opts.From)
if err != nil {
t.Fatal(err)
}
sim.Commit()
callopts := bind.CallOpts{}
signer := types.NewEIP155Signer(big.NewInt(1337))
gasOracle := gasprice.NewRollupOracle()
// Set the L1 base fee
if _, err := gpo.SetL1BaseFee(opts, big.NewInt(1)); err != nil {
t.Fatal("cannot set 1l base fee")
}
sim.Commit()
tests := map[string]struct {
tx *types.Transaction
}{
"simple": {
types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{}),
},
"high-nonce": {
types.NewTransaction(12345678, common.Address{}, big.NewInt(0), 0, big.NewInt(0), []byte{}),
},
"full-tx": {
types.NewTransaction(20, common.HexToAddress("0x"), big.NewInt(1234), 215000, big.NewInt(769109341), common.FromHex(GasPriceOracleBin)),
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
tx := tt.tx
raw := new(bytes.Buffer)
if err := tx.EncodeRLP(raw); err != nil {
t.Fatal("cannot rlp encode tx")
}
l1BaseFee, err := gpo.L1BaseFee(&callopts)
if err != nil {
t.Fatal("cannot get l1 base fee")
}
overhead, err := gpo.Overhead(&callopts)
if err != nil {
t.Fatal("cannot get overhead")
}
scalar, err := gpo.Scalar(&callopts)
if err != nil {
t.Fatal("cannot get scalar")
}
decimals, err := gpo.Decimals(&callopts)
if err != nil {
t.Fatal("cannot get decimals")
}
l2GasPrice, err := gpo.GasPrice(&callopts)
if err != nil {
t.Fatal("cannot get l2 gas price")
}
gasOracle.SetL1GasPrice(l1BaseFee)
gasOracle.SetL2GasPrice(l2GasPrice)
gasOracle.SetOverhead(overhead)
gasOracle.SetScalar(scalar, decimals)
l1Fee, err := gpo.GetL1Fee(&callopts, raw.Bytes())
if err != nil {
t.Fatal("cannot get l1 fee")
}
scaled := fees.ScaleDecimals(scalar, decimals)
expectL1Fee := fees.CalculateL1Fee(raw.Bytes(), overhead, l1BaseFee, scaled)
if expectL1Fee.Cmp(l1Fee) != 0 {
t.Fatal("solidity does not match go")
}
state, err := chain.State()
if err != nil {
t.Fatal("cannot get state")
}
// Ignore the error here because the tx isn't signed
msg, _ := tx.AsMessage(signer)
l1MsgFee, err := fees.CalculateL1MsgFee(msg, state, &addr)
if l1MsgFee.Cmp(expectL1Fee) != 0 {
t.Fatal("l1 msg fee not computed correctly")
}
msgFee, err := fees.CalculateTotalMsgFee(msg, state, new(big.Int).SetUint64(msg.Gas()), &addr)
if err != nil {
t.Fatal("cannot calculate total msg fee")
}
txFee, err := fees.CalculateTotalFee(tx, gasOracle)
if err != nil {
t.Fatal("cannot calculate total tx fee")
}
if msgFee.Cmp(txFee) != 0 {
t.Fatal("msg fee and tx fee mismatch")
}
})
}
}
func newSimulatedBackend(key *ecdsa.PrivateKey) (*backends.SimulatedBackend, ethdb.Database) {
var gasLimit uint64 = 9_000_000
auth, _ := NewKeyedTransactor(key)
genAlloc := make(core.GenesisAlloc)
genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)}
db := rawdb.NewMemoryDatabase()
sim := backends.NewSimulatedBackendWithDatabase(db, genAlloc, gasLimit)
return sim, db
}
// NewKeyedTransactor is a utility method to easily create a transaction signer
// from a single private key. This was copied and modified from upstream geth
func NewKeyedTransactor(key *ecdsa.PrivateKey) (*bind.TransactOpts, error) {
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
return &bind.TransactOpts{
From: keyAddr,
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != keyAddr {
return nil, errors.New("unauthorized")
}
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
if err != nil {
return nil, err
}
return tx.WithSignature(signer, signature)
},
Context: context.Background(),
}, nil
}
package fees
import (
"bytes"
"context"
"errors"
"fmt"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
var (
// errFeeTooLow represents the error case of then the user pays too little
ErrFeeTooLow = errors.New("fee too low")
// errFeeTooHigh represents the error case of when the user pays too much
ErrFeeTooHigh = errors.New("fee too high")
// ErrGasPriceTooLow represents the error case of then the user pays too little
ErrGasPriceTooLow = errors.New("gas price too low")
// ErrGasPriceTooHigh represents the error case of when the user pays too much
ErrGasPriceTooHigh = errors.New("gas price too high")
// ErrInsufficientFunds represents the error case of when the user doesn't
// have enough funds to cover the transaction
ErrInsufficientFunds = errors.New("insufficient funds for l1Fee + l2Fee + value")
// errMissingInput represents the error case of missing required input to
// PaysEnough
errMissingInput = errors.New("missing input")
// ErrL2GasLimitTooLow represents the error case of when a user sends a
// transaction to the sequencer with a L2 gas limit that is too small
ErrL2GasLimitTooLow = errors.New("L2 gas limit too low")
// errTransactionSigned represents the error case of passing in a signed
// transaction to the L1 fee calculation routine. The signature is accounted
// for externally
errTransactionSigned = errors.New("transaction is signed")
// big10 is used for decimal scaling
big10 = new(big.Int).SetUint64(10)
)
// overhead represents the fixed cost of batch submission of a single
// transaction in gas.
const overhead uint64 = 2750
// feeScalar is used to scale the calculations in EncodeL2GasLimit
// to prevent them from being too large
const feeScalar uint64 = 10_000_000
// TxGasPrice is a constant that determines the result of `eth_gasPrice`
// It is scaled upwards by 50%
// tx.gasPrice is hard coded to 1500 * wei and all transactions must set that
// gas price.
const TxGasPrice uint64 = feeScalar + (feeScalar / 2)
// BigTxGasPrice is the L2GasPrice as type big.Int
var BigTxGasPrice = new(big.Int).SetUint64(TxGasPrice)
var bigFeeScalar = new(big.Int).SetUint64(feeScalar)
const tenThousand = 10000
var BigTenThousand = new(big.Int).SetUint64(tenThousand)
// EncodeTxGasLimit computes the `tx.gasLimit` based on the L1/L2 gas prices and
// the L2 gas limit. The L2 gas limit is encoded inside of the lower order bits
// of the number like so: [ | l2GasLimit ]
// [ tx.gaslimit ]
// The lower order bits must be large enough to fit the L2 gas limit, so 10**8
// is chosen. If higher order bits collide with any bits from the L2 gas limit,
// the L2 gas limit will not be able to be decoded.
// An explicit design goal of this scheme was to make the L2 gas limit be human
// readable. The entire number is interpreted as the gas limit when computing
// the fee, so increasing the L2 Gas limit will increase the fee paid.
// The calculation is:
// l1GasLimit = zero_count(data) * 4 + non_zero_count(data) * 16 + overhead
// roundedL2GasLimit = ceilmod(l2GasLimit, 10_000)
// l1Fee = l1GasPrice * l1GasLimit
// l2Fee = l2GasPrice * roundedL2GasLimit
// sum = l1Fee + l2Fee
// scaled = sum / scalar
// rounded = ceilmod(scaled, tenThousand)
// roundedScaledL2GasLimit = roundedL2GasLimit / tenThousand
// result = rounded + roundedScaledL2GasLimit
// Note that for simplicity purposes, only the calldata is passed into this
// function when in reality the RLP encoded transaction should be. The
// additional cost is added to the overhead constant to prevent the need to RLP
// encode transactions during calls to `eth_estimateGas`
func EncodeTxGasLimit(data []byte, l1GasPrice, l2GasLimit, l2GasPrice *big.Int) *big.Int {
l1GasLimit := calculateL1GasLimit(data, overhead)
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand)
l1Fee := new(big.Int).Mul(l1GasPrice, l1GasLimit)
l2Fee := new(big.Int).Mul(l2GasPrice, roundedL2GasLimit)
sum := new(big.Int).Add(l1Fee, l2Fee)
scaled := new(big.Int).Div(sum, bigFeeScalar)
rounded := Ceilmod(scaled, BigTenThousand)
roundedScaledL2GasLimit := new(big.Int).Div(roundedL2GasLimit, BigTenThousand)
result := new(big.Int).Add(rounded, roundedScaledL2GasLimit)
return result
}
func Ceilmod(a, b *big.Int) *big.Int {
remainder := new(big.Int).Mod(a, b)
if remainder.Cmp(common.Big0) == 0 {
return a
}
sum := new(big.Int).Add(a, b)
rounded := new(big.Int).Sub(sum, remainder)
return rounded
}
// DecodeL2GasLimit decodes the L2 gas limit from an encoded L2 gas limit
func DecodeL2GasLimit(gasLimit *big.Int) *big.Int {
scaled := new(big.Int).Mod(gasLimit, BigTenThousand)
return new(big.Int).Mul(scaled, BigTenThousand)
}
func DecodeL2GasLimitU64(gasLimit uint64) uint64 {
scaled := gasLimit % tenThousand
return scaled * tenThousand
// Message represents the interface of a message.
// It should be a subset of the methods found on
// types.Message
type Message interface {
From() common.Address
To() *common.Address
GasPrice() *big.Int
Gas() uint64
Value() *big.Int
Nonce() uint64
Data() []byte
}
// StateDB represents the StateDB interface
// required to compute the L1 fee
type StateDB interface {
GetState(common.Address, common.Hash) common.Hash
}
// RollupOracle represents the interface of the in
// memory cache of the gas price oracle
type RollupOracle interface {
SuggestL1GasPrice(ctx context.Context) (*big.Int, error)
SuggestL2GasPrice(ctx context.Context) (*big.Int, error)
SuggestOverhead(ctx context.Context) (*big.Int, error)
SuggestScalar(ctx context.Context) (*big.Float, error)
}
// CalculateTotalFee will calculate the total fee given a transaction.
// This function is used at the RPC layer to ensure that users
// have enough ETH to cover their fee
func CalculateTotalFee(tx *types.Transaction, gpo RollupOracle) (*big.Int, error) {
// Read the variables from the cache
l1GasPrice, err := gpo.SuggestL1GasPrice(context.Background())
if err != nil {
return nil, err
}
overhead, err := gpo.SuggestOverhead(context.Background())
if err != nil {
return nil, err
}
scalar, err := gpo.SuggestScalar(context.Background())
if err != nil {
return nil, err
}
unsigned := copyTransaction(tx)
raw, err := rlpEncode(unsigned)
if err != nil {
return nil, err
}
l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
l2GasLimit := new(big.Int).SetUint64(tx.Gas())
l2Fee := new(big.Int).Mul(tx.GasPrice(), l2GasLimit)
fee := new(big.Int).Add(l1Fee, l2Fee)
return fee, nil
}
// CalculateMsgFee will calculate the total fee given a Message.
// This function is used during the state transition to transfer
// value to the sequencer. Since Messages do not have a signature
// and the signature is submitted to L1 in a batch, extra bytes
// are padded to the raw transaction
func CalculateTotalMsgFee(msg Message, state StateDB, gasUsed *big.Int, gpo *common.Address) (*big.Int, error) {
if gpo == nil {
gpo = &rcfg.L2GasPriceOracleAddress
}
l1Fee, err := CalculateL1MsgFee(msg, state, gpo)
if err != nil {
return nil, err
}
// Multiply the gas price and the gas used to get the L2 fee
l2Fee := new(big.Int).Mul(msg.GasPrice(), gasUsed)
// Add the L1 cost and the L2 cost to get the total fee being paid
fee := new(big.Int).Add(l1Fee, l2Fee)
return fee, nil
}
// CalculateL1MsgFee computes the L1 portion of the fee given
// a Message and a StateDB
func CalculateL1MsgFee(msg Message, state StateDB, gpo *common.Address) (*big.Int, error) {
tx := asTransaction(msg)
raw, err := rlpEncode(tx)
if err != nil {
return nil, err
}
if gpo == nil {
gpo = &rcfg.L2GasPriceOracleAddress
}
l1GasPrice, overhead, scalar := readGPOStorageSlots(*gpo, state)
l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
return l1Fee, nil
}
// CalculateL1Fee computes the L1 fee
func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Float) *big.Int {
l1GasUsed := CalculateL1GasUsed(data, overhead)
l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice)
return mulByFloat(l1Fee, scalar)
}
// CalculateL1GasUsed computes the L1 gas used based on the calldata and
// constant sized overhead. The overhead can be decreased as the cost of the
// batch submission goes down via contract optimizations. This will not overflow
// under standard network conditions.
func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int {
zeroes, ones := zeroesAndOnes(data)
zeroesGas := zeroes * params.TxDataZeroGas
onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028
l1Gas := new(big.Int).SetUint64(zeroesGas + onesGas)
return new(big.Int).Add(l1Gas, overhead)
}
func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Float) {
l1GasPrice := state.GetState(addr, rcfg.L1GasPriceSlot)
overhead := state.GetState(addr, rcfg.OverheadSlot)
scalar := state.GetState(addr, rcfg.ScalarSlot)
decimals := state.GetState(addr, rcfg.DecimalsSlot)
scaled := ScaleDecimals(scalar.Big(), decimals.Big())
return l1GasPrice.Big(), overhead.Big(), scaled
}
// ScaleDecimals will scale a value by decimals
func ScaleDecimals(scalar, decimals *big.Int) *big.Float {
// 10**decimals
divisor := new(big.Int).Exp(big10, decimals, nil)
fscalar := new(big.Float).SetInt(scalar)
fdivisor := new(big.Float).SetInt(divisor)
// fscalar / fdivisor
return new(big.Float).Quo(fscalar, fdivisor)
}
// rlpEncode RLP encodes the transaction into bytes
// When a signature is not included, set pad to true to
// fill in a dummy signature full on non 0 bytes
func rlpEncode(tx *types.Transaction) ([]byte, error) {
raw := new(bytes.Buffer)
if err := tx.EncodeRLP(raw); err != nil {
return nil, err
}
r, v, s := tx.RawSignatureValues()
if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 {
return []byte{}, errTransactionSigned
}
// Slice off the 0 bytes representing the signature
b := raw.Bytes()
return b[:len(b)-3], nil
}
// asTransaction turns a Message into a types.Transaction
func asTransaction(msg Message) *types.Transaction {
if msg.To() == nil {
return types.NewContractCreation(
msg.Nonce(),
msg.Value(),
msg.Gas(),
msg.GasPrice(),
msg.Data(),
)
}
return types.NewTransaction(
msg.Nonce(),
*msg.To(),
msg.Value(),
msg.Gas(),
msg.GasPrice(),
msg.Data(),
)
}
// copyTransaction copies the transaction, removing the signature
func copyTransaction(tx *types.Transaction) *types.Transaction {
if tx.To() == nil {
return types.NewContractCreation(
tx.Nonce(),
tx.Value(),
tx.Gas(),
tx.GasPrice(),
tx.Data(),
)
}
return types.NewTransaction(
tx.Nonce(),
*tx.To(),
tx.Value(),
tx.Gas(),
tx.GasPrice(),
tx.Data(),
)
}
// PaysEnoughOpts represent the options to PaysEnough
type PaysEnoughOpts struct {
UserFee, ExpectedFee *big.Int
ThresholdUp, ThresholdDown *big.Float
UserGasPrice, ExpectedGasPrice *big.Int
ThresholdUp, ThresholdDown *big.Float
}
// PaysEnough returns an error if the fee is not large enough
// `GasPrice` and `Fee` are required arguments.
func PaysEnough(opts *PaysEnoughOpts) error {
if opts.UserFee == nil {
if opts.UserGasPrice == nil {
return fmt.Errorf("%w: no user fee", errMissingInput)
}
if opts.ExpectedFee == nil {
if opts.ExpectedGasPrice == nil {
return fmt.Errorf("%w: no expected fee", errMissingInput)
}
fee := new(big.Int).Set(opts.ExpectedFee)
fee := new(big.Int).Set(opts.ExpectedGasPrice)
// Allow for a downward buffer to protect against L1 gas price volatility
if opts.ThresholdDown != nil {
fee = mulByFloat(fee, opts.ThresholdDown)
}
// Protect the sequencer from being underpaid
// if user fee < expected fee, return error
if opts.UserFee.Cmp(fee) == -1 {
return ErrFeeTooLow
if opts.UserGasPrice.Cmp(fee) == -1 {
return ErrGasPriceTooLow
}
// Protect users from overpaying by too much
if opts.ThresholdUp != nil {
// overpaying = user fee - expected fee
overpaying := new(big.Int).Sub(opts.UserFee, opts.ExpectedFee)
threshold := mulByFloat(opts.ExpectedFee, opts.ThresholdUp)
overpaying := new(big.Int).Sub(opts.UserGasPrice, opts.ExpectedGasPrice)
threshold := mulByFloat(opts.ExpectedGasPrice, opts.ThresholdUp)
// if overpaying > threshold, return error
if overpaying.Cmp(threshold) == 1 {
return ErrFeeTooHigh
return ErrGasPriceTooHigh
}
}
return nil
}
func mulByFloat(num *big.Int, float *big.Float) *big.Int {
n := new(big.Float).SetUint64(num.Uint64())
product := n.Mul(n, float)
pfloat, _ := product.Float64()
rounded := math.Ceil(pfloat)
return new(big.Int).SetUint64(uint64(rounded))
}
// calculateL1GasLimit computes the L1 gasLimit based on the calldata and
// constant sized overhead. The overhead can be decreased as the cost of the
// batch submission goes down via contract optimizations. This will not overflow
// under standard network conditions.
func calculateL1GasLimit(data []byte, overhead uint64) *big.Int {
zeroes, ones := zeroesAndOnes(data)
zeroesCost := zeroes * params.TxDataZeroGas
onesCost := ones * params.TxDataNonZeroGasEIP2028
gasLimit := zeroesCost + onesCost + overhead
return new(big.Int).SetUint64(gasLimit)
}
// zeroesAndOnes counts the number of 0 bytes and non 0 bytes in a byte slice
func zeroesAndOnes(data []byte) (uint64, uint64) {
var zeroes uint64
var ones uint64
......@@ -174,3 +285,13 @@ func zeroesAndOnes(data []byte) (uint64, uint64) {
}
return zeroes, ones
}
// mulByFloat multiplies a big.Int by a float and returns the
// big.Int rounded upwards
func mulByFloat(num *big.Int, float *big.Float) *big.Int {
n := new(big.Float).SetUint64(num.Uint64())
product := n.Mul(n, float)
pfloat, _ := product.Float64()
rounded := math.Ceil(pfloat)
return new(big.Int).SetUint64(uint64(rounded))
}
......@@ -6,105 +6,8 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
var l1GasLimitTests = map[string]struct {
data []byte
overhead uint64
expect *big.Int
}{
"simple": {[]byte{}, 0, big.NewInt(0)},
"simple-overhead": {[]byte{}, 10, big.NewInt(10)},
"zeros": {[]byte{0x00, 0x00, 0x00, 0x00}, 10, big.NewInt(26)},
"ones": {[]byte{0x01, 0x02, 0x03, 0x04}, 200, big.NewInt(16*4 + 200)},
}
func TestL1GasLimit(t *testing.T) {
for name, tt := range l1GasLimitTests {
t.Run(name, func(t *testing.T) {
got := calculateL1GasLimit(tt.data, tt.overhead)
if got.Cmp(tt.expect) != 0 {
t.Fatal("Calculated gas limit does not match")
}
})
}
}
var feeTests = map[string]struct {
dataLen int
l1GasPrice uint64
l2GasLimit uint64
l2GasPrice uint64
}{
"simple": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 437118,
l2GasPrice: params.GWei,
},
"zero-l2-gasprice": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 196205,
l2GasPrice: 0,
},
"one-l2-gasprice": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 196205,
l2GasPrice: 1,
},
"zero-l1-gasprice": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 196205,
l2GasPrice: params.GWei,
},
"one-l1-gasprice": {
dataLen: 10,
l1GasPrice: 1,
l2GasLimit: 23255,
l2GasPrice: params.GWei,
},
"zero-gasprices": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 23255,
l2GasPrice: 0,
},
"max-gaslimit": {
dataLen: 10,
l1GasPrice: params.GWei,
l2GasLimit: 99_970_000,
l2GasPrice: params.GWei,
},
"larger-divisor": {
dataLen: 10,
l1GasPrice: 0,
l2GasLimit: 10,
l2GasPrice: 0,
},
}
func TestCalculateRollupFee(t *testing.T) {
for name, tt := range feeTests {
t.Run(name, func(t *testing.T) {
data := make([]byte, tt.dataLen)
l1GasPrice := new(big.Int).SetUint64(tt.l1GasPrice)
l2GasLimit := new(big.Int).SetUint64(tt.l2GasLimit)
l2GasPrice := new(big.Int).SetUint64(tt.l2GasPrice)
fee := EncodeTxGasLimit(data, l1GasPrice, l2GasLimit, l2GasPrice)
decodedGasLimit := DecodeL2GasLimit(fee)
roundedL2GasLimit := Ceilmod(l2GasLimit, BigTenThousand)
if roundedL2GasLimit.Cmp(decodedGasLimit) != 0 {
t.Errorf("rollup fee check failed: expected %d, got %d", l2GasLimit.Uint64(), decodedGasLimit)
}
})
}
}
func TestPaysEnough(t *testing.T) {
tests := map[string]struct {
opts *PaysEnoughOpts
......@@ -112,84 +15,84 @@ func TestPaysEnough(t *testing.T) {
}{
"missing-gas-price": {
opts: &PaysEnoughOpts{
UserFee: nil,
ExpectedFee: new(big.Int),
ThresholdUp: nil,
ThresholdDown: nil,
UserGasPrice: nil,
ExpectedGasPrice: new(big.Int),
ThresholdUp: nil,
ThresholdDown: nil,
},
err: errMissingInput,
},
"missing-fee": {
opts: &PaysEnoughOpts{
UserFee: nil,
ExpectedFee: nil,
ThresholdUp: nil,
ThresholdDown: nil,
UserGasPrice: nil,
ExpectedGasPrice: nil,
ThresholdUp: nil,
ThresholdDown: nil,
},
err: errMissingInput,
},
"equal-fee": {
opts: &PaysEnoughOpts{
UserFee: common.Big1,
ExpectedFee: common.Big1,
ThresholdUp: nil,
ThresholdDown: nil,
UserGasPrice: common.Big1,
ExpectedGasPrice: common.Big1,
ThresholdUp: nil,
ThresholdDown: nil,
},
err: nil,
},
"fee-too-low": {
opts: &PaysEnoughOpts{
UserFee: common.Big1,
ExpectedFee: common.Big2,
ThresholdUp: nil,
ThresholdDown: nil,
UserGasPrice: common.Big1,
ExpectedGasPrice: common.Big2,
ThresholdUp: nil,
ThresholdDown: nil,
},
err: ErrFeeTooLow,
err: ErrGasPriceTooLow,
},
"fee-threshold-down": {
opts: &PaysEnoughOpts{
UserFee: common.Big1,
ExpectedFee: common.Big2,
ThresholdUp: nil,
ThresholdDown: new(big.Float).SetFloat64(0.5),
UserGasPrice: common.Big1,
ExpectedGasPrice: common.Big2,
ThresholdUp: nil,
ThresholdDown: new(big.Float).SetFloat64(0.5),
},
err: nil,
},
"fee-threshold-up": {
opts: &PaysEnoughOpts{
UserFee: common.Big256,
ExpectedFee: common.Big1,
ThresholdUp: new(big.Float).SetFloat64(1.5),
ThresholdDown: nil,
UserGasPrice: common.Big256,
ExpectedGasPrice: common.Big1,
ThresholdUp: new(big.Float).SetFloat64(1.5),
ThresholdDown: nil,
},
err: ErrFeeTooHigh,
err: ErrGasPriceTooHigh,
},
"fee-too-low-high": {
opts: &PaysEnoughOpts{
UserFee: new(big.Int).SetUint64(10_000),
ExpectedFee: new(big.Int).SetUint64(1),
ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8),
UserGasPrice: new(big.Int).SetUint64(10_000),
ExpectedGasPrice: new(big.Int).SetUint64(1),
ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8),
},
err: ErrFeeTooHigh,
err: ErrGasPriceTooHigh,
},
"fee-too-low-down": {
opts: &PaysEnoughOpts{
UserFee: new(big.Int).SetUint64(1),
ExpectedFee: new(big.Int).SetUint64(10_000),
ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8),
UserGasPrice: new(big.Int).SetUint64(1),
ExpectedGasPrice: new(big.Int).SetUint64(10_000),
ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8),
},
err: ErrFeeTooLow,
err: ErrGasPriceTooLow,
},
"fee-too-low-down-2": {
opts: &PaysEnoughOpts{
UserFee: new(big.Int).SetUint64(0),
ExpectedFee: new(big.Int).SetUint64(10_000),
ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8),
UserGasPrice: new(big.Int).SetUint64(0),
ExpectedGasPrice: new(big.Int).SetUint64(10_000),
ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8),
},
err: ErrFeeTooLow,
err: ErrGasPriceTooLow,
},
}
......
package rcfg
import (
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
)
// UsingOVM is used to enable or disable functionality necessary for the OVM.
var UsingOVM bool
var (
// l2GasPriceSlot refers to the storage slot that the L2 gas price is stored
// in in the OVM_GasPriceOracle predeploy
L2GasPriceSlot = common.BigToHash(big.NewInt(1))
// l1GasPriceSlot refers to the storage slot that the L1 gas price is stored
// in in the OVM_GasPriceOracle predeploy
L1GasPriceSlot = common.BigToHash(big.NewInt(2))
// l2GasPriceOracleOwnerSlot refers to the storage slot that the owner of
// the OVM_GasPriceOracle is stored in
L2GasPriceOracleOwnerSlot = common.BigToHash(big.NewInt(0))
// l2GasPriceOracleAddress is the address of the OVM_GasPriceOracle
// predeploy
L2GasPriceOracleAddress = common.HexToAddress("0x420000000000000000000000000000000000000F")
// OverheadSlot refers to the storage slot in the OVM_GasPriceOracle that
// holds the per transaction overhead. This is added to the L1 cost portion
// of the fee
OverheadSlot = common.BigToHash(big.NewInt(3))
// ScalarSlot refers to the storage slot in the OVM_GasPriceOracle that
// holds the transaction fee scalar. This value is scaled upwards by
// the number of decimals
ScalarSlot = common.BigToHash(big.NewInt(4))
// DecimalsSlot refers to the storage slot in the OVM_GasPriceOracle that
// holds the number of decimals in the fee scalar
DecimalsSlot = common.BigToHash(big.NewInt(5))
)
func init() {
UsingOVM = os.Getenv("USING_OVM") == "true"
}
......@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
var (
......@@ -37,18 +38,6 @@ var (
float1 = big.NewFloat(1)
)
var (
// l2GasPriceSlot refers to the storage slot that the L2 gas price is stored
// in in the OVM_GasPriceOracle predeploy
l2GasPriceSlot = common.BigToHash(big.NewInt(1))
// l2GasPriceOracleOwnerSlot refers to the storage slot that the owner of
// the OVM_GasPriceOracle is stored in
l2GasPriceOracleOwnerSlot = common.BigToHash(big.NewInt(0))
// l2GasPriceOracleAddress is the address of the OVM_GasPriceOracle
// predeploy
l2GasPriceOracleAddress = common.HexToAddress("0x420000000000000000000000000000000000000F")
)
// SyncService implements the main functionality around pulling in transactions
// and executing them. It can be configured to run in both sequencer mode and in
// verifier mode.
......@@ -95,8 +84,7 @@ func NewSyncService(ctx context.Context, cfg Config, txpool *core.TxPool, bc *co
log.Info("Running in verifier mode", "sync-backend", cfg.Backend.String())
} else {
log.Info("Running in sequencer mode", "sync-backend", cfg.Backend.String())
log.Info("Fees", "gas-price", fees.BigTxGasPrice, "threshold-up", cfg.FeeThresholdUp,
"threshold-down", cfg.FeeThresholdDown)
log.Info("Fees", "threshold-up", cfg.FeeThresholdUp, "threshold-down", cfg.FeeThresholdDown)
log.Info("Enforce Fees", "set", cfg.EnforceFees)
}
......@@ -260,9 +248,6 @@ func (s *SyncService) Start() error {
if err := s.updateGasPriceOracleCache(nil); err != nil {
return err
}
if err := s.updateL1GasPrice(); err != nil {
return err
}
if s.verifier {
go s.VerifierLoop()
......@@ -382,15 +367,9 @@ func (s *SyncService) VerifierLoop() {
log.Info("Starting Verifier Loop", "poll-interval", s.pollInterval, "timestamp-refresh-threshold", s.timestampRefreshThreshold)
t := time.NewTicker(s.pollInterval)
for ; true; <-t.C {
if err := s.updateL1GasPrice(); err != nil {
log.Error("Cannot update L1 gas price", "msg", err)
}
if err := s.verify(); err != nil {
log.Error("Could not verify", "error", err)
}
if err := s.updateGasPriceOracleCache(nil); err != nil {
log.Error("Cannot update L2 gas price", "msg", err)
}
}
}
......@@ -416,18 +395,12 @@ func (s *SyncService) SequencerLoop() {
log.Info("Starting Sequencer Loop", "poll-interval", s.pollInterval, "timestamp-refresh-threshold", s.timestampRefreshThreshold)
t := time.NewTicker(s.pollInterval)
for ; true; <-t.C {
if err := s.updateL1GasPrice(); err != nil {
log.Error("Cannot update L1 gas price", "msg", err)
}
s.txLock.Lock()
if err := s.sequence(); err != nil {
log.Error("Could not sequence", "error", err)
}
s.txLock.Unlock()
if err := s.updateGasPriceOracleCache(nil); err != nil {
log.Error("Cannot update L2 gas price", "msg", err)
}
if err := s.updateContext(); err != nil {
log.Error("Could not update execution context", "error", err)
}
......@@ -480,50 +453,81 @@ func (s *SyncService) syncTransactionsToTip() error {
// updateL1GasPrice queries for the current L1 gas price and then stores it
// in the L1 Gas Price Oracle. This must be called over time to properly
// estimate the transaction fees that the sequencer should charge.
func (s *SyncService) updateL1GasPrice() error {
l1GasPrice, err := s.client.GetL1GasPrice()
func (s *SyncService) updateL1GasPrice(statedb *state.StateDB) error {
value, err := s.readGPOStorageSlot(statedb, rcfg.L1GasPriceSlot)
if err != nil {
return fmt.Errorf("cannot fetch L1 gas price: %w", err)
return err
}
s.RollupGpo.SetL1GasPrice(l1GasPrice)
return nil
return s.RollupGpo.SetL1GasPrice(value)
}
// updateL2GasPrice accepts a state db and reads the gas price from the gas
// price oracle at the state that corresponds to the state db. If no state db
// is passed in, then the tip is used.
func (s *SyncService) updateL2GasPrice(statedb *state.StateDB) error {
var err error
if statedb == nil {
statedb, err = s.bc.State()
if err != nil {
return err
}
value, err := s.readGPOStorageSlot(statedb, rcfg.L2GasPriceSlot)
if err != nil {
return err
}
result := statedb.GetState(l2GasPriceOracleAddress, l2GasPriceSlot)
s.RollupGpo.SetL2GasPrice(result.Big())
return nil
return s.RollupGpo.SetL2GasPrice(value)
}
// updateOverhead will update the overhead value from the OVM_GasPriceOracle
// in the local cache
func (s *SyncService) updateOverhead(statedb *state.StateDB) error {
value, err := s.readGPOStorageSlot(statedb, rcfg.OverheadSlot)
if err != nil {
return err
}
return s.RollupGpo.SetOverhead(value)
}
// updateScalar will update the scalar value from the OVM_GasPriceOracle
// in the local cache
func (s *SyncService) updateScalar(statedb *state.StateDB) error {
scalar, err := s.readGPOStorageSlot(statedb, rcfg.ScalarSlot)
if err != nil {
return err
}
decimals, err := s.readGPOStorageSlot(statedb, rcfg.DecimalsSlot)
if err != nil {
return err
}
return s.RollupGpo.SetScalar(scalar, decimals)
}
// cacheGasPriceOracleOwner accepts a statedb and caches the gas price oracle
// owner address locally
func (s *SyncService) cacheGasPriceOracleOwner(statedb *state.StateDB) error {
s.gasPriceOracleOwnerAddressLock.Lock()
defer s.gasPriceOracleOwnerAddressLock.Unlock()
value, err := s.readGPOStorageSlot(statedb, rcfg.L2GasPriceOracleOwnerSlot)
if err != nil {
return err
}
s.gasPriceOracleOwnerAddress = common.BigToAddress(value)
return nil
}
// readGPOStorageSlot is a helper function for reading storage
// slots from the OVM_GasPriceOracle
func (s *SyncService) readGPOStorageSlot(statedb *state.StateDB, hash common.Hash) (*big.Int, error) {
var err error
if statedb == nil {
statedb, err = s.bc.State()
if err != nil {
return err
return nil, err
}
}
s.gasPriceOracleOwnerAddressLock.Lock()
defer s.gasPriceOracleOwnerAddressLock.Unlock()
result := statedb.GetState(l2GasPriceOracleAddress, l2GasPriceOracleOwnerSlot)
s.gasPriceOracleOwnerAddress = common.BytesToAddress(result.Bytes())
return nil
result := statedb.GetState(rcfg.L2GasPriceOracleAddress, hash)
return result.Big(), nil
}
// updateGasPriceOracleCache caches the owner as well as updating the
// the L2 gas price from the OVM_GasPriceOracle
// the L2 gas price from the OVM_GasPriceOracle.
// This should be sure to read all public variables from the
// OVM_GasPriceOracle
func (s *SyncService) updateGasPriceOracleCache(hash *common.Hash) error {
var statedb *state.StateDB
var err error
......@@ -541,6 +545,15 @@ func (s *SyncService) updateGasPriceOracleCache(hash *common.Hash) error {
if err := s.updateL2GasPrice(statedb); err != nil {
return err
}
if err := s.updateL1GasPrice(statedb); err != nil {
return err
}
if err := s.updateOverhead(statedb); err != nil {
return err
}
if err := s.updateScalar(statedb); err != nil {
return err
}
return nil
}
......@@ -790,7 +803,7 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
s.SetLatestEnqueueIndex(tx.GetMeta().QueueIndex)
}
// The index was set above so it is safe to dereference
log.Debug("Applying transaction to tip", "index", *tx.GetMeta().Index, "hash", tx.Hash().Hex())
log.Debug("Applying transaction to tip", "index", *tx.GetMeta().Index, "hash", tx.Hash().Hex(), "origin", tx.QueueOrigin().String())
txs := types.Transactions{tx}
s.txFeed.Send(core.NewTxsEvent{Txs: txs})
......@@ -798,6 +811,15 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
log.Trace("Waiting for transaction to be added to chain", "hash", tx.Hash().Hex())
<-s.chainHeadCh
// Update the cache when the transaction is from the owner
// of the gas price oracle
sender, _ := types.Sender(s.signer, tx)
owner := s.GasPriceOracleOwnerAddress()
if owner != nil && sender == *owner {
if err := s.updateGasPriceOracleCache(nil); err != nil {
return err
}
}
return nil
}
......@@ -824,15 +846,35 @@ func (s *SyncService) applyBatchedTransaction(tx *types.Transaction) error {
// verifyFee will verify that a valid fee is being paid.
func (s *SyncService) verifyFee(tx *types.Transaction) error {
fee, err := fees.CalculateTotalFee(tx, s.RollupGpo)
if err != nil {
return fmt.Errorf("invalid transaction: %w", err)
}
// Prevent transactions without enough balance from
// being accepted by the chain but allow through 0
// gas price transactions
cost := tx.Value()
if tx.GasPrice().Cmp(common.Big0) != 0 {
cost = cost.Add(cost, fee)
}
state, err := s.bc.State()
if err != nil {
return err
}
from, err := types.Sender(s.signer, tx)
if err != nil {
return fmt.Errorf("invalid transaction: %w", core.ErrInvalidSender)
}
if state.GetBalance(from).Cmp(cost) < 0 {
return fmt.Errorf("invalid transaction: %w", core.ErrInsufficientFunds)
}
if tx.GasPrice().Cmp(common.Big0) == 0 {
// Allow 0 gas price transactions only if it is the owner of the gas
// price oracle
gpoOwner := s.GasPriceOracleOwnerAddress()
if gpoOwner != nil {
from, err := types.Sender(s.signer, tx)
if err != nil {
return fmt.Errorf("invalid transaction: %w", core.ErrInvalidSender)
}
if from == *gpoOwner {
return nil
}
......@@ -844,54 +886,32 @@ func (s *SyncService) verifyFee(tx *types.Transaction) error {
// If fees are not enforced and the gas price is 0, return early
return nil
}
// When the gas price is non zero, it must be equal to the constant
if tx.GasPrice().Cmp(fees.BigTxGasPrice) != 0 {
return fmt.Errorf("tx.gasPrice must be %d", fees.TxGasPrice)
}
l1GasPrice, err := s.RollupGpo.SuggestL1GasPrice(context.Background())
if err != nil {
return err
}
// Ensure that the user L2 gas price is high enough
l2GasPrice, err := s.RollupGpo.SuggestL2GasPrice(context.Background())
if err != nil {
return err
}
// Calculate the fee based on decoded L2 gas limit
gas := new(big.Int).SetUint64(tx.Gas())
l2GasLimit := fees.DecodeL2GasLimit(gas)
// When the L2 gas limit is smaller than the min L2 gas limit,
// reject the transaction
if l2GasLimit.Cmp(s.minL2GasLimit) == -1 {
return fmt.Errorf("%w: %d, use at least %d", fees.ErrL2GasLimitTooLow, l2GasLimit, s.minL2GasLimit)
}
// Only count the calldata here as the overhead of the fully encoded
// RLP transaction is handled inside of EncodeL2GasLimit
expectedTxGasLimit := fees.EncodeTxGasLimit(tx.Data(), l1GasPrice, l2GasLimit, l2GasPrice)
// This should only happen if the unscaled transaction fee is greater than 18.44 ETH
if !expectedTxGasLimit.IsUint64() {
return fmt.Errorf("fee overflow: %s", expectedTxGasLimit.String())
}
userFee := new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice())
expectedFee := new(big.Int).Mul(expectedTxGasLimit, fees.BigTxGasPrice)
// Reject user transactions that do not have large enough of a gas price.
// Allow for a buffer in case the gas price changes in between the user
// calling `eth_gasPrice` and submitting the transaction.
opts := fees.PaysEnoughOpts{
UserFee: userFee,
ExpectedFee: expectedFee,
ThresholdUp: s.feeThresholdUp,
ThresholdDown: s.feeThresholdDown,
UserGasPrice: tx.GasPrice(),
ExpectedGasPrice: l2GasPrice,
ThresholdUp: s.feeThresholdUp,
ThresholdDown: s.feeThresholdDown,
}
// Check the error type and return the correct error message to the user
if err := fees.PaysEnough(&opts); err != nil {
if errors.Is(err, fees.ErrFeeTooLow) {
return fmt.Errorf("%w: %d, use at least tx.gasLimit = %d and tx.gasPrice = %d",
fees.ErrFeeTooLow, userFee, expectedTxGasLimit, fees.BigTxGasPrice)
if errors.Is(err, fees.ErrGasPriceTooLow) {
return fmt.Errorf("%w: %d wei, use at least tx.gasPrice = %s wei",
fees.ErrGasPriceTooLow, tx.GasPrice(), l2GasPrice)
}
if errors.Is(err, fees.ErrFeeTooHigh) {
return fmt.Errorf("%w: %d, use less than %d * %f", fees.ErrFeeTooHigh, userFee,
expectedFee, s.feeThresholdUp)
if errors.Is(err, fees.ErrGasPriceTooHigh) {
return fmt.Errorf("%w: %d wei, use at most tx.gasPrice = %s wei",
fees.ErrGasPriceTooHigh, tx.GasPrice(), l2GasPrice)
}
return err
}
......
......@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
func setupLatestEthContextTest() (*SyncService, *EthContext) {
......@@ -500,16 +501,23 @@ func TestSyncServiceL1GasPrice(t *testing.T) {
t.Fatal("expected 0 gas price, got", gasBefore)
}
state, err := service.bc.State()
if err != nil {
t.Fatal("Cannot get state db")
}
l1GasPrice := big.NewInt(100000000000)
state.SetState(rcfg.L2GasPriceOracleAddress, rcfg.L1GasPriceSlot, common.BigToHash(l1GasPrice))
_, _ = state.Commit(false)
// Update the gas price
service.updateL1GasPrice()
service.updateL1GasPrice(state)
gasAfter, err := service.RollupGpo.SuggestL1GasPrice(context.Background())
if err != nil {
t.Fatal(err)
}
expect, _ := service.client.GetL1GasPrice()
if gasAfter.Cmp(expect) != 0 {
if gasAfter.Cmp(l1GasPrice) != 0 {
t.Fatal("expected 100 gas price, got", gasAfter)
}
}
......@@ -534,7 +542,7 @@ func TestSyncServiceL2GasPrice(t *testing.T) {
t.Fatal("Cannot get state db")
}
l2GasPrice := big.NewInt(100000000000)
state.SetState(l2GasPriceOracleAddress, l2GasPriceSlot, common.BigToHash(l2GasPrice))
state.SetState(rcfg.L2GasPriceOracleAddress, rcfg.L2GasPriceSlot, common.BigToHash(l2GasPrice))
_, _ = state.Commit(false)
service.updateL2GasPrice(state)
......@@ -600,7 +608,7 @@ func TestSyncServiceGasPriceOracleOwnerAddress(t *testing.T) {
// Update the owner in the state to a non zero address
updatedOwner := common.HexToAddress("0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8")
state.SetState(l2GasPriceOracleAddress, l2GasPriceOracleOwnerSlot, updatedOwner.Hash())
state.SetState(rcfg.L2GasPriceOracleAddress, rcfg.L2GasPriceOracleOwnerSlot, updatedOwner.Hash())
hash, _ := state.Commit(false)
// Update the cache based on the latest state root
......
#!/bin/bash
# Deterministically recreate the gas price oracle bindings
# for testing. This script depends on geth being in the monorepo
SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
ABIGEN="$SCRIPTS_DIR/../cmd/abigen/main.go"
CONTRACTS_PATH="$SCRIPTS_DIR/../../packages/contracts/artifacts/contracts"
GAS_PRICE_ORACLE="$CONTRACTS_PATH/L2/predeploys/OVM_GasPriceOracle.sol/OVM_GasPriceOracle.json"
OUT_DIR="$SCRIPTS_DIR/../rollup/fees/bindings"
mkdir -p $OUT_DIR
tmp=$(mktemp)
cat $GAS_PRICE_ORACLE | jq -r .bytecode > $tmp
cat $GAS_PRICE_ORACLE \
| jq .abi \
| go run $ABIGEN --pkg bindings \
--abi - \
--out $OUT_DIR/gaspriceoracle.go \
--type GasPriceOracle \
--bin "$tmp"
rm $tmp
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