diff --git a/consensus/misc/eip7997.go b/consensus/misc/eip7997.go new file mode 100644 index 0000000000..8564d5283a --- /dev/null +++ b/consensus/misc/eip7997.go @@ -0,0 +1,47 @@ +// 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 misc + +import ( + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// ApplyEIP7997 inserts the deterministic deployment factory into the state as an +// irregular state transition, as specified by EIP-7997. The factory is a keyless +// CREATE2 factory that, once present at the canonical address on every EVM chain, +// allows contracts to be deployed at identical addresses across chains. +func ApplyEIP7997(statedb vm.StateDB) { + // The account must hold the canonical factory runtime code. If its code hash + // already matches, the chain satisfies EIP-7997 and nothing needs to change. + wantHash := crypto.Keccak256Hash(params.DeterministicFactoryCode) + if statedb.GetCodeHash(params.DeterministicFactoryAddress) == wantHash { + return + } + if !statedb.Exist(params.DeterministicFactoryAddress) { + statedb.CreateAccount(params.DeterministicFactoryAddress) + } + statedb.CreateContract(params.DeterministicFactoryAddress) + statedb.SetCode(params.DeterministicFactoryAddress, params.DeterministicFactoryCode, tracing.CodeChangeUnspecified) + + // Preserve a pre-existing nonce; only bump the default zero nonce to 1. + if statedb.GetNonce(params.DeterministicFactoryAddress) == 0 { + statedb.SetNonce(params.DeterministicFactoryAddress, 1, tracing.NonceChangeNewContract) + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index d93ce80dca..f02e0341bb 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -385,6 +385,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { misc.ApplyDAOHardFork(statedb) } + // EIP-7997: insert the deterministic deployment factory at the Amsterdam + // activation block via an irregular state transition. + if config.IsAmsterdam(b.header.Number, b.header.Time) && !config.IsAmsterdam(parent.Number(), parent.Time()) { + misc.ApplyEIP7997(statedb) + } if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) { // EIP-2935 diff --git a/core/eip7997_test.go b/core/eip7997_test.go new file mode 100644 index 0000000000..4c4d26507b --- /dev/null +++ b/core/eip7997_test.go @@ -0,0 +1,113 @@ +// 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 . + +// Tests for EIP-7997: the deterministic deployment factory inserted as an +// irregular state transition at the Amsterdam activation block. + +package core + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc" + "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/params" + "github.com/holiman/uint256" +) + +// TestApplyEIP7997 verifies the irregular state transition seeds the factory +// account with the canonical code and nonce. +func TestApplyEIP7997(t *testing.T) { + sdb := mkState(nil) + misc.ApplyEIP7997(sdb) + + if got := sdb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) { + t.Fatalf("factory code mismatch:\n got %x\nwant %x", got, params.DeterministicFactoryCode) + } + if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 1 { + t.Fatalf("factory nonce = %d, want %d", got, 1) + } +} + +// TestApplyEIP7997Existing checks that a chain which already hosts the factory +// (for example via its keyless creation transaction) is left untouched, so the +// transition never rewrites an existing nonce. +func TestApplyEIP7997Existing(t *testing.T) { + sdb := mkState(types.GenesisAlloc{ + params.DeterministicFactoryAddress: {Code: params.DeterministicFactoryCode, Nonce: 5}, + }) + misc.ApplyEIP7997(sdb) + + if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 5 { + t.Fatalf("existing factory nonce overwritten: got %d, want 5", got) + } +} + +// TestApplyEIP7997WrongCode checks that an account occupying the factory address +// with the wrong code is force-overwritten with the canonical runtime code, while +// a pre-existing non-zero nonce is preserved. +func TestApplyEIP7997WrongCode(t *testing.T) { + sdb := mkState(types.GenesisAlloc{ + params.DeterministicFactoryAddress: {Code: []byte{0x60, 0x00}, Nonce: 7}, + }) + misc.ApplyEIP7997(sdb) + + if got := sdb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) { + t.Fatalf("factory code not overwritten:\n got %x\nwant %x", got, params.DeterministicFactoryCode) + } + if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 7 { + t.Fatalf("factory nonce = %d, want %d (existing nonce must be preserved)", got, 7) + } +} + +// TestEIP7997FactoryDeploys exercises the inserted factory bytecode: calling it +// with a salt followed by init code must CREATE2-deploy the contract at the +// canonical deterministic address and return that address (20 bytes, unpadded). +func TestEIP7997FactoryDeploys(t *testing.T) { + sdb := mkState(nil) + misc.ApplyEIP7997(sdb) + + var ( + caller = common.Address{0xca} + salt [32]byte + // initcode returning the single-byte runtime 0xfe: + // PUSH1 0xfe PUSH1 0x00 MSTORE8 PUSH1 0x01 PUSH1 0x00 RETURN + initcode = common.FromHex("60fe60005360016000f3") + ) + salt[31] = 0x42 + + input := append(append([]byte{}, salt[:]...), initcode...) + + ret, _, err := amsterdamCoreEVM(sdb).Call(caller, params.DeterministicFactoryAddress, input, vm.NewGasBudget(10_000_000, 0), new(uint256.Int)) + if err != nil { + t.Fatalf("factory call failed: %v", err) + } + + want := crypto.CreateAddress2(params.DeterministicFactoryAddress, salt, crypto.Keccak256(initcode)) + if len(ret) != 20 { + t.Fatalf("factory returned %d bytes, want 20", len(ret)) + } + if got := common.BytesToAddress(ret); got != want { + t.Fatalf("factory returned address %x, want %x", got, want) + } + if code := sdb.GetCode(want); !bytes.Equal(code, []byte{0xfe}) { + t.Fatalf("deployed runtime code = %x, want fe", code) + } +} diff --git a/core/genesis.go b/core/genesis.go index 910a3fb0f5..7fad915ce2 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -725,6 +725,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis { // EIP-8282 - Builder Execution Requests params.BuilderDepositAddress: {Nonce: 1, Code: params.BuilderDepositCode, Balance: common.Big0}, params.BuilderExitAddress: {Nonce: 1, Code: params.BuilderExitCode, Balance: common.Big0}, + // EIP-7997 - Deterministic deployment factory + params.DeterministicFactoryAddress: {Nonce: 1, Code: params.DeterministicFactoryCode, Balance: common.Big0}, }, } if faucet != nil { diff --git a/core/state_processor.go b/core/state_processor.go index 5b81abef6f..bc565db6f9 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -81,6 +81,11 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(tracingStateDB) } + // EIP-7997: insert the deterministic deployment factory at the Amsterdam + // activation block via an irregular state transition. + if isEIP7997Transition(config, p.chain, header) { + misc.ApplyEIP7997(tracingStateDB) + } var ( context = NewEVMBlockContext(header, p.chain, nil) signer = types.MakeSigner(config, header.Number, header.Time) @@ -137,6 +142,19 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated }, nil } +// isEIP7997Transition reports whether the given header belongs to the first block +// on which the Amsterdam fork is active. +func isEIP7997Transition(config *params.ChainConfig, chain ChainContext, header *types.Header) bool { + if header.Number.Sign() == 0 || !config.IsAmsterdam(header.Number, header.Time) { + return false + } + parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return false + } + return !config.IsAmsterdam(parent.Number, parent.Time) +} + // PreExecution processes pre-execution system calls. func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList { _, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution") diff --git a/params/protocol_params.go b/params/protocol_params.go index a56e810074..cace632931 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -270,6 +270,10 @@ var ( BuilderDepositCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe146101065760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023457600182026001905f5b5f82111560695781019083028483029004916001019190604e565b90939004925050503660b814608957366102345734610234575f5260205ff35b8034106102345760383567ffffffffffffffff1680633b9aca001161023457633b9aca00029034031061023457600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b600354600254808203806101001161011d57506101005b5f5b8181146101c3578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360200181600301548152602001816004015481526020019060050154905260010161011f565b91018092146101d557906002556101e0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561020d57505f5b6001546020828201116102225750505f610228565b01602090035b5f555f60015560b8025ff35b5f5ffd") BuilderExitAddress = common.HexToAddress("0x000014574A74c805590AFF9499fc7A690f008282") BuilderExitCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd") + + // EIP-7997 - Deterministic deployment factory (keyless CREATE2 factory) + DeterministicFactoryAddress = common.HexToAddress("0x4e59b44847b379578588920cA78FbF26c0B4956C") + DeterministicFactoryCode = common.FromHex("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3") ) // System log events.