mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
* feat: reject txs with max l1 data fee * hide mutable constant using getter function * nit
267 lines
7.8 KiB
Go
267 lines
7.8 KiB
Go
package fees
|
|
|
|
import (
|
|
"bytes"
|
|
"math"
|
|
"math/big"
|
|
|
|
"github.com/holiman/uint256"
|
|
|
|
"github.com/scroll-tech/go-ethereum/common"
|
|
"github.com/scroll-tech/go-ethereum/core/types"
|
|
"github.com/scroll-tech/go-ethereum/crypto"
|
|
"github.com/scroll-tech/go-ethereum/params"
|
|
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
|
|
)
|
|
|
|
var (
|
|
// txExtraDataBytes is the number of bytes that we commit to L1 in addition
|
|
// to the RLP-encoded signed transaction. Note that these are all assumed
|
|
// to be non-zero.
|
|
// - tx length prefix: 4 bytes
|
|
txExtraDataBytes = uint64(4)
|
|
|
|
// L1 data fee cap.
|
|
l1DataFeeCap = new(big.Int).SetUint64(math.MaxUint64)
|
|
)
|
|
|
|
func MaxL1DataFee() *big.Int {
|
|
return new(big.Int).Set(l1DataFeeCap)
|
|
}
|
|
|
|
// 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
|
|
GasFeeCap() *big.Int
|
|
GasTipCap() *big.Int
|
|
Value() *big.Int
|
|
Nonce() uint64
|
|
Data() []byte
|
|
AccessList() types.AccessList
|
|
IsL1MessageTx() bool
|
|
SetCodeAuthorizations() []types.SetCodeAuthorization
|
|
}
|
|
|
|
// StateDB represents the StateDB interface
|
|
// required to compute the L1 fee
|
|
type StateDB interface {
|
|
GetState(common.Address, common.Hash) common.Hash
|
|
}
|
|
|
|
type gpoState struct {
|
|
l1BaseFee *big.Int
|
|
overhead *big.Int
|
|
scalar *big.Int
|
|
l1BlobBaseFee *big.Int
|
|
commitScalar *big.Int
|
|
blobScalar *big.Int
|
|
}
|
|
|
|
func EstimateL1DataFeeForMessage(msg Message, baseFee *big.Int, config *params.ChainConfig, signer types.Signer, state StateDB, blockNumber *big.Int) (*big.Int, error) {
|
|
if msg.IsL1MessageTx() {
|
|
return big.NewInt(0), nil
|
|
}
|
|
|
|
unsigned := asUnsignedTx(msg, baseFee, config.ChainID)
|
|
// with v=1
|
|
tx, err := unsigned.WithSignature(signer, append(bytes.Repeat([]byte{0xff}, crypto.SignatureLength-1), 0x01))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
raw, err := tx.MarshalBinary()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gpoState := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state)
|
|
|
|
var l1DataFee *big.Int
|
|
|
|
if !config.IsCurie(blockNumber) {
|
|
l1DataFee = calculateEncodedL1DataFee(raw, gpoState.overhead, gpoState.l1BaseFee, gpoState.scalar)
|
|
} else {
|
|
l1DataFee = calculateEncodedL1DataFeeCurie(raw, gpoState.l1BaseFee, gpoState.l1BlobBaseFee, gpoState.commitScalar, gpoState.blobScalar)
|
|
}
|
|
|
|
return l1DataFee, nil
|
|
}
|
|
|
|
// asUnsignedTx turns a Message into a types.Transaction
|
|
func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction {
|
|
if baseFee == nil {
|
|
if msg.AccessList() == nil {
|
|
return asUnsignedLegacyTx(msg)
|
|
}
|
|
|
|
return asUnsignedAccessListTx(msg, chainID)
|
|
}
|
|
|
|
if msg.SetCodeAuthorizations() == nil {
|
|
return asUnsignedDynamicTx(msg, chainID)
|
|
}
|
|
|
|
return asUnsignedSetCodeTx(msg, chainID)
|
|
}
|
|
|
|
func asUnsignedLegacyTx(msg Message) *types.Transaction {
|
|
return types.NewTx(&types.LegacyTx{
|
|
Nonce: msg.Nonce(),
|
|
To: msg.To(),
|
|
Value: msg.Value(),
|
|
Gas: msg.Gas(),
|
|
GasPrice: msg.GasPrice(),
|
|
Data: msg.Data(),
|
|
})
|
|
}
|
|
|
|
func asUnsignedAccessListTx(msg Message, chainID *big.Int) *types.Transaction {
|
|
return types.NewTx(&types.AccessListTx{
|
|
Nonce: msg.Nonce(),
|
|
To: msg.To(),
|
|
Value: msg.Value(),
|
|
Gas: msg.Gas(),
|
|
GasPrice: msg.GasPrice(),
|
|
Data: msg.Data(),
|
|
AccessList: msg.AccessList(),
|
|
ChainID: chainID,
|
|
})
|
|
}
|
|
|
|
func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction {
|
|
return types.NewTx(&types.DynamicFeeTx{
|
|
Nonce: msg.Nonce(),
|
|
To: msg.To(),
|
|
Value: msg.Value(),
|
|
Gas: msg.Gas(),
|
|
GasFeeCap: msg.GasFeeCap(),
|
|
GasTipCap: msg.GasTipCap(),
|
|
Data: msg.Data(),
|
|
AccessList: msg.AccessList(),
|
|
ChainID: chainID,
|
|
})
|
|
}
|
|
|
|
func asUnsignedSetCodeTx(msg Message, chainID *big.Int) *types.Transaction {
|
|
tx := types.SetCodeTx{
|
|
Nonce: msg.Nonce(),
|
|
Value: uint256.MustFromBig(msg.Value()),
|
|
Gas: msg.Gas(),
|
|
GasFeeCap: uint256.MustFromBig(msg.GasFeeCap()),
|
|
GasTipCap: uint256.MustFromBig(msg.GasTipCap()),
|
|
Data: msg.Data(),
|
|
AccessList: msg.AccessList(),
|
|
AuthList: msg.SetCodeAuthorizations(),
|
|
ChainID: uint256.MustFromBig(chainID),
|
|
}
|
|
if msg.To() != nil {
|
|
tx.To = *msg.To()
|
|
}
|
|
return types.NewTx(&tx)
|
|
}
|
|
|
|
func readGPOStorageSlots(addr common.Address, state StateDB) gpoState {
|
|
var gpoState gpoState
|
|
gpoState.l1BaseFee = state.GetState(addr, rcfg.L1BaseFeeSlot).Big()
|
|
gpoState.overhead = state.GetState(addr, rcfg.OverheadSlot).Big()
|
|
gpoState.scalar = state.GetState(addr, rcfg.ScalarSlot).Big()
|
|
gpoState.l1BlobBaseFee = state.GetState(addr, rcfg.L1BlobBaseFeeSlot).Big()
|
|
gpoState.commitScalar = state.GetState(addr, rcfg.CommitScalarSlot).Big()
|
|
gpoState.blobScalar = state.GetState(addr, rcfg.BlobScalarSlot).Big()
|
|
return gpoState
|
|
}
|
|
|
|
// calculateEncodedL1DataFee computes the L1 fee for an RLP-encoded tx
|
|
func calculateEncodedL1DataFee(data []byte, overhead, l1BaseFee *big.Int, scalar *big.Int) *big.Int {
|
|
l1GasUsed := calculateL1GasUsed(data, overhead)
|
|
l1DataFee := new(big.Int).Mul(l1GasUsed, l1BaseFee)
|
|
return mulAndScale(l1DataFee, scalar, rcfg.Precision)
|
|
}
|
|
|
|
// calculateEncodedL1DataFeeCurie computes the L1 fee for an RLP-encoded tx, post Curie
|
|
func calculateEncodedL1DataFeeCurie(data []byte, l1BaseFee *big.Int, l1BlobBaseFee *big.Int, commitScalar *big.Int, blobScalar *big.Int) *big.Int {
|
|
// calldata component of commit fees (calldata gas + execution)
|
|
calldataGas := new(big.Int).Mul(commitScalar, l1BaseFee)
|
|
|
|
// blob component of commit fees
|
|
blobGas := big.NewInt(int64(len(data)))
|
|
blobGas = new(big.Int).Mul(blobGas, l1BlobBaseFee)
|
|
blobGas = new(big.Int).Mul(blobGas, blobScalar)
|
|
|
|
// combined
|
|
l1DataFee := new(big.Int).Add(calldataGas, blobGas)
|
|
l1DataFee = new(big.Int).Quo(l1DataFee, rcfg.Precision)
|
|
|
|
return l1DataFee
|
|
}
|
|
|
|
// 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 + txExtraDataBytes) * 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)
|
|
}
|
|
|
|
func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.ChainConfig, blockNumber *big.Int) (*big.Int, error) {
|
|
if tx.IsL1MessageTx() {
|
|
return big.NewInt(0), nil
|
|
}
|
|
|
|
raw, err := tx.MarshalBinary()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gpoState := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state)
|
|
|
|
var l1DataFee *big.Int
|
|
|
|
if !config.IsCurie(blockNumber) {
|
|
l1DataFee = calculateEncodedL1DataFee(raw, gpoState.overhead, gpoState.l1BaseFee, gpoState.scalar)
|
|
} else {
|
|
l1DataFee = calculateEncodedL1DataFeeCurie(raw, gpoState.l1BaseFee, gpoState.l1BlobBaseFee, gpoState.commitScalar, gpoState.blobScalar)
|
|
}
|
|
|
|
// ensure l1DataFee fits into uint64 for circuit compatibility
|
|
// (note: in practice this value should never be this big)
|
|
if l1DataFee.Cmp(l1DataFeeCap) > 0 {
|
|
l1DataFee = new(big.Int).Set(l1DataFeeCap)
|
|
}
|
|
|
|
return l1DataFee, nil
|
|
}
|
|
|
|
func GetL1BaseFee(state StateDB) *big.Int {
|
|
return state.GetState(rcfg.L1GasPriceOracleAddress, rcfg.L1BaseFeeSlot).Big()
|
|
}
|