diff --git a/core/state_transition.go b/core/state_transition.go index 9fcdb74458..72c174165f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -139,6 +139,7 @@ type Message struct { AccessList types.AccessList BlobGasFeeCap *big.Int BlobHashes []common.Hash + Delegate bool // When SkipAccountChecks is true, the message nonce is not checked against the // account nonce in state. It also disables checking that the sender is an EOA. @@ -161,6 +162,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In SkipAccountChecks: false, BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), + Delegate: tx.Delegate(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -416,7 +418,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } else { // Increment the nonce for the next transaction st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) - ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value) + if !msg.Delegate { + ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value) + } else { + var contract = vm.NewContract(sender, sender, nil, st.gasRemaining) + ret, st.gasRemaining, vmerr = st.evm.DelegateCall(contract, st.to(), msg.Data, st.gasRemaining) + } } if !rules.IsLondon { diff --git a/core/txpool/validation.go b/core/txpool/validation.go index af678277f8..32f8009464 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -66,6 +66,9 @@ func ValidateTransaction(tx *types.Transaction, blobs []kzg4844.Blob, commits [] if !opts.Config.IsCancun(head.Number, head.Time) && tx.Type() == types.BlobTxType { return fmt.Errorf("%w: type %d rejected, pool not yet in Cancun", core.ErrTxTypeNotSupported, tx.Type()) } + if !opts.Config.IsPrague(head.Number, head.Time) && tx.Type() == types.DelegateTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type()) + } // Check whether the init code size has been exceeded if opts.Config.IsShanghai(head.Number, head.Time) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) diff --git a/core/types/receipt.go b/core/types/receipt.go index 6b4b897245..7ff63386c1 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -194,7 +194,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errShortTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType, BlobTxType: + case DynamicFeeTxType, AccessListTxType, BlobTxType, DelegateTxType: var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -302,7 +302,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { } w.WriteByte(r.Type) switch r.Type { - case AccessListTxType, DynamicFeeTxType, BlobTxType: + case AccessListTxType, DynamicFeeTxType, BlobTxType, DelegateTxType: rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for diff --git a/core/types/transaction.go b/core/types/transaction.go index 579fc00d10..d010d918ef 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -46,6 +46,7 @@ const ( AccessListTxType = 0x01 DynamicFeeTxType = 0x02 BlobTxType = 0x03 + DelegateTxType = 0x04 ) // Transaction is an Ethereum transaction. @@ -68,7 +69,7 @@ func NewTx(inner TxData) *Transaction { // TxData is the underlying data of a transaction. // -// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx. +// This is implemented by DynamicFeeTx, LegacyTx, AccessListTx and DelegateTx. type TxData interface { txType() byte // returns the type ID copy() TxData // creates a deep copy and initializes all fields @@ -200,6 +201,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { var inner BlobTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err + case DelegateTxType: + var inner DelegateTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported } @@ -264,6 +269,11 @@ func (tx *Transaction) Type() uint8 { return tx.inner.txType() } +// Delegate returns wether the transaction type corresponds to a delegate operation or not. (see EIP-5806) +func (tx *Transaction) Delegate() bool { + return tx.inner.txType() == DelegateTxType +} + // ChainId returns the EIP155 chain ID of the transaction. The return value will always be // non-nil. For legacy transactions which are not replay-protected, the return value is // zero. diff --git a/core/types/tx_delegate.go b/core/types/tx_delegate.go new file mode 100644 index 0000000000..079c710334 --- /dev/null +++ b/core/types/tx_delegate.go @@ -0,0 +1,113 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// DelegateTx represents an EIP-5806 transaction. +type DelegateTx struct { + ChainID *big.Int + Nonce uint64 + GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *big.Int // a.k.a. maxFeePerGas + Gas uint64 + To *common.Address `rlp:"nil"` // nil means contract creation + Data []byte + AccessList AccessList + + // Signature values + V *big.Int `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *DelegateTx) copy() TxData { + cpy := &DelegateTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + ChainID: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *DelegateTx) txType() byte { return DelegateTxType } +func (tx *DelegateTx) chainID() *big.Int { return tx.ChainID } +func (tx *DelegateTx) accessList() AccessList { return tx.AccessList } +func (tx *DelegateTx) data() []byte { return tx.Data } +func (tx *DelegateTx) gas() uint64 { return tx.Gas } +func (tx *DelegateTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *DelegateTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *DelegateTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *DelegateTx) value() *big.Int { return big.NewInt(0) } +func (tx *DelegateTx) nonce() uint64 { return tx.Nonce } +func (tx *DelegateTx) to() *common.Address { return tx.To } +func (tx *DelegateTx) blobGas() uint64 { return 0 } +func (tx *DelegateTx) blobGasFeeCap() *big.Int { return nil } +func (tx *DelegateTx) blobHashes() []common.Hash { return nil } + +func (tx *DelegateTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap) + } + tip := dst.Sub(tx.GasFeeCap, baseFee) + if tip.Cmp(tx.GasTipCap) > 0 { + tip.Set(tx.GasTipCap) + } + return tip.Add(tip, baseFee) +} + +func (tx *DelegateTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *DelegateTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +}