go-ethereum/rollup/fees/rollup_fee.go
HAOYUatHZ cfbb26922f
fix: improve "insufficient funds for l1fee" error msg (#237)
fix(l2geth): improve "insufficient funds for l1fee" error msg
2023-03-02 20:03:37 +08:00

208 lines
5.7 KiB
Go

package fees
import (
"bytes"
"errors"
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
)
var (
// 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")
)
// 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
GetBalance(addr common.Address) *big.Int
}
// CalculateL1MsgFee computes the L1 portion of the fee given
// a Message and a StateDB
// Reference: https://github.com/ethereum-optimism/optimism/blob/develop/l2geth/rollup/fees/rollup_fee.go
func CalculateL1MsgFee(msg Message, state StateDB) (*big.Int, error) {
tx := asTransaction(msg)
raw, err := rlpEncode(tx)
if err != nil {
return nil, err
}
l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state)
l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar)
return l1Fee, 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(),
)
}
// 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 nil, errTransactionSigned
}
// Slice off the 0 bytes representing the signature
b := raw.Bytes()
return b[:len(b)-3], nil
}
func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Int) {
l1BaseFee := state.GetState(addr, rcfg.L1BaseFeeSlot)
overhead := state.GetState(addr, rcfg.OverheadSlot)
scalar := state.GetState(addr, rcfg.ScalarSlot)
return l1BaseFee.Big(), overhead.Big(), scalar.Big()
}
// CalculateL1Fee computes the L1 fee
func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) *big.Int {
l1GasUsed := CalculateL1GasUsed(data, overhead)
l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice)
return mulAndScale(l1Fee, scalar, rcfg.Precision)
}
// 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)
}
// 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
for _, byt := range data {
if byt == 0 {
zeroes++
} else {
ones++
}
}
return zeroes, ones
}
// mulAndScale multiplies a big.Int by a big.Int and then scale it by precision,
// rounded towards zero
func mulAndScale(x *big.Int, y *big.Int, precision *big.Int) *big.Int {
z := new(big.Int).Mul(x, y)
return new(big.Int).Quo(z, precision)
}
// 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(),
)
}
func CalculateFees(tx *types.Transaction, state StateDB) (*big.Int, *big.Int, *big.Int, error) {
unsigned := copyTransaction(tx)
raw, err := rlpEncode(unsigned)
if err != nil {
return nil, nil, nil, err
}
l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state)
l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar)
l2GasLimit := new(big.Int).SetUint64(tx.Gas())
l2Fee := new(big.Int).Mul(tx.GasPrice(), l2GasLimit)
fee := new(big.Int).Add(l1Fee, l2Fee)
return l1Fee, l2Fee, fee, nil
}
func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error {
from, err := types.Sender(signer, tx)
if err != nil {
return errors.New("invalid transaction: invalid sender")
}
balance := state.GetBalance(from)
l1Fee, l2Fee, _, err := CalculateFees(tx, state)
if err != nil {
return fmt.Errorf("invalid transaction: %w", err)
}
cost := tx.Value()
cost = cost.Add(cost, l2Fee)
if balance.Cmp(cost) < 0 {
return errors.New("invalid transaction: insufficient funds for gas * price + value")
}
cost = cost.Add(cost, l1Fee)
if balance.Cmp(cost) < 0 {
return errors.New("invalid transaction: insufficient funds for l1fee + gas * price + value")
}
// TODO: check GasPrice is in an expected range
return nil
}