diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index 0f39df0753a9..e4b2dc43e9af 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -134,7 +134,7 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0)
- gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
+ gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, tx.SenderAuthorization() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
r.Error = err
results = append(results, r)
diff --git a/core/bench_test.go b/core/bench_test.go
index 932188f8e203..6e7b6d779ae9 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes)
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
- gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
+ gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go
index 7704ba41fb66..9d5d5e43e524 100644
--- a/core/bintrie_witness_test.go
+++ b/core/bintrie_witness_test.go
@@ -63,12 +63,12 @@ var (
func TestProcessVerkle(t *testing.T) {
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
- intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true)
+ intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
- intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
+ intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
signer = types.LatestSigner(testVerkleChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
diff --git a/core/evm.go b/core/evm.go
index 18d940fdd228..594d8d26d2bb 100644
--- a/core/evm.go
+++ b/core/evm.go
@@ -25,6 +25,8 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
@@ -82,13 +84,55 @@ func NewEVMTxContext(msg *Message) vm.TxContext {
Origin: msg.From,
GasPrice: new(big.Int).Set(msg.GasPrice),
BlobHashes: msg.BlobHashes,
+
+ // EIP-7701 fields
+ TxType: msg.TxType,
+ Nonce: msg.Nonce,
+ Sender: msg.Sender,
+ Deployer: msg.Deployer,
+ Paymaster: msg.Paymaster,
+ SenderExecutionData: msg.Data,
+ SenderExecutionGas: msg.GasLimit,
}
if msg.BlobGasFeeCap != nil {
ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap)
}
+ if msg.GasTipCap != nil {
+ ctx.MaxPriorityFeePerGas = new(big.Int).Set(msg.GasTipCap)
+ }
+ if msg.GasFeeCap != nil {
+ ctx.MaxFeePerGas = new(big.Int).Set(msg.GasFeeCap)
+ }
+
+ // Compute AccessListHash
+ if len(msg.AccessList) > 0 {
+ ctx.AccessListHash = rlpHash(msg.AccessList)
+ }
+
+ // Compute AuthorizationListHash
+ if len(msg.SetCodeAuthorizations) > 0 {
+ ctx.AuthorizationListHash = rlpHash(msg.SetCodeAuthorizations)
+ }
+
+ // TxHashForSignature is nil for AA transactions
+ // For non-AA transactions, we leave it nil as we don't have access to the
+ // original transaction here. If needed, this should be set by the caller.
+ if !msg.Abstract {
+ // TODO: compute TxHashForSignature for non-AA transactions if needed
+ }
+
return ctx
}
+// rlpHash computes the keccak256 hash of the RLP encoding of x.
+func rlpHash(x interface{}) common.Hash {
+ encoded, err := rlp.EncodeToBytes(x)
+ if err != nil {
+ return common.Hash{}
+ }
+ return crypto.Keccak256Hash(encoded)
+}
+
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
// Cache will initially contain [refHash.parent],
diff --git a/core/state_transition.go b/core/state_transition.go
index bf5ac07636d5..12eec70a8b7f 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/core/vm/roles"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@@ -68,11 +69,13 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
-func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool, isAbstract bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
gas = params.TxGasContractCreation
+ } else if isAbstract {
+ gas = params.TxGasAbstract
} else {
gas = params.TxGas
}
@@ -143,6 +146,7 @@ func toWordSize(size uint64) uint64 {
// A Message contains the data derived from a single transaction that is relevant to state
// processing.
type Message struct {
+ TxType uint8
To *common.Address
From common.Address
Nonce uint64
@@ -157,6 +161,15 @@ type Message struct {
BlobHashes []common.Hash
SetCodeAuthorizations []types.SetCodeAuthorization
+ Abstract bool
+ Sender *types.AbstractAuthorization
+ Deployer *types.AbstractAuthorization
+ Paymaster *types.PaymasterAuthorization
+ Payer common.Address
+
+ AllData []byte
+ TotalGasLimit uint64
+
// When SkipNonceChecks is true, the message nonce is not checked against the
// account nonce in state.
//
@@ -175,6 +188,7 @@ type Message struct {
// TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
msg := &Message{
+ TxType: tx.Type(),
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
GasPrice: new(big.Int).Set(tx.GasPrice()),
@@ -189,6 +203,14 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipTransactionChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
+
+ Abstract: tx.SenderAuthorization() != nil,
+ Sender: tx.SenderAuthorization(),
+ Deployer: tx.DeployerAuthorization(),
+ Paymaster: tx.PaymasterAuthorization(),
+
+ AllData: common.CopyBytes(tx.Data()),
+ TotalGasLimit: tx.Gas(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
@@ -199,6 +221,27 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
}
var err error
msg.From, err = types.Sender(s, tx)
+
+ msg.Payer = msg.From
+ if msg.Abstract {
+ msg.Payer = msg.Paymaster.Target
+ msg.SkipNonceChecks = true
+ }
+
+ // Sum all gas limits if AA.
+ if msg.Abstract {
+ msg.AllData = append(msg.AllData, msg.Sender.Data...)
+ msg.TotalGasLimit += msg.Sender.Gas
+ if msg.Deployer != nil {
+ msg.AllData = append(msg.AllData, msg.Deployer.Data...)
+ msg.TotalGasLimit += msg.Deployer.Gas
+ }
+ if msg.Paymaster != nil {
+ msg.AllData = append(msg.AllData, msg.Paymaster.Data...)
+ msg.TotalGasLimit += msg.Paymaster.Gas
+ }
+ }
+
return msg, err
}
@@ -264,11 +307,11 @@ func (st *stateTransition) to() common.Address {
}
func (st *stateTransition) buyGas() error {
- mgval := new(big.Int).SetUint64(st.msg.GasLimit)
+ mgval := new(big.Int).SetUint64(st.msg.TotalGasLimit)
mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval)
if st.msg.GasFeeCap != nil {
- balanceCheck.SetUint64(st.msg.GasLimit)
+ balanceCheck.SetUint64(st.msg.TotalGasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
}
balanceCheck.Add(balanceCheck, st.msg.Value)
@@ -289,21 +332,21 @@ func (st *stateTransition) buyGas() error {
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
- if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
- return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
+ if have, want := st.state.GetBalance(st.msg.Payer), balanceCheckU256; have.Cmp(want) < 0 {
+ return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.Payer.Hex(), have, want)
}
- if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
+ if err := st.gp.SubGas(st.msg.TotalGasLimit); err != nil {
return err
}
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
- st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
+ st.evm.Config.Tracer.OnGasChange(0, st.msg.TotalGasLimit, tracing.GasChangeTxInitialBalance)
}
- st.gasRemaining = st.msg.GasLimit
+ st.gasRemaining = st.msg.TotalGasLimit
- st.initialGas = st.msg.GasLimit
+ st.initialGas = st.msg.TotalGasLimit
mgvalU256, _ := uint256.FromBig(mgval)
- st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
+ st.state.SubBalance(st.msg.Payer, mgvalU256, tracing.BalanceDecreaseGasBuy)
return nil
}
@@ -327,8 +370,8 @@ func (st *stateTransition) preCheck() error {
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
if !msg.SkipTransactionChecks {
// Verify tx gas limit does not exceed EIP-7825 cap.
- if isOsaka && msg.GasLimit > params.MaxTxGas {
- return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
+ if isOsaka && msg.TotalGasLimit > params.MaxTxGas {
+ return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.TotalGasLimit)
}
// Make sure the sender is an EOA
code := st.state.GetCode(msg.From)
@@ -443,7 +486,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
- gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
+ gas, err := IntrinsicGas(msg.AllData, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, msg.Abstract)
if err != nil {
return nil, err
}
@@ -452,11 +495,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Gas limit suffices for the floor data cost (EIP-7623)
if rules.IsPrague {
- floorDataGas, err = FloorDataGas(msg.Data)
+ floorDataGas, err = FloorDataGas(msg.AllData)
if err != nil {
return nil, err
}
- if msg.GasLimit < floorDataGas {
+ if msg.TotalGasLimit < floorDataGas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
}
}
@@ -471,6 +514,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if targetAddr := msg.To; targetAddr != nil {
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0, !st.state.Exist(*targetAddr))
}
+ if msg.Deployer != nil {
+ target := msg.Deployer.Target
+ st.evm.AccessEvents.AddTxDestination(target, false, !st.state.Exist(target))
+ }
+ if msg.Paymaster != nil {
+ target := msg.Paymaster.Target
+ st.evm.AccessEvents.AddTxDestination(target, false, !st.state.Exist(target))
+ }
}
// Check clause 6
@@ -492,12 +543,44 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// - reset transient storage(eip 1153)
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
+ // Run the abstract validations:
+ // - account deployment, in case the user's account does not yet exist
+ // - standard validation, i.e. nonce and authorization checks
+ // - paymaster validation, when the user operation is sponsored
+ if msg.Abstract {
+ // Attempt to deploy user's smart account.
+ if msg.Deployer != nil && len(st.state.GetCode(msg.From)) != 0 {
+ remaining, err := st.evm.CallWithRole(msg.Deployer.Target, msg.Deployer.Gas, roles.SenderDeployment)
+ if err != nil {
+ return nil, fmt.Errorf("deployer failed: %v", err)
+ }
+ st.gasRemaining += remaining
+ }
+
+ // Validate abstract tx.
+ remaining, err := st.evm.CallWithRole(msg.Sender.Target, msg.Sender.Gas, roles.SenderValidation)
+ if err != nil {
+ return nil, fmt.Errorf("sender validation failed: %v", err)
+ }
+ st.gasRemaining += remaining
+
+ // Validate paymaster promise to sponsor.
+ if msg.Paymaster != nil {
+ remaining, err := st.evm.CallWithRole(msg.Paymaster.Target, msg.Paymaster.Gas, roles.PaymasterValidation)
+ if err != nil {
+ return nil, fmt.Errorf("paymaster failed: %v", err)
+ }
+ st.gasRemaining += remaining
+ }
+ }
+
var (
- ret []byte
- vmerr error // vm errors do not effect consensus and are therefore not assigned to err
+ ret []byte
+ vmerr error // vm errors do not effect consensus and are therefore not assigned to err
+ remaining uint64
)
if contractCreation {
- ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
+ ret, _, remaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
} else {
// Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
@@ -520,9 +603,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Execute the transaction's call.
- ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
+ ret, remaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
}
+ st.gasRemaining += remaining
+
// Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution.
peakGasUsed := st.gasUsed()
diff --git a/core/txpool/validation.go b/core/txpool/validation.go
index 4f985a8bd068..066a24cc2420 100644
--- a/core/txpool/validation.go
+++ b/core/txpool/validation.go
@@ -122,7 +122,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
- intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)
+ intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, tx.SenderAuthorization() == nil)
if err != nil {
return err
}
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 6af960b8c3ea..51f39eb98f99 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -50,6 +50,7 @@ const (
DynamicFeeTxType = 0x02
BlobTxType = 0x03
SetCodeTxType = 0x04
+ AbstractTxType = 0x06 // skip 0x05 since it magic for set code tx
)
// Transaction is an Ethereum transaction.
@@ -535,6 +536,30 @@ func (tx *Transaction) SetCodeAuthorities() []common.Address {
return auths
}
+func (tx *Transaction) SenderAuthorization() *AbstractAuthorization {
+ abstracttx, ok := tx.inner.(*AbstractTx)
+ if !ok {
+ return nil
+ }
+ return abstracttx.Sender.copy()
+}
+
+func (tx *Transaction) DeployerAuthorization() *AbstractAuthorization {
+ abstracttx, ok := tx.inner.(*AbstractTx)
+ if !ok {
+ return nil
+ }
+ return abstracttx.Deployer.copy()
+}
+
+func (tx *Transaction) PaymasterAuthorization() *PaymasterAuthorization {
+ abstracttx, ok := tx.inner.(*AbstractTx)
+ if !ok {
+ return nil
+ }
+ return abstracttx.Paymaster.copy()
+}
+
// SetTime sets the decoding time of a transaction. This is used by tests to set
// arbitrary times and by persistent transaction pools when loading old txs from
// disk.
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index ef8fb194d5f9..98bc7f730a3d 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -231,6 +231,9 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
if fork >= forks.Prague {
s.txtypes.set(SetCodeTxType)
}
+ if fork >= forks.Bogota {
+ s.txtypes.set(AbstractTxType)
+ }
return s
}
@@ -262,6 +265,9 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.ChainId().Cmp(s.chainID) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID)
}
+ if tt == AbstractTxType {
+ return tx.SenderAuthorization().Target, nil
+ }
// 'modern' txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V, R, S := tx.RawSignatureValues()
@@ -287,6 +293,18 @@ func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
return R, S, V, nil
}
+// NewBogotaSigner returns a signer that accepts
+// - EIP-7701 abstraction transactions
+// - EIP-7702 set code transactions
+// - EIP-4844 blob transactions
+// - EIP-1559 dynamic fee transactions
+// - EIP-2930 access list transactions,
+// - EIP-155 replay protected transactions, and
+// - legacy Homestead transactions.
+func NewBogotaSigner(chainId *big.Int) Signer {
+ return newModernSigner(chainId, forks.Bogota)
+}
+
// NewPragueSigner returns a signer that accepts
// - EIP-7702 set code transactions
// - EIP-4844 blob transactions
diff --git a/core/types/tx_abstract.go b/core/types/tx_abstract.go
new file mode 100644
index 000000000000..c19ee5dce6dd
--- /dev/null
+++ b/core/types/tx_abstract.go
@@ -0,0 +1,161 @@
+// Copyright 2026 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 (
+ "bytes"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/uint256"
+)
+
+// AbstractAuthorization is the input used to abstractly validate
+// authorizations.
+type AbstractAuthorization struct {
+ Target common.Address
+ Data []byte
+ Gas uint64
+}
+
+func (a *AbstractAuthorization) copy() *AbstractAuthorization {
+ if a == nil {
+ return nil
+ }
+ return &AbstractAuthorization{
+ Target: a.Target,
+ Data: common.CopyBytes(a.Data),
+ Gas: a.Gas,
+ }
+}
+
+// PaymasterAuthorization includes the neccessary data to validate an
+// authorization from a paymaster to sponsor the transaction.
+type PaymasterAuthorization struct {
+ Target common.Address
+ Data []byte
+ Gas uint64
+ PostOpGas uint64
+}
+
+func (a *PaymasterAuthorization) copy() *PaymasterAuthorization {
+ if a == nil {
+ return nil
+ }
+ return &PaymasterAuthorization{
+ Target: a.Target,
+ Data: common.CopyBytes(a.Data),
+ Gas: a.Gas,
+ PostOpGas: a.PostOpGas,
+ }
+}
+
+// AbstractTx implements the EIP-7701 transaction type that allows arbitrary
+// implementation of sender validation and paymaster validation.
+type AbstractTx struct {
+ ChainID *uint256.Int
+ Nonce uint64
+
+ Sender AbstractAuthorization
+ Deployer *AbstractAuthorization
+ Paymaster *PaymasterAuthorization
+
+ Data []byte // FromExecutionData
+ Gas uint64
+
+ GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas
+ GasFeeCap *uint256.Int // a.k.a. maxFeePerGas
+
+ AccessList AccessList
+ AuthList []SetCodeAuthorization
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *AbstractTx) copy() TxData {
+ cpy := &AbstractTx{
+ Nonce: tx.Nonce,
+ Sender: *tx.Sender.copy(),
+ Deployer: tx.Deployer.copy(),
+ Paymaster: tx.Paymaster.copy(),
+ Data: common.CopyBytes(tx.Data),
+ Gas: tx.Gas,
+
+ // These are copied below.
+ AccessList: make(AccessList, len(tx.AccessList)),
+ AuthList: make([]SetCodeAuthorization, len(tx.AuthList)),
+ ChainID: new(uint256.Int),
+ GasTipCap: new(uint256.Int),
+ GasFeeCap: new(uint256.Int),
+ }
+ copy(cpy.AccessList, tx.AccessList)
+ copy(cpy.AuthList, tx.AuthList)
+ 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)
+ }
+ return cpy
+}
+
+// accessors for innerTx.
+func (tx *AbstractTx) txType() byte { return AbstractTxType }
+func (tx *AbstractTx) chainID() *big.Int { return tx.ChainID.ToBig() }
+func (tx *AbstractTx) accessList() AccessList { return tx.AccessList }
+func (tx *AbstractTx) data() []byte { return tx.Data }
+func (tx *AbstractTx) gas() uint64 { return tx.Gas }
+func (tx *AbstractTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() }
+func (tx *AbstractTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() }
+func (tx *AbstractTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() }
+func (tx *AbstractTx) value() *big.Int { return big.NewInt(0) }
+func (tx *AbstractTx) nonce() uint64 { return tx.Nonce }
+func (tx *AbstractTx) to() *common.Address { return nil } // TODO: is returning nil here correct?
+
+func (tx *AbstractTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
+ if baseFee == nil {
+ return dst.Set(tx.GasFeeCap.ToBig())
+ }
+ tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee)
+ if tip.Cmp(tx.GasTipCap.ToBig()) > 0 {
+ tip.Set(tx.GasTipCap.ToBig())
+ }
+ return tip.Add(tip, baseFee)
+}
+
+func (tx *AbstractTx) rawSignatureValues() (v, r, s *big.Int) {
+ panic("abstract tx does not have signature")
+}
+
+func (tx *AbstractTx) setSignatureValues(chainID, v, r, s *big.Int) {
+ panic("abstract tx does not have signature")
+}
+
+func (tx *AbstractTx) encode(b *bytes.Buffer) error {
+ return rlp.Encode(b, tx)
+}
+
+func (tx *AbstractTx) decode(input []byte) error {
+ return rlp.DecodeBytes(input, tx)
+}
+
+func (tx *AbstractTx) sigHash(chainID *big.Int) common.Hash {
+ panic("abstract tx does not have signature")
+}
diff --git a/core/vm/contract.go b/core/vm/contract.go
index 165ca833f885..001d8f0cb05d 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -19,6 +19,7 @@ package vm
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/vm/roles"
"github.com/holiman/uint256"
)
@@ -42,6 +43,8 @@ type Contract struct {
IsDeployment bool
IsSystemCall bool
+ Role roles.Role
+
Gas uint64
value *uint256.Int
}
@@ -58,6 +61,7 @@ func NewContract(caller common.Address, address common.Address, value *uint256.I
jumpDests: jumpDests,
Gas: gas,
value: value,
+ Role: roles.SenderExecution,
}
}
diff --git a/core/vm/eips.go b/core/vm/eips.go
index dfcac4b93029..d9870df5dcfc 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/vm/roles"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@@ -40,6 +41,7 @@ var activators = map[int]func(*JumpTable){
1344: enable1344,
1153: enable1153,
4762: enable4762,
+ 7701: enable7701,
7702: enable7702,
7939: enable7939,
8024: enable8024,
@@ -579,3 +581,280 @@ func enable7702(jt *JumpTable) {
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
}
+
+// opCurrentRole pushes the current execution role to the stack.
+func opCurrentRole(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
+ role := uint256.NewInt(uint64(scope.Contract.Role))
+ scope.Stack.push(role)
+ return nil, nil
+}
+
+// opAcceptRole accepts the provided role from the stack.
+func opAcceptRole(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
+ // TODO: should this return data also? What will consume it?
+ role := scope.Stack.pop()
+ evm.accepted = roles.Role(role.Uint64())
+ return nil, errStopToken
+}
+
+type paramLoader func(*EVM, uint64) []byte
+
+// uint64ToBytes32 converts a uint64 to a 32-byte big-endian slice.
+func uint64ToBytes32(v uint64) []byte {
+ return common.LeftPadBytes(new(uint256.Int).SetUint64(v).Bytes(), 32)
+}
+
+func loadType(evm *EVM, id uint64) []byte {
+ return uint64ToBytes32(uint64(evm.TxContext.TxType))
+}
+
+func loadNonce(evm *EVM, id uint64) []byte {
+ return uint64ToBytes32(evm.TxContext.Nonce)
+}
+
+func loadSender(evm *EVM, id uint64) []byte {
+ return common.LeftPadBytes(evm.TxContext.Origin.Bytes(), 32)
+}
+
+func loadSenderValidationData(evm *EVM, id uint64) []byte {
+ if evm.TxContext.Sender == nil {
+ return nil
+ }
+ return evm.TxContext.Sender.Data
+}
+
+func loadDeployer(evm *EVM, id uint64) []byte {
+ // 0 or 32 bytes: return nil when not set
+ if evm.TxContext.Deployer == nil {
+ return nil
+ }
+ return common.LeftPadBytes(evm.TxContext.Deployer.Target.Bytes(), 32)
+}
+
+func loadDeployerData(evm *EVM, id uint64) []byte {
+ // dynamic, default empty array
+ if evm.TxContext.Deployer == nil {
+ return nil
+ }
+ return evm.TxContext.Deployer.Data
+}
+
+func loadPaymaster(evm *EVM, id uint64) []byte {
+ // 0 or 32 bytes: return nil when not set
+ if evm.TxContext.Paymaster == nil {
+ return nil
+ }
+ return common.LeftPadBytes(evm.TxContext.Paymaster.Target.Bytes(), 32)
+}
+
+func loadPaymasterData(evm *EVM, id uint64) []byte {
+ // dynamic, default empty array
+ if evm.TxContext.Paymaster == nil {
+ return nil
+ }
+ return evm.TxContext.Paymaster.Data
+}
+
+func loadSenderExecutionData(evm *EVM, id uint64) []byte {
+ return evm.TxContext.SenderExecutionData
+}
+
+func loadMaxPriorityFeePerGas(evm *EVM, id uint64) []byte {
+ if evm.TxContext.MaxPriorityFeePerGas == nil {
+ return make([]byte, 32)
+ }
+ return common.LeftPadBytes(evm.TxContext.MaxPriorityFeePerGas.Bytes(), 32)
+}
+
+func loadMaxFeePerGas(evm *EVM, id uint64) []byte {
+ if evm.TxContext.MaxFeePerGas == nil {
+ return make([]byte, 32)
+ }
+ return common.LeftPadBytes(evm.TxContext.MaxFeePerGas.Bytes(), 32)
+}
+
+func loadSenderValidationGas(evm *EVM, id uint64) []byte {
+ if evm.TxContext.Sender == nil {
+ return uint64ToBytes32(0)
+ }
+ return uint64ToBytes32(evm.TxContext.Sender.Gas)
+}
+
+func loadPaymasterValidationGas(evm *EVM, id uint64) []byte {
+ // 32 bytes, default 0
+ if evm.TxContext.Paymaster == nil {
+ return uint64ToBytes32(0)
+ }
+ return uint64ToBytes32(evm.TxContext.Paymaster.Gas)
+}
+
+func loadSenderExecutionGas(evm *EVM, id uint64) []byte {
+ return uint64ToBytes32(evm.TxContext.SenderExecutionGas)
+}
+
+func loadPaymasterPostOpGas(evm *EVM, id uint64) []byte {
+ // 32 bytes, default 0
+ if evm.TxContext.Paymaster == nil {
+ return uint64ToBytes32(0)
+ }
+ return uint64ToBytes32(evm.TxContext.Paymaster.PostOpGas)
+}
+
+func loadAccessListHash(evm *EVM, id uint64) []byte {
+ return evm.TxContext.AccessListHash[:]
+}
+
+func loadAuthorizationListHash(evm *EVM, id uint64) []byte {
+ return evm.TxContext.AuthorizationListHash[:]
+}
+
+func loadExecutionStatus(evm *EVM, id uint64) []byte {
+ return uint64ToBytes32(evm.TxContext.ExecutionStatus)
+}
+
+func loadExecutionGasUsed(evm *EVM, id uint64) []byte {
+ return uint64ToBytes32(evm.TxContext.ExecutionGasUsed)
+}
+
+func loadTxHashForSignature(evm *EVM, id uint64) []byte {
+ if evm.TxContext.TxHashForSignature == nil {
+ return nil
+ }
+ return evm.TxContext.TxHashForSignature[:]
+}
+
+// idToParam is a map of identifier to transaction parameter loader.
+var idToParam = map[uint64]paramLoader{
+ 0x00: loadType,
+ 0x01: loadNonce,
+ 0x02: loadSender,
+ 0x03: loadSenderValidationData,
+ 0x04: loadDeployer,
+ 0x05: loadDeployerData,
+ 0x06: loadPaymaster,
+ 0x07: loadPaymasterData,
+ 0x08: loadSenderExecutionData,
+ 0x0B: loadMaxPriorityFeePerGas,
+ 0x0C: loadMaxFeePerGas,
+ 0x0D: loadSenderValidationGas,
+ 0x0E: loadPaymasterValidationGas,
+ 0x0F: loadSenderExecutionGas,
+ 0x10: loadPaymasterPostOpGas,
+ 0x11: loadAccessListHash,
+ 0x12: loadAuthorizationListHash,
+ 0xf1: loadExecutionStatus,
+ 0xf2: loadExecutionGasUsed,
+ 0xff: loadTxHashForSignature,
+}
+
+// opTxParamLoad will load the 32-byte word of a transaction parameter at the
+// specified index.
+func opTxParamLoad(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
+ var (
+ stack = scope.Stack
+ idx = stack.pop()
+ id = stack.pop()
+ get paramLoader
+ ok bool
+ )
+ id64, overflow := id.Uint64WithOverflow()
+ if get, ok = idToParam[id64]; !ok || overflow {
+ return nil, ErrTxParamIdInvalid
+ }
+
+ param := get(evm, id.Uint64())
+ value := new(uint256.Int)
+ idx64, overflow := idx.Uint64WithOverflow()
+ if !overflow {
+ value.SetBytes(getData(param, idx64, 32))
+ }
+ stack.push(value)
+ return nil, nil
+}
+
+// opTxParamSize loads the specified transaction parameter then computes its
+// size and pushes the result to the stack.
+func opTxParamSize(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
+ var (
+ stack = scope.Stack
+ id = stack.peek()
+ get paramLoader
+ ok bool
+ )
+ id64, overflow := id.Uint64WithOverflow()
+ if get, ok = idToParam[id64]; !ok || overflow {
+ return nil, ErrTxParamIdInvalid
+ }
+
+ param := get(evm, id.Uint64())
+ stack.push(id.SetUint64(uint64(len(param))))
+
+ return nil, nil
+}
+
+// opTxParamCopy copies the specified transaction parameter into memory.
+func opTxParamCopy(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
+ var (
+ stack = scope.Stack
+ memOffset = scope.Stack.pop()
+ dataOffset = scope.Stack.pop()
+ length = scope.Stack.pop()
+ id = stack.pop()
+ get paramLoader
+ ok bool
+ )
+
+ id64, overflow := id.Uint64WithOverflow()
+ if get, ok = idToParam[id64]; !ok || overflow {
+ return nil, ErrTxParamIdInvalid
+ }
+ param := get(evm, id.Uint64())
+
+ dataOffset64, overflow := dataOffset.Uint64WithOverflow()
+ if overflow {
+ dataOffset64 = math.MaxUint64
+ }
+
+ // These values are checked for overflow during gas cost calculation
+ memOffset64 := memOffset.Uint64()
+ length64 := length.Uint64()
+ scope.Memory.Set(memOffset64, length64, getData(param, dataOffset64, length64))
+ return nil, nil
+}
+
+// enable7701 enables the EIP-7701 changes to support native account
+// abstraction.
+func enable7701(jt *JumpTable) {
+ jt[CURRENTROLE] = &operation{
+ execute: opCurrentRole,
+ constantGas: GasQuickStep,
+ minStack: minStack(0, 1),
+ maxStack: maxStack(0, 1),
+ }
+ jt[ACCEPTROLE] = &operation{
+ execute: opAcceptRole,
+ constantGas: GasQuickStep,
+ minStack: minStack(1, 0),
+ maxStack: maxStack(1, 0),
+ }
+ jt[TXPARAMLOAD] = &operation{
+ execute: opTxParamLoad,
+ constantGas: GasFastestStep,
+ minStack: minStack(2, 1),
+ maxStack: maxStack(2, 1),
+ }
+ jt[TXPARAMSIZE] = &operation{
+ execute: opTxParamSize,
+ constantGas: GasQuickStep,
+ minStack: minStack(1, 1),
+ maxStack: maxStack(1, 1),
+ }
+ jt[TXPARAMCOPY] = &operation{
+ execute: opTxParamCopy,
+ constantGas: GasFastestStep,
+ dynamicGas: gasCallDataCopy,
+ minStack: minStack(4, 0),
+ maxStack: maxStack(4, 0),
+ memorySize: memoryCallDataCopy,
+ }
+}
diff --git a/core/vm/errors.go b/core/vm/errors.go
index e33c9fcb853c..48526e6082eb 100644
--- a/core/vm/errors.go
+++ b/core/vm/errors.go
@@ -38,6 +38,7 @@ var (
ErrGasUintOverflow = errors.New("gas uint64 overflow")
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
+ ErrTxParamIdInvalid = errors.New("tx param id invalid")
// errStopToken is an internal token indicating interpreter loop termination,
// never returned to outside callers.
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 8975c791c842..7f6809c2d78b 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -18,6 +18,7 @@ package vm
import (
"errors"
+ "fmt"
"math/big"
"sync/atomic"
@@ -25,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm/roles"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@@ -77,6 +79,30 @@ type TxContext struct {
BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
+
+ // EIP-7701: Transaction parameters for TXPARAM* opcodes
+ TxType uint8 // 0x00: transaction type
+ Nonce uint64 // 0x01: transaction nonce
+ // Origin is sender (0x02)
+
+ // AA authorization structs (nil for non-AA txs)
+ Sender *types.AbstractAuthorization // 0x03: Data, 0x0D: Gas
+ Deployer *types.AbstractAuthorization // 0x04: Target, 0x05: Data
+ Paymaster *types.PaymasterAuthorization // 0x06: Target, 0x07: Data, 0x0E: Gas, 0x10: PostOpGas
+
+ SenderExecutionData []byte // 0x08: execution calldata
+ SenderExecutionGas uint64 // 0x0F: execution gas limit
+ MaxPriorityFeePerGas *big.Int // 0x0B: max priority fee per gas
+ MaxFeePerGas *big.Int // 0x0C: max fee per gas
+
+ // Hashes (computed when creating TxContext)
+ AccessListHash common.Hash // 0x11: hash of access list
+ AuthorizationListHash common.Hash // 0x12: hash of authorization list
+ TxHashForSignature *common.Hash // 0xff: hash for signature (nil for AA txs)
+
+ // Mutable execution state (set during AA tx processing)
+ ExecutionStatus uint64 // 0xf1: execution result status
+ ExecutionGasUsed uint64 // 0xf2: gas used during execution
}
// EVM is the Ethereum Virtual Machine base object and provides
@@ -130,6 +156,8 @@ type EVM struct {
readOnly bool // Whether to throw on stateful modifications
returnData []byte // Last CALL's return data for subsequent reuse
+
+ accepted roles.Role // Last CALL's accepted role, if any
}
// NewEVM constructs an EVM instance with the supplied block context, state
@@ -317,6 +345,86 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
return ret, gas, err
}
+// TODO: should this function take the data input?
+func (evm *EVM) CallWithRole(addr common.Address, gas uint64, role roles.Role) (leftOverGas uint64, err error) {
+ // Capture the tracer start/end events in debug mode
+ var ret []byte
+ if evm.Config.Tracer != nil {
+ evm.captureBegin(evm.depth, CALL, params.AAEntryPoint, addr, nil, gas, common.Big0)
+ defer func(startGas uint64) {
+ evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
+ }(gas)
+ }
+ // Fail if we're trying to execute above the call depth limit
+ if evm.depth > int(params.CallCreateDepth) {
+ return 0, ErrDepth
+ }
+ snapshot := evm.StateDB.Snapshot()
+ p, isPrecompile := evm.precompile(addr)
+
+ if !evm.StateDB.Exist(addr) {
+ if !isPrecompile && evm.chainRules.IsEIP4762 {
+ // Add proof of absence to witness
+ // At this point, the read costs have already been charged, either because this
+ // is a direct tx call, in which case it's covered by the intrinsic gas, or because
+ // of a CALL instruction, in which case BASIC_DATA has been added to the access
+ // list in write mode. If there is enough gas paying for the addition of the code
+ // hash leaf to the access list, then account creation will proceed unimpaired.
+ // Thus, only pay for the creation of the code hash leaf here.
+ wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false)
+ if gas < wgas {
+ evm.StateDB.RevertToSnapshot(snapshot)
+ return gas, ErrOutOfGas
+ }
+ gas -= wgas
+ }
+
+ if !isPrecompile && evm.chainRules.IsEIP158 {
+ // Calling a non-existing account, don't do anything.
+ return 0, nil
+ }
+ evm.StateDB.CreateAccount(addr)
+ }
+
+ if isPrecompile {
+ ret, gas, err = RunPrecompiledContract(p, nil, gas, evm.Config.Tracer)
+ } else {
+ // Initialise a new contract and set the code that is to be used by the EVM.
+ code := evm.resolveCode(addr)
+ if len(code) == 0 {
+ ret, err = nil, nil // gas is unchanged
+ } else {
+ // The contract is a scoped environment for this execution context only.
+ contract := NewContract(params.AAEntryPoint, addr, common.U2560, gas, evm.jumpDests)
+ contract.IsSystemCall = false
+ contract.Role = role
+ contract.SetCallCode(evm.resolveCodeHash(addr), code)
+ ret, err = evm.Run(contract, nil, false)
+ gas = contract.Gas
+ }
+ }
+ // When an error was returned by the EVM or when setting the creation code
+ // above we revert to the snapshot and consume any gas remaining. Additionally,
+ // when we're in homestead this also counts for code storage gas errors.
+ if err != nil {
+ evm.StateDB.RevertToSnapshot(snapshot)
+ if err != ErrExecutionReverted {
+ if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
+ evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
+ }
+
+ gas = 0
+ }
+ // TODO: consider clearing up unused snapshots:
+ //} else {
+ // evm.StateDB.DiscardSnapshot(snapshot)
+ }
+ if evm.accepted != role {
+ return gas, fmt.Errorf("role %s not accepted, got %s", role, evm.accepted)
+ }
+ return gas, nil
+}
+
// CallCode executes the contract associated with the addr with the given input
// as parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
@@ -373,7 +481,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
//
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
-func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
+func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int, role roles.Role) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
// DELEGATECALL inherits value from parent call
@@ -397,6 +505,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
// Note: The value refers to the original value from the parent call.
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
+ contract.Role = role
ret, err = evm.Run(contract, input, false)
gas = contract.Gas
}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 6b04a2daff7e..db96b4fc7286 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -812,7 +812,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
- ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value)
+ ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value, scope.Contract.Role)
if err != nil {
temp.Clear()
} else {
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index 9a32126a80b7..0b57cbce5641 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -220,6 +220,15 @@ const (
DATACOPY OpCode = 0xd3
)
+// EIP-7701 operations.
+const (
+ TXPARAMLOAD OpCode = 0xdb
+ TXPARAMSIZE OpCode = 0xdc
+ TXPARAMCOPY OpCode = 0xdd
+ CURRENTROLE OpCode = 0xde
+ ACCEPTROLE OpCode = 0xdf
+)
+
// 0xe0 range - eof operations.
const (
RJUMP OpCode = 0xe0
@@ -422,6 +431,10 @@ var opCodeToString = [256]string{
DATASIZE: "DATASIZE",
DATACOPY: "DATACOPY",
+ // EIP-7701 ops.
+ CURRENTROLE: "CURRENTROLE",
+ ACCEPTROLE: "ACCEPTROLE",
+
// 0xe0 range.
RJUMP: "RJUMP",
RJUMPI: "RJUMPI",
@@ -608,6 +621,8 @@ var stringToOp = map[string]OpCode{
"DATALOADN": DATALOADN,
"DATASIZE": DATASIZE,
"DATACOPY": DATACOPY,
+ "CURRENTROLE": CURRENTROLE,
+ "ACCEPTROLE": ACCEPTROLE,
"RJUMP": RJUMP,
"RJUMPI": RJUMPI,
"RJUMPV": RJUMPV,
diff --git a/core/vm/roles/roles.go b/core/vm/roles/roles.go
new file mode 100644
index 000000000000..62dc5b259bed
--- /dev/null
+++ b/core/vm/roles/roles.go
@@ -0,0 +1,50 @@
+//
+// 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 roles
+
+import "fmt"
+
+type Role byte
+
+const (
+ SenderDeployment = 0xA0
+ SenderValidation = 0xA1
+ PaymasterValidation = 0xA2
+ SenderExecution = 0xA3
+ PaymasterPostOp = 0xA4
+)
+
+func (r Role) String() string {
+ if s := roleToString[r]; s != "" {
+ return s
+ }
+ return fmt.Sprintf("role %#x not defined", int(r))
+}
+
+var roleToString = map[Role]string{
+ SenderDeployment: "SENDER_DEPLOYMENT",
+ SenderValidation: "SENDER_VALIDATION",
+ PaymasterValidation: "PAYMASTER_VALIDATION",
+ SenderExecution: "SENDER_EXECUTION",
+ PaymasterPostOp: "PAYMASTER_POST_OP",
+}
+
+// var stringToRole = map[string]Role{
+// "SENDER_DEPLOYMENT": SenderDeployment,
+// "SENDER_VALIDATION": SenderValidation,
+// "PAYMASTER_VALIDATION": PaymasterValidation,
+// "SENDER_EXECUTION": SenderExecution,
+// "PAYMASTER_POST_OP": PaymasterPostOp,
+// }
diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go
index e54041f7e277..3fb32d9f2e70 100644
--- a/core/vm/runtime/env.go
+++ b/core/vm/runtime/env.go
@@ -27,6 +27,12 @@ func NewEnv(cfg *Config) *vm.EVM {
GasPrice: cfg.GasPrice,
BlobHashes: cfg.BlobHashes,
BlobFeeCap: cfg.BlobFeeCap,
+
+ // EIP-7701: Set basic fields for non-AA transactions
+ // AA-specific fields (Sender, Deployer, Paymaster) remain nil
+ MaxPriorityFeePerGas: cfg.GasPrice,
+ MaxFeePerGas: cfg.GasPrice,
+ SenderExecutionGas: cfg.GasLimit,
}
blockContext := vm.BlockContext{
CanTransfer: core.CanTransfer,
diff --git a/params/forks/forks.go b/params/forks/forks.go
index 641d59434b72..1484bdbc6aad 100644
--- a/params/forks/forks.go
+++ b/params/forks/forks.go
@@ -46,6 +46,7 @@ const (
BPO4
BPO5
Amsterdam
+ Bogota
)
// String implements fmt.Stringer.
diff --git a/params/protocol_params.go b/params/protocol_params.go
index bb506af015c5..3147a9a20bdf 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -37,6 +37,7 @@ const (
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions.
+ TxGasAbstract uint64 = 15000 // Per transaction that has abstract validation.
TxDataZeroGas uint64 = 4 // Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions.
QuadCoeffDiv uint64 = 512 // Divisor for the quadratic particle of the memory cost equation.
LogDataGas uint64 = 8 // Per byte in a LOG* operation's data.
@@ -204,6 +205,9 @@ var (
// SystemAddress is where the system-transaction is sent from as per EIP-4788
SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
+ // AAEntryPoint is the default caller for top level AbstractTx frames.
+ AAEntryPoint = common.HexToAddress("0x7701")
+
// EIP-4788 - Beacon block root in the EVM
BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02")
BeaconRootsCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500")
diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go
index a90c2d522f64..484aa94f1310 100644
--- a/tests/transaction_test_util.go
+++ b/tests/transaction_test_util.go
@@ -81,7 +81,7 @@ func (tt *TransactionTest) Run() error {
return
}
// Intrinsic gas
- requiredGas, err = core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
+ requiredGas, err = core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, tx.SenderAuthorization() == nil)
if err != nil {
return
}