github.com/ethereum-optimism/optimism/l2geth@v0.0.0-20230612200230-50b04ade19e3/rollup/fees/rollup_fee.go (about)

     1  package fees
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"math/big"
    10  
    11  	"github.com/ethereum-optimism/optimism/l2geth/common"
    12  	"github.com/ethereum-optimism/optimism/l2geth/core/types"
    13  	"github.com/ethereum-optimism/optimism/l2geth/params"
    14  	"github.com/ethereum-optimism/optimism/l2geth/rollup/rcfg"
    15  )
    16  
    17  var (
    18  	// ErrGasPriceTooLow represents the error case of then the user pays too little
    19  	ErrGasPriceTooLow = errors.New("gas price too low")
    20  	// ErrGasPriceTooHigh represents the error case of when the user pays too much
    21  	ErrGasPriceTooHigh = errors.New("gas price too high")
    22  	// ErrInsufficientFunds represents the error case of when the user doesn't
    23  	// have enough funds to cover the transaction
    24  	ErrInsufficientFunds = errors.New("insufficient funds for l1Fee + l2Fee + value")
    25  	// errMissingInput represents the error case of missing required input to
    26  	// PaysEnough
    27  	errMissingInput = errors.New("missing input")
    28  	// ErrL2GasLimitTooLow represents the error case of when a user sends a
    29  	// transaction to the sequencer with a L2 gas limit that is too small
    30  	ErrL2GasLimitTooLow = errors.New("L2 gas limit too low")
    31  	// errTransactionSigned represents the error case of passing in a signed
    32  	// transaction to the L1 fee calculation routine. The signature is accounted
    33  	// for externally
    34  	errTransactionSigned = errors.New("transaction is signed")
    35  	// big10 is used for decimal scaling
    36  	big10 = new(big.Int).SetUint64(10)
    37  )
    38  
    39  // Message represents the interface of a message.
    40  // It should be a subset of the methods found on
    41  // types.Message
    42  type Message interface {
    43  	From() common.Address
    44  	To() *common.Address
    45  	GasPrice() *big.Int
    46  	Gas() uint64
    47  	Value() *big.Int
    48  	Nonce() uint64
    49  	Data() []byte
    50  }
    51  
    52  // StateDB represents the StateDB interface
    53  // required to compute the L1 fee
    54  type StateDB interface {
    55  	GetState(common.Address, common.Hash) common.Hash
    56  }
    57  
    58  // RollupOracle represents the interface of the in
    59  // memory cache of the gas price oracle
    60  type RollupOracle interface {
    61  	SuggestL1GasPrice(ctx context.Context) (*big.Int, error)
    62  	SuggestL2GasPrice(ctx context.Context) (*big.Int, error)
    63  	SuggestOverhead(ctx context.Context) (*big.Int, error)
    64  	SuggestScalar(ctx context.Context) (*big.Float, error)
    65  }
    66  
    67  // CalculateTotalFee will calculate the total fee given a transaction.
    68  // This function is used at the RPC layer to ensure that users
    69  // have enough ETH to cover their fee
    70  func CalculateTotalFee(tx *types.Transaction, gpo RollupOracle) (*big.Int, error) {
    71  	// Read the variables from the cache
    72  	l1GasPrice, err := gpo.SuggestL1GasPrice(context.Background())
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	overhead, err := gpo.SuggestOverhead(context.Background())
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	scalar, err := gpo.SuggestScalar(context.Background())
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	unsigned := copyTransaction(tx)
    86  	raw, err := rlpEncode(unsigned)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
    92  	l2GasLimit := new(big.Int).SetUint64(tx.Gas())
    93  	l2Fee := new(big.Int).Mul(tx.GasPrice(), l2GasLimit)
    94  	fee := new(big.Int).Add(l1Fee, l2Fee)
    95  	return fee, nil
    96  }
    97  
    98  // CalculateMsgFee will calculate the total fee given a Message.
    99  // This function is used during the state transition to transfer
   100  // value to the sequencer. Since Messages do not have a signature
   101  // and the signature is submitted to L1 in a batch, extra bytes
   102  // are padded to the raw transaction
   103  func CalculateTotalMsgFee(msg Message, state StateDB, gasUsed *big.Int, gpo *common.Address) (*big.Int, error) {
   104  	if gpo == nil {
   105  		gpo = &rcfg.L2GasPriceOracleAddress
   106  	}
   107  
   108  	l1Fee, err := CalculateL1MsgFee(msg, state, gpo)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	// Multiply the gas price and the gas used to get the L2 fee
   113  	l2Fee := new(big.Int).Mul(msg.GasPrice(), gasUsed)
   114  	// Add the L1 cost and the L2 cost to get the total fee being paid
   115  	fee := new(big.Int).Add(l1Fee, l2Fee)
   116  	return fee, nil
   117  }
   118  
   119  // CalculateL1MsgFee computes the L1 portion of the fee given
   120  // a Message and a StateDB
   121  func CalculateL1MsgFee(msg Message, state StateDB, gpo *common.Address) (*big.Int, error) {
   122  	tx := asTransaction(msg)
   123  	raw, err := rlpEncode(tx)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	if gpo == nil {
   129  		gpo = &rcfg.L2GasPriceOracleAddress
   130  	}
   131  
   132  	l1GasPrice, overhead, scalar := readGPOStorageSlots(*gpo, state)
   133  	l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
   134  	return l1Fee, nil
   135  }
   136  
   137  // CalculateL1Fee computes the L1 fee
   138  func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Float) *big.Int {
   139  	l1GasUsed := CalculateL1GasUsed(data, overhead)
   140  	l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice)
   141  	return mulByFloat(l1Fee, scalar)
   142  }
   143  
   144  // CalculateL1GasUsed computes the L1 gas used based on the calldata and
   145  // constant sized overhead. The overhead can be decreased as the cost of the
   146  // batch submission goes down via contract optimizations. This will not overflow
   147  // under standard network conditions.
   148  func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int {
   149  	zeroes, ones := zeroesAndOnes(data)
   150  	zeroesGas := zeroes * params.TxDataZeroGas
   151  	onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028
   152  	l1Gas := new(big.Int).SetUint64(zeroesGas + onesGas)
   153  	return new(big.Int).Add(l1Gas, overhead)
   154  }
   155  
   156  // DeriveL1GasInfo reads L1 gas related information to be included
   157  // on the receipt
   158  func DeriveL1GasInfo(msg Message, state StateDB) (*big.Int, *big.Int, *big.Int, *big.Float, error) {
   159  	tx := asTransaction(msg)
   160  	raw, err := rlpEncode(tx)
   161  	if err != nil {
   162  		return nil, nil, nil, nil, err
   163  	}
   164  
   165  	l1GasPrice, overhead, scalar := readGPOStorageSlots(rcfg.L2GasPriceOracleAddress, state)
   166  	l1GasUsed := CalculateL1GasUsed(raw, overhead)
   167  	l1Fee := CalculateL1Fee(raw, overhead, l1GasPrice, scalar)
   168  	return l1Fee, l1GasPrice, l1GasUsed, scalar, nil
   169  }
   170  
   171  func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Float) {
   172  	l1GasPrice := state.GetState(addr, rcfg.L1GasPriceSlot)
   173  	overhead := state.GetState(addr, rcfg.OverheadSlot)
   174  	scalar := state.GetState(addr, rcfg.ScalarSlot)
   175  	decimals := state.GetState(addr, rcfg.DecimalsSlot)
   176  	scaled := ScaleDecimals(scalar.Big(), decimals.Big())
   177  	return l1GasPrice.Big(), overhead.Big(), scaled
   178  }
   179  
   180  // ScaleDecimals will scale a value by decimals
   181  func ScaleDecimals(scalar, decimals *big.Int) *big.Float {
   182  	// 10**decimals
   183  	divisor := new(big.Int).Exp(big10, decimals, nil)
   184  	fscalar := new(big.Float).SetInt(scalar)
   185  	fdivisor := new(big.Float).SetInt(divisor)
   186  	// fscalar / fdivisor
   187  	return new(big.Float).Quo(fscalar, fdivisor)
   188  }
   189  
   190  // rlpEncode RLP encodes the transaction into bytes
   191  // When a signature is not included, set pad to true to
   192  // fill in a dummy signature full on non 0 bytes
   193  func rlpEncode(tx *types.Transaction) ([]byte, error) {
   194  	raw := new(bytes.Buffer)
   195  	if err := tx.EncodeRLP(raw); err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	r, v, s := tx.RawSignatureValues()
   200  	if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 {
   201  		return nil, errTransactionSigned
   202  	}
   203  
   204  	// Slice off the 0 bytes representing the signature
   205  	b := raw.Bytes()
   206  	return b[:len(b)-3], nil
   207  }
   208  
   209  // asTransaction turns a Message into a types.Transaction
   210  func asTransaction(msg Message) *types.Transaction {
   211  	if msg.To() == nil {
   212  		return types.NewContractCreation(
   213  			msg.Nonce(),
   214  			msg.Value(),
   215  			msg.Gas(),
   216  			msg.GasPrice(),
   217  			msg.Data(),
   218  		)
   219  	}
   220  	return types.NewTransaction(
   221  		msg.Nonce(),
   222  		*msg.To(),
   223  		msg.Value(),
   224  		msg.Gas(),
   225  		msg.GasPrice(),
   226  		msg.Data(),
   227  	)
   228  }
   229  
   230  // copyTransaction copies the transaction, removing the signature
   231  func copyTransaction(tx *types.Transaction) *types.Transaction {
   232  	if tx.To() == nil {
   233  		return types.NewContractCreation(
   234  			tx.Nonce(),
   235  			tx.Value(),
   236  			tx.Gas(),
   237  			tx.GasPrice(),
   238  			tx.Data(),
   239  		)
   240  	}
   241  	return types.NewTransaction(
   242  		tx.Nonce(),
   243  		*tx.To(),
   244  		tx.Value(),
   245  		tx.Gas(),
   246  		tx.GasPrice(),
   247  		tx.Data(),
   248  	)
   249  }
   250  
   251  // PaysEnoughOpts represent the options to PaysEnough
   252  type PaysEnoughOpts struct {
   253  	UserGasPrice, ExpectedGasPrice *big.Int
   254  	ThresholdUp, ThresholdDown     *big.Float
   255  }
   256  
   257  // PaysEnough returns an error if the fee is not large enough
   258  // `GasPrice` and `Fee` are required arguments.
   259  func PaysEnough(opts *PaysEnoughOpts) error {
   260  	if opts.UserGasPrice == nil {
   261  		return fmt.Errorf("%w: no user fee", errMissingInput)
   262  	}
   263  	if opts.ExpectedGasPrice == nil {
   264  		return fmt.Errorf("%w: no expected fee", errMissingInput)
   265  	}
   266  
   267  	fee := new(big.Int).Set(opts.ExpectedGasPrice)
   268  	// Allow for a downward buffer to protect against L1 gas price volatility
   269  	if opts.ThresholdDown != nil {
   270  		fee = mulByFloat(fee, opts.ThresholdDown)
   271  	}
   272  	// Protect the sequencer from being underpaid
   273  	// if user fee < expected fee, return error
   274  	if opts.UserGasPrice.Cmp(fee) == -1 {
   275  		return ErrGasPriceTooLow
   276  	}
   277  	// Protect users from overpaying by too much
   278  	if opts.ThresholdUp != nil {
   279  		// overpaying = user fee - expected fee
   280  		overpaying := new(big.Int).Sub(opts.UserGasPrice, opts.ExpectedGasPrice)
   281  		threshold := mulByFloat(opts.ExpectedGasPrice, opts.ThresholdUp)
   282  		// if overpaying > threshold, return error
   283  		if overpaying.Cmp(threshold) == 1 {
   284  			return ErrGasPriceTooHigh
   285  		}
   286  	}
   287  	return nil
   288  }
   289  
   290  // zeroesAndOnes counts the number of 0 bytes and non 0 bytes in a byte slice
   291  func zeroesAndOnes(data []byte) (uint64, uint64) {
   292  	var zeroes uint64
   293  	var ones uint64
   294  	for _, byt := range data {
   295  		if byt == 0 {
   296  			zeroes++
   297  		} else {
   298  			ones++
   299  		}
   300  	}
   301  	return zeroes, ones
   302  }
   303  
   304  // mulByFloat multiplies a big.Int by a float and returns the
   305  // big.Int rounded upwards
   306  func mulByFloat(num *big.Int, float *big.Float) *big.Int {
   307  	n := new(big.Float).SetUint64(num.Uint64())
   308  	product := n.Mul(n, float)
   309  	pfloat, _ := product.Float64()
   310  	rounded := math.Ceil(pfloat)
   311  	return new(big.Int).SetUint64(uint64(rounded))
   312  }