diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index ab03e481f5..81e427d631 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -1,145 +1,145 @@ package fees import ( - "bytes" - "errors" - "fmt" - "math/big" + "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/crypto" - "github.com/scroll-tech/go-ethereum/params" - "github.com/scroll-tech/go-ethereum/rollup/rcfg" + "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) + // 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) ) // 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 + 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 } // 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 + GetState(common.Address, common.Hash) common.Hash + GetBalance(addr common.Address) *big.Int } func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) { - if msg.IsL1MessageTx() { - return big.NewInt(0), nil - } + if msg.IsL1MessageTx() { + return big.NewInt(0), nil + } - unsigned := asUnsignedTx(msg, baseFee, chainID) - // with v=1 - tx, err := unsigned.WithSignature(signer, append(bytes.Repeat([]byte{0xff}, crypto.SignatureLength-1), 0x01)) - if err != nil { - return nil, err - } + unsigned := asUnsignedTx(msg, baseFee, 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 := rlpEncode(tx) - if err != nil { - return nil, err - } + raw, err := rlpEncode(tx) + if err != nil { + return nil, err + } - l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) - return l1DataFee, nil + l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) + l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + 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) - } + if baseFee == nil { + if msg.AccessList() == nil { + return asUnsignedLegacyTx(msg) + } - return asUnsignedAccessListTx(msg, chainID) - } + return asUnsignedAccessListTx(msg, chainID) + } - return asUnsignedDynamicTx(msg, chainID) + return asUnsignedDynamicTx(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(), - }) + 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, - }) + 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, - }) + 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, + }) } // rlpEncode RLP encodes the transaction into bytes func rlpEncode(tx *types.Transaction) ([]byte, error) { - raw := new(bytes.Buffer) - if err := tx.EncodeRLP(raw); err != nil { - return nil, err - } + raw := new(bytes.Buffer) + if err := tx.EncodeRLP(raw); err != nil { + return nil, err + } - return raw.Bytes(), nil + return raw.Bytes(), 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() + 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() } // calculateEncodedL1DataFee computes the L1 fee for an RLP-encoded tx func calculateEncodedL1DataFee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) *big.Int { - l1GasUsed := CalculateL1GasUsed(data, overhead) - l1DataFee := new(big.Int).Mul(l1GasUsed, l1GasPrice) - return mulAndScale(l1DataFee, scalar, rcfg.Precision) + l1GasUsed := CalculateL1GasUsed(data, overhead) + l1DataFee := new(big.Int).Mul(l1GasUsed, l1GasPrice) + return mulAndScale(l1DataFee, scalar, rcfg.Precision) } // CalculateL1GasUsed computes the L1 gas used based on the calldata and @@ -147,79 +147,79 @@ func calculateEncodedL1DataFee(data []byte, overhead, l1GasPrice *big.Int, scala // 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) + 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 + 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) + z := new(big.Int).Mul(x, y) + return new(big.Int).Quo(z, precision) } func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error) { - if tx.IsL1MessageTx() { - return big.NewInt(0), nil - } + if tx.IsL1MessageTx() { + return big.NewInt(0), nil + } - raw, err := rlpEncode(tx) - if err != nil { - return nil, err - } + raw, err := rlpEncode(tx) + if err != nil { + return nil, err + } - l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) - return l1DataFee, nil + l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) + l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + return l1DataFee, nil } func calculateL2Fee(tx *types.Transaction) *big.Int { - l2GasLimit := new(big.Int).SetUint64(tx.Gas()) - return new(big.Int).Mul(tx.GasPrice(), l2GasLimit) + l2GasLimit := new(big.Int).SetUint64(tx.Gas()) + return new(big.Int).Mul(tx.GasPrice(), l2GasLimit) } 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") - } + from, err := types.Sender(signer, tx) + if err != nil { + return errors.New("invalid transaction: invalid sender") + } - balance := state.GetBalance(from) - l2Fee := calculateL2Fee(tx) - l1DataFee, err := CalculateL1DataFee(tx, state) - if err != nil { - return fmt.Errorf("invalid transaction: %w", err) - } + balance := state.GetBalance(from) + l2Fee := calculateL2Fee(tx) + l1DataFee, err := CalculateL1DataFee(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 := 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, l1DataFee) - if balance.Cmp(cost) < 0 { - return errors.New("invalid transaction: insufficient funds for l1fee + gas * price + value") - } + cost = cost.Add(cost, l1DataFee) + 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 + // TODO: check GasPrice is in an expected range - return nil + return nil } diff --git a/rollup/fees/rollup_fee_test.go b/rollup/fees/rollup_fee_test.go index 3e8b65045b..54b77eeb15 100644 --- a/rollup/fees/rollup_fee_test.go +++ b/rollup/fees/rollup_fee_test.go @@ -1,20 +1,20 @@ package fees import ( - "math/big" - "testing" + "math/big" + "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestCalculateEncodedL1DataFee(t *testing.T) { - l1BaseFee := new(big.Int).SetUint64(15000000) + l1BaseFee := new(big.Int).SetUint64(15000000) - data := []byte{0, 10, 1, 0} - overhead := new(big.Int).SetUint64(100) - scalar := new(big.Int).SetUint64(10) + data := []byte{0, 10, 1, 0} + overhead := new(big.Int).SetUint64(100) + scalar := new(big.Int).SetUint64(10) - expected := new(big.Int).SetUint64(184) // 184.2 - actual := calculateEncodedL1DataFee(data, overhead, l1BaseFee, scalar) - assert.Equal(t, expected, actual) + expected := new(big.Int).SetUint64(184) // 184.2 + actual := calculateEncodedL1DataFee(data, overhead, l1BaseFee, scalar) + assert.Equal(t, expected, actual) } diff --git a/rollup/rcfg/config.go b/rollup/rcfg/config.go index 54822047ef..3e0e00ce04 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -1,32 +1,32 @@ package rcfg import ( - "math/big" + "math/big" - "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/common" ) // TODO: // verify in consensus layer when decentralizing sequencer var ( - // L2MessageQueueAddress is the address of the L2MessageQueue - // predeploy - // see contracts/src/L2/predeploys/L2MessageQueue.sol - L2MessageQueueAddress = common.HexToAddress("0x5300000000000000000000000000000000000000") - WithdrawTrieRootSlot = common.BigToHash(big.NewInt(0)) + // L2MessageQueueAddress is the address of the L2MessageQueue + // predeploy + // see contracts/src/L2/predeploys/L2MessageQueue.sol + L2MessageQueueAddress = common.HexToAddress("0x5300000000000000000000000000000000000000") + WithdrawTrieRootSlot = common.BigToHash(big.NewInt(0)) - // ScrollFeeVaultAddress is the address of the L2TxFeeVault - // predeploy - // see scroll-tech/scroll/contracts/src/L2/predeploys/L2TxFeeVault.sol - ScrollFeeVaultAddress = common.HexToAddress("0x5300000000000000000000000000000000000005") + // ScrollFeeVaultAddress is the address of the L2TxFeeVault + // predeploy + // see scroll-tech/scroll/contracts/src/L2/predeploys/L2TxFeeVault.sol + ScrollFeeVaultAddress = common.HexToAddress("0x5300000000000000000000000000000000000005") - // L1GasPriceOracleAddress is the address of the L1GasPriceOracle - // predeploy - // see scroll-tech/scroll/contracts/src/L2/predeploys/L1GasPriceOracle.sol - L1GasPriceOracleAddress = common.HexToAddress("0x5300000000000000000000000000000000000002") - Precision = new(big.Int).SetUint64(1e9) - L1BaseFeeSlot = common.BigToHash(big.NewInt(1)) - OverheadSlot = common.BigToHash(big.NewInt(2)) - ScalarSlot = common.BigToHash(big.NewInt(3)) + // L1GasPriceOracleAddress is the address of the L1GasPriceOracle + // predeploy + // see scroll-tech/scroll/contracts/src/L2/predeploys/L1GasPriceOracle.sol + L1GasPriceOracleAddress = common.HexToAddress("0x5300000000000000000000000000000000000002") + Precision = new(big.Int).SetUint64(1e9) + L1BaseFeeSlot = common.BigToHash(big.NewInt(1)) + OverheadSlot = common.BigToHash(big.NewInt(2)) + ScalarSlot = common.BigToHash(big.NewInt(3)) ) diff --git a/rollup/withdrawtrie/withdraw_trie.go b/rollup/withdrawtrie/withdraw_trie.go index 22e51139a4..85f0347309 100644 --- a/rollup/withdrawtrie/withdraw_trie.go +++ b/rollup/withdrawtrie/withdraw_trie.go @@ -1,18 +1,18 @@ package withdrawtrie import ( - "github.com/scroll-tech/go-ethereum/common" - "github.com/scroll-tech/go-ethereum/rollup/rcfg" + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" ) // StateDB represents the StateDB interface // required to get withdraw trie root type StateDB interface { - GetState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash } // ReadWTRSlot reads WithdrawTrieRoot slot in L2MessageQueue predeploy, i.e., `messageRoot` // in contracts/src/libraries/common/AppendOnlyMerkleTree.sol func ReadWTRSlot(addr common.Address, state StateDB) common.Hash { - return state.GetState(addr, rcfg.WithdrawTrieRootSlot) + return state.GetState(addr, rcfg.WithdrawTrieRootSlot) }