Skip to content

Commit

Permalink
core: handle base fee, l1 availability fee, tx fee (ethereum#27)
Browse files Browse the repository at this point in the history
base fee and l1 availability fee are redirected to configurable
accounts; where as tx fee is redirected as coinbase reward
  • Loading branch information
tuxcanfly authored and protolambda committed Sep 6, 2022
1 parent 98bc5b0 commit d65c418
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 3 deletions.
1 change: 1 addition & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,7 @@ func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
func (m callMsg) Mint() *big.Int { return nil }
func (m callMsg) L1Cost() *big.Int { return nil }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
Expand Down
4 changes: 4 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,8 @@ var (

// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa")

// ErrNoL1Cost is returned if the transaction requires L1 cost but doesn't
// provide it.
ErrNoL1Cost = errors.New("l1 cost is nil")
)
100 changes: 100 additions & 0 deletions core/l1_fee_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package core

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)

var big10 = big.NewInt(10)

var (
L1BaseFeeSlot = common.BigToHash(big.NewInt(1))
OverheadSlot = common.BigToHash(big.NewInt(3))
ScalarSlot = common.BigToHash(big.NewInt(4))
DecimalsSlot = common.BigToHash(big.NewInt(5))
)

var (
OVM_GasPriceOracleAddr = common.HexToAddress("0x420000000000000000000000000000000000000F")
L1BlockAddr = common.HexToAddress("0x4200000000000000000000000000000000000015")
)

// calculateL1GasUsed returns the gas used to include the transaction data in
// the calldata on L1.
func calculateL1GasUsed(data []byte, overhead *big.Int) *big.Int {
var zeroes uint64
var ones uint64
for _, byt := range data {
if byt == 0 {
zeroes++
} else {
ones++
}
}

zeroesGas := zeroes * params.TxDataZeroGas
onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028
l1Gas := new(big.Int).SetUint64(zeroesGas + onesGas)
return new(big.Int).Add(l1Gas, overhead)
}

// L1FeeContext includes all the context necessary to calculate the cost of
// including the transaction in L1.
type L1FeeContext struct {
BaseFee *big.Int
Overhead *big.Int
Scalar *big.Int
Decimals *big.Int
Divisor *big.Int
}

// NewL1FeeContext returns a context for calculating L1 fee cost.
// This depends on the oracles because gas costs can change over time.
func NewL1FeeContext(statedb *state.StateDB) *L1FeeContext {
l1BaseFee := statedb.GetState(L1BlockAddr, L1BaseFeeSlot).Big()
overhead := statedb.GetState(OVM_GasPriceOracleAddr, OverheadSlot).Big()
scalar := statedb.GetState(OVM_GasPriceOracleAddr, ScalarSlot).Big()
decimals := statedb.GetState(OVM_GasPriceOracleAddr, DecimalsSlot).Big()
divisor := new(big.Int).Exp(big10, decimals, nil)

return &L1FeeContext{
BaseFee: l1BaseFee,
Overhead: overhead,
Scalar: scalar,
Decimals: decimals,
Divisor: divisor,
}
}

// L1Cost returns the L1 fee cost.
func L1Cost(tx *types.Transaction, ctx *L1FeeContext) *big.Int {
bytes, err := tx.MarshalBinary()
if err != nil {
panic(err)
}
l1GasUsed := calculateL1GasUsed(bytes, ctx.Overhead)
l1Cost := new(big.Int).Mul(l1GasUsed, ctx.BaseFee)
l1Cost = l1Cost.Mul(l1Cost, ctx.Scalar)
l1Cost = l1Cost.Div(l1Cost, ctx.Divisor)
return l1Cost
}
24 changes: 22 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,19 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}
blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)

opts := make([][]types.MsgOption, len(block.Transactions()))
if p.config.Optimism != nil && p.config.Optimism.Enabled {
l1FeeContext := NewL1FeeContext(statedb)
for i, tx := range block.Transactions() {
l1Cost := L1Cost(tx, l1FeeContext)
opts[i] = append(opts[i], types.L1CostOption(l1Cost))
}
}

// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee, opts[i]...)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
Expand Down Expand Up @@ -142,10 +152,20 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, author *com
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
var opts []types.MsgOption = nil
isOptimism := config.Optimism != nil && config.Optimism.Enabled
if isOptimism {
l1FeeContext := NewL1FeeContext(statedb)
l1Cost := L1Cost(tx, l1FeeContext)
opts = append(opts, types.L1CostOption(l1Cost))
}
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee, opts...)
if err != nil {
return nil, err
}
if isOptimism && msg.Nonce() != types.DepositsNonce && msg.L1Cost() == nil {
return nil, ErrNoL1Cost
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
Expand Down
18 changes: 18 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ type Message interface {
// Mint is nil if there is no minting
Mint() *big.Int

L1Cost() *big.Int

Nonce() uint64
IsFake() bool
Data() []byte
Expand Down Expand Up @@ -197,11 +199,17 @@ func (st *StateTransition) to() common.Address {
func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.Gas())
mgval = mgval.Mul(mgval, st.gasPrice)
if st.msg.L1Cost() != nil {
mgval = mgval.Add(mgval, st.msg.L1Cost())
}
balanceCheck := mgval
if st.gasFeeCap != nil {
balanceCheck = new(big.Int).SetUint64(st.msg.Gas())
balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap)
balanceCheck.Add(balanceCheck, st.value)
if st.msg.L1Cost() != nil {
balanceCheck.Add(balanceCheck, st.msg.L1Cost())
}
}
if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want)
Expand Down Expand Up @@ -400,6 +408,16 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
st.state.AddBalance(st.evm.Context.Coinbase, fee)
}

if st.evm.ChainConfig().Optimism != nil {
optimismConfig := st.evm.ChainConfig().Optimism
if optimismConfig.Enabled {
st.state.AddBalance(optimismConfig.BaseFeeRecipient, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee))
if st.msg.L1Cost() != nil {
st.state.AddBalance(optimismConfig.L1FeeRecipient, st.msg.L1Cost())
}
}
}

return &ExecutionResult{
UsedGas: st.gasUsed(),
Err: vmerr,
Expand Down
35 changes: 34 additions & 1 deletion core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"container/heap"
"errors"
"fmt"
"io"
"math/big"
"sync/atomic"
Expand Down Expand Up @@ -612,6 +613,22 @@ func (t *TransactionsByPriceAndNonce) Pop() {
heap.Pop(&t.heads)
}

// MsgOption defines optional params for AsMessage.
// This is currently used to update Message with L1Cost but can be accommodated
// for additional params to be applied to tx or msg.
// See L1CostOption for usage.
type MsgOption interface {
Apply(tx *Transaction, msg *Message) error
}

// MsgOptionFunc defines the optional param func.
type MsgOptionFunc func(tx *Transaction, msg *Message) error

// Apply applies the optional param func and is called from AsMessage.
func (fn MsgOptionFunc) Apply(tx *Transaction, msg *Message) error {
return fn(tx, msg)
}

// Message is a fully derived transaction and implements core.Message
//
// NOTE: In a future PR this will be removed.
Expand All @@ -628,6 +645,7 @@ type Message struct {
accessList AccessList
isFake bool
mint *big.Int
l1Cost *big.Int
}

func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message {
Expand All @@ -648,7 +666,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
}

// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int, opts ...MsgOption) (Message, error) {
msg := Message{
nonce: tx.Nonce(),
gasLimit: tx.Gas(),
Expand All @@ -661,8 +679,14 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
accessList: tx.AccessList(),
isFake: false,
}
for i, opt := range opts {
if err := opt.Apply(tx, &msg); err != nil {
return Message{}, fmt.Errorf("failed to apply option %d: %w", i, err)
}
}
if dep, ok := tx.inner.(*DepositTx); ok {
msg.mint = dep.Mint
msg.l1Cost = nil
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
Expand All @@ -685,6 +709,7 @@ func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) IsFake() bool { return m.isFake }
func (m Message) Mint() *big.Int { return m.mint }
func (m Message) L1Cost() *big.Int { return m.l1Cost }

// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
Expand All @@ -694,3 +719,11 @@ func copyAddressPtr(a *common.Address) *common.Address {
cpy := *a
return &cpy
}

// L1CostOption applies the l1 cost to the msg
func L1CostOption(cost *big.Int) MsgOption {
return MsgOptionFunc(func(_ *Transaction, msg *Message) error {
msg.l1Cost = cost
return nil
})
}

0 comments on commit d65c418

Please sign in to comment.