diff --git a/core/state_transition.go b/core/state_transition.go index 1987e17cd0..0ffebe2c0e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -27,6 +27,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto/codehash" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) var emptyKeccakCodeHash = codehash.EmptyKeccakCodeHash @@ -42,8 +43,10 @@ The state transitioning model does all the necessary work to work out a valid ne 3) Create a new state object if the recipient is \0*32 4) Value transfer == If contract creation == - 4a) Attempt to run transaction data - 4b) If valid, use result as code for the new state object + + 4a) Attempt to run transaction data + 4b) If valid, use result as code for the new state object + == end == 5) Run Script section 6) Derive new state root @@ -60,6 +63,9 @@ type StateTransition struct { data []byte state vm.StateDB evm *vm.EVM + + // l1 rollup fee + l1Fee *big.Int } // Message represents a message sent to a contract. @@ -157,6 +163,11 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b // NewStateTransition initialises and returns a new state transition object. func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { + l1Fee := new(big.Int) + if evm.ChainConfig().UsingScroll { + l1Fee, _ = fees.CalculateL1MsgFee(msg, evm.StateDB) + } + return &StateTransition{ gp: gp, evm: evm, @@ -167,6 +178,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition value: msg.Value(), data: msg.Data(), state: evm.StateDB, + l1Fee: l1Fee, } } @@ -192,6 +204,12 @@ 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.evm.ChainConfig().UsingScroll { + // always add l1fee, because all tx are L2-to-L1 ATM + mgval = mgval.Add(mgval, st.l1Fee) + } + balanceCheck := mgval if st.gasFeeCap != nil { balanceCheck = new(big.Int).SetUint64(st.msg.Gas()) @@ -262,13 +280,13 @@ func (st *StateTransition) preCheck() error { // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // -// - used gas: -// total gas used (including gas being refunded) -// - returndata: -// the returned data from evm -// - concrete execution error: -// various **EVM** error which aborts the execution, -// e.g. ErrOutOfGas, ErrExecutionReverted +// - used gas: +// total gas used (including gas being refunded) +// - returndata: +// the returned data from evm +// - concrete execution error: +// various **EVM** error which aborts the execution, +// e.g. ErrOutOfGas, ErrExecutionReverted // // However if any consensus issue encountered, return the error directly with // nil evm execution result. @@ -336,7 +354,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if london { effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee)) } - st.state.AddBalance(st.evm.FeeRecipient(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) + + if st.evm.ChainConfig().UsingScroll { + // The L2 Fee is the same as the fee that is charged in the normal geth + // codepath. Add the L1 fee to the L2 fee for the total fee that is sent + // to the sequencer. + l2Fee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip) + fee := new(big.Int).Add(st.l1Fee, l2Fee) + st.state.AddBalance(st.evm.FeeRecipient(), fee) + } else { + st.state.AddBalance(st.evm.FeeRecipient(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) + } return &ExecutionResult{ UsedGas: st.gasUsed(), diff --git a/core/types/l2trace.go b/core/types/l2trace.go index 727352ad64..35f3f39e0d 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -52,6 +52,7 @@ type StorageTrace struct { // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value type ExecutionResult struct { + L1Fee uint64 `json:"l1Fee,omitempty"` Gas uint64 `json:"gas"` Failed bool `json:"failed"` ReturnValue string `json:"returnValue"` diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 19c4f18406..0e12074e16 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/big" "runtime" "sync" @@ -14,6 +15,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rollup/withdrawtrie" "github.com/scroll-tech/go-ethereum/rpc" @@ -328,11 +330,17 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc } } + l1Fee := big.NewInt(0) + if vmenv.ChainConfig().UsingScroll { + l1Fee, _ = fees.CalculateL1MsgFee(msg, vmenv.StateDB) + } + env.executionResults[index] = &types.ExecutionResult{ From: sender, To: receiver, AccountCreated: createdAcc, AccountsAfter: after, + L1Fee: l1Fee.Uint64(), Gas: result.UsedGas, Failed: result.Failed(), ReturnValue: fmt.Sprintf("%x", returnVal), diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go new file mode 100644 index 0000000000..7aa6829b4a --- /dev/null +++ b/rollup/fees/rollup_fee.go @@ -0,0 +1,153 @@ +package fees + +import ( + "bytes" + "errors" + "math" + "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 +} + +// 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.Float) { + l1BaseFee := state.GetState(addr, rcfg.L1BaseFeeSlot) + overhead := state.GetState(addr, rcfg.OverheadSlot) + scalar := state.GetState(addr, rcfg.ScalarSlot) + scaled := ScalePrecision(scalar.Big(), rcfg.Precision) + return l1BaseFee.Big(), overhead.Big(), scaled +} + +// ScalePrecision will scale a value by precision +func ScalePrecision(scalar, precision *big.Int) *big.Float { + fscalar := new(big.Float).SetInt(scalar) + fdivisor := new(big.Float).SetInt(precision) + // fscalar / fdivisor + return new(big.Float).Quo(fscalar, fdivisor) +} + +// CalculateL1Fee computes the L1 fee +func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Float) *big.Int { + l1GasUsed := CalculateL1GasUsed(data, overhead) + l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice) + return mulByFloat(l1Fee, scalar) +} + +// 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 +} + +// mulByFloat multiplies a big.Int by a float and returns the +// big.Int rounded upwards +func mulByFloat(num *big.Int, float *big.Float) *big.Int { + n := new(big.Float).SetUint64(num.Uint64()) + product := n.Mul(n, float) + pfloat, _ := product.Float64() + rounded := math.Ceil(pfloat) + return new(big.Int).SetUint64(uint64(rounded)) +} diff --git a/rollup/rcfg/config.go b/rollup/rcfg/config.go index 74ed809efc..3d2bd54e67 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -15,4 +15,13 @@ var ( // see contracts/src/L2/predeploys/L2MessageQueue.sol L2MessageQueueAddress = common.HexToAddress("0x5300000000000000000000000000000000000000") WithdrawTrieRootSlot = common.BigToHash(big.NewInt(0)) + + // 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)) )