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 ( ...@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg" "github.com/ethereum/go-ethereum/rollup/rcfg"
) )
...@@ -61,6 +62,8 @@ type StateTransition struct { ...@@ -61,6 +62,8 @@ type StateTransition struct {
data []byte data []byte
state vm.StateDB state vm.StateDB
evm *vm.EVM evm *vm.EVM
// UsingOVM
l1Fee *big.Int
} }
// Message represents a message sent to a contract. // Message represents a message sent to a contract.
...@@ -122,6 +125,15 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo ...@@ -122,6 +125,15 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo
// NewStateTransition initialises and returns a new state transition object. // NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { 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{ return &StateTransition{
gp: gp, gp: gp,
evm: evm, evm: evm,
...@@ -130,6 +142,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition ...@@ -130,6 +142,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
value: msg.Value(), value: msg.Value(),
data: msg.Data(), data: msg.Data(),
state: evm.StateDB, state: evm.StateDB,
l1Fee: l1Fee,
} }
} }
...@@ -163,6 +176,15 @@ func (st *StateTransition) useGas(amount uint64) error { ...@@ -163,6 +176,15 @@ func (st *StateTransition) useGas(amount uint64) error {
func (st *StateTransition) buyGas() error { func (st *StateTransition) buyGas() error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) 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 { if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas return errInsufficientBalanceForGas
} }
...@@ -243,7 +265,16 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo ...@@ -243,7 +265,16 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
} }
} }
st.refundGas() 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 return ret, st.gasUsed(), vmerr != nil, err
} }
......
...@@ -565,8 +565,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { ...@@ -565,8 +565,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
} }
// Transactor should have enough funds to cover the costs // Transactor should have enough funds to cover the costs
// cost == V + GP * GL // cost == V + GP * GL
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { if !rcfg.UsingOVM {
return ErrInsufficientFunds // 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. // Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul) intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul)
......
...@@ -27,7 +27,6 @@ import ( ...@@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "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 //go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
...@@ -214,14 +213,12 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { ...@@ -214,14 +213,12 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return nil return nil
} }
func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } 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) 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) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) SetNonce(nonce uint64) { tx.data.AccountNonce = nonce } func (tx *Transaction) SetNonce(nonce uint64) { tx.data.AccountNonce = nonce }
// To returns the recipient address of the transaction. // To returns the recipient address of the transaction.
......
...@@ -6,23 +6,28 @@ import ( ...@@ -6,23 +6,28 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
// RollupOracle holds the L1 and L2 gas prices for fee calculation // RollupOracle holds the L1 and L2 gas prices for fee calculation
type RollupOracle struct { type RollupOracle struct {
l1GasPrice *big.Int l1GasPrice *big.Int
l2GasPrice *big.Int l2GasPrice *big.Int
overhead *big.Int
scalar *big.Float
l1GasPriceLock sync.RWMutex l1GasPriceLock sync.RWMutex
l2GasPriceLock sync.RWMutex l2GasPriceLock sync.RWMutex
overheadLock sync.RWMutex
scalarLock sync.RWMutex
} }
// NewRollupOracle returns an initialized RollupOracle // NewRollupOracle returns an initialized RollupOracle
func NewRollupOracle() *RollupOracle { func NewRollupOracle() *RollupOracle {
return &RollupOracle{ return &RollupOracle{
l1GasPrice: new(big.Int), l1GasPrice: new(big.Int),
l2GasPrice: new(big.Int), l2GasPrice: new(big.Int),
l1GasPriceLock: sync.RWMutex{}, overhead: new(big.Int),
l2GasPriceLock: sync.RWMutex{}, scalar: new(big.Float),
} }
} }
...@@ -59,3 +64,38 @@ func (gpo *RollupOracle) SetL2GasPrice(gasPrice *big.Int) error { ...@@ -59,3 +64,38 @@ func (gpo *RollupOracle) SetL2GasPrice(gasPrice *big.Int) error {
log.Info("Set L2 Gas Price", "gasprice", gpo.l2GasPrice) log.Info("Set L2 Gas Price", "gasprice", gpo.l2GasPrice)
return nil 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 ( ...@@ -51,10 +51,6 @@ import (
var errOVMUnsupported = errors.New("OVM: Unsupported RPC Method") 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. // PublicEthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone. // It offers only methods that operate on public data that is freely available to anyone.
type PublicEthereumAPI struct { type PublicEthereumAPI struct {
...@@ -66,9 +62,13 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI { ...@@ -66,9 +62,13 @@ func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
return &PublicEthereumAPI{b} 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) { 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 // ProtocolVersion returns the current Ethereum protocol version this node supports
......
...@@ -506,7 +506,7 @@ func (w *worker) mainLoop() { ...@@ -506,7 +506,7 @@ func (w *worker) mainLoop() {
} }
w.pendingMu.Unlock() w.pendingMu.Unlock()
} else { } else {
log.Debug("Problem committing transaction: %w", err) log.Debug("Problem committing transaction", "msg", err)
} }
case ev := <-w.txsCh: case ev := <-w.txsCh:
......
This diff is collapsed.
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
}
This diff is collapsed.
...@@ -6,105 +6,8 @@ import ( ...@@ -6,105 +6,8 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "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) { func TestPaysEnough(t *testing.T) {
tests := map[string]struct { tests := map[string]struct {
opts *PaysEnoughOpts opts *PaysEnoughOpts
...@@ -112,84 +15,84 @@ func TestPaysEnough(t *testing.T) { ...@@ -112,84 +15,84 @@ func TestPaysEnough(t *testing.T) {
}{ }{
"missing-gas-price": { "missing-gas-price": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: nil, UserGasPrice: nil,
ExpectedFee: new(big.Int), ExpectedGasPrice: new(big.Int),
ThresholdUp: nil, ThresholdUp: nil,
ThresholdDown: nil, ThresholdDown: nil,
}, },
err: errMissingInput, err: errMissingInput,
}, },
"missing-fee": { "missing-fee": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: nil, UserGasPrice: nil,
ExpectedFee: nil, ExpectedGasPrice: nil,
ThresholdUp: nil, ThresholdUp: nil,
ThresholdDown: nil, ThresholdDown: nil,
}, },
err: errMissingInput, err: errMissingInput,
}, },
"equal-fee": { "equal-fee": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: common.Big1, UserGasPrice: common.Big1,
ExpectedFee: common.Big1, ExpectedGasPrice: common.Big1,
ThresholdUp: nil, ThresholdUp: nil,
ThresholdDown: nil, ThresholdDown: nil,
}, },
err: nil, err: nil,
}, },
"fee-too-low": { "fee-too-low": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: common.Big1, UserGasPrice: common.Big1,
ExpectedFee: common.Big2, ExpectedGasPrice: common.Big2,
ThresholdUp: nil, ThresholdUp: nil,
ThresholdDown: nil, ThresholdDown: nil,
}, },
err: ErrFeeTooLow, err: ErrGasPriceTooLow,
}, },
"fee-threshold-down": { "fee-threshold-down": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: common.Big1, UserGasPrice: common.Big1,
ExpectedFee: common.Big2, ExpectedGasPrice: common.Big2,
ThresholdUp: nil, ThresholdUp: nil,
ThresholdDown: new(big.Float).SetFloat64(0.5), ThresholdDown: new(big.Float).SetFloat64(0.5),
}, },
err: nil, err: nil,
}, },
"fee-threshold-up": { "fee-threshold-up": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: common.Big256, UserGasPrice: common.Big256,
ExpectedFee: common.Big1, ExpectedGasPrice: common.Big1,
ThresholdUp: new(big.Float).SetFloat64(1.5), ThresholdUp: new(big.Float).SetFloat64(1.5),
ThresholdDown: nil, ThresholdDown: nil,
}, },
err: ErrFeeTooHigh, err: ErrGasPriceTooHigh,
}, },
"fee-too-low-high": { "fee-too-low-high": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: new(big.Int).SetUint64(10_000), UserGasPrice: new(big.Int).SetUint64(10_000),
ExpectedFee: new(big.Int).SetUint64(1), ExpectedGasPrice: new(big.Int).SetUint64(1),
ThresholdUp: new(big.Float).SetFloat64(3), ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8), ThresholdDown: new(big.Float).SetFloat64(0.8),
}, },
err: ErrFeeTooHigh, err: ErrGasPriceTooHigh,
}, },
"fee-too-low-down": { "fee-too-low-down": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: new(big.Int).SetUint64(1), UserGasPrice: new(big.Int).SetUint64(1),
ExpectedFee: new(big.Int).SetUint64(10_000), ExpectedGasPrice: new(big.Int).SetUint64(10_000),
ThresholdUp: new(big.Float).SetFloat64(3), ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8), ThresholdDown: new(big.Float).SetFloat64(0.8),
}, },
err: ErrFeeTooLow, err: ErrGasPriceTooLow,
}, },
"fee-too-low-down-2": { "fee-too-low-down-2": {
opts: &PaysEnoughOpts{ opts: &PaysEnoughOpts{
UserFee: new(big.Int).SetUint64(0), UserGasPrice: new(big.Int).SetUint64(0),
ExpectedFee: new(big.Int).SetUint64(10_000), ExpectedGasPrice: new(big.Int).SetUint64(10_000),
ThresholdUp: new(big.Float).SetFloat64(3), ThresholdUp: new(big.Float).SetFloat64(3),
ThresholdDown: new(big.Float).SetFloat64(0.8), ThresholdDown: new(big.Float).SetFloat64(0.8),
}, },
err: ErrFeeTooLow, err: ErrGasPriceTooLow,
}, },
} }
......
package rcfg package rcfg
import ( import (
"math/big"
"os" "os"
"github.com/ethereum/go-ethereum/common"
) )
// UsingOVM is used to enable or disable functionality necessary for the OVM. // UsingOVM is used to enable or disable functionality necessary for the OVM.
var UsingOVM bool 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() { func init() {
UsingOVM = os.Getenv("USING_OVM") == "true" UsingOVM = os.Getenv("USING_OVM") == "true"
} }
This diff is collapsed.
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
) )
func setupLatestEthContextTest() (*SyncService, *EthContext) { func setupLatestEthContextTest() (*SyncService, *EthContext) {
...@@ -500,16 +501,23 @@ func TestSyncServiceL1GasPrice(t *testing.T) { ...@@ -500,16 +501,23 @@ func TestSyncServiceL1GasPrice(t *testing.T) {
t.Fatal("expected 0 gas price, got", gasBefore) 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 // Update the gas price
service.updateL1GasPrice() service.updateL1GasPrice(state)
gasAfter, err := service.RollupGpo.SuggestL1GasPrice(context.Background()) gasAfter, err := service.RollupGpo.SuggestL1GasPrice(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expect, _ := service.client.GetL1GasPrice() if gasAfter.Cmp(l1GasPrice) != 0 {
if gasAfter.Cmp(expect) != 0 {
t.Fatal("expected 100 gas price, got", gasAfter) t.Fatal("expected 100 gas price, got", gasAfter)
} }
} }
...@@ -534,7 +542,7 @@ func TestSyncServiceL2GasPrice(t *testing.T) { ...@@ -534,7 +542,7 @@ func TestSyncServiceL2GasPrice(t *testing.T) {
t.Fatal("Cannot get state db") t.Fatal("Cannot get state db")
} }
l2GasPrice := big.NewInt(100000000000) l2GasPrice := big.NewInt(100000000000)
state.SetState(l2GasPriceOracleAddress, l2GasPriceSlot, common.BigToHash(l2GasPrice)) state.SetState(rcfg.L2GasPriceOracleAddress, rcfg.L2GasPriceSlot, common.BigToHash(l2GasPrice))
_, _ = state.Commit(false) _, _ = state.Commit(false)
service.updateL2GasPrice(state) service.updateL2GasPrice(state)
...@@ -600,7 +608,7 @@ func TestSyncServiceGasPriceOracleOwnerAddress(t *testing.T) { ...@@ -600,7 +608,7 @@ func TestSyncServiceGasPriceOracleOwnerAddress(t *testing.T) {
// Update the owner in the state to a non zero address // Update the owner in the state to a non zero address
updatedOwner := common.HexToAddress("0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8") updatedOwner := common.HexToAddress("0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8")
state.SetState(l2GasPriceOracleAddress, l2GasPriceOracleOwnerSlot, updatedOwner.Hash()) state.SetState(rcfg.L2GasPriceOracleAddress, rcfg.L2GasPriceOracleOwnerSlot, updatedOwner.Hash())
hash, _ := state.Commit(false) hash, _ := state.Commit(false)
// Update the cache based on the latest state root // 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