From 44125c2e4532983381d0baee99b1893ded27c1f7 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 12:50:22 +0200 Subject: [PATCH 01/47] core/vm: add analysis tests --- core/vm/analysis_eof_test.go | 76 +++++++++++++++++++++++++++++++++ core/vm/analysis_legacy_test.go | 28 ------------ 2 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 core/vm/analysis_eof_test.go diff --git a/core/vm/analysis_eof_test.go b/core/vm/analysis_eof_test.go new file mode 100644 index 0000000000..f01bbefec9 --- /dev/null +++ b/core/vm/analysis_eof_test.go @@ -0,0 +1,76 @@ +// Copyright 2024 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 vm + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestEOFAnalysis(t *testing.T) { + tests := []struct { + code []byte + exp byte + which int + }{ + {[]byte{byte(RJUMP), 0x01, 0x01, 0x01}, 0b0000_0110, 0}, + {[]byte{byte(RJUMPI), byte(RJUMP), byte(RJUMP), byte(RJUMPI)}, 0b0011_0110, 0}, + {[]byte{byte(RJUMPV), 0x02, byte(RJUMP), 0x00, byte(RJUMPI), 0x00}, 0b0011_1110, 0}, + } + for i, test := range tests { + ret := eofCodeBitmap(test.code) + if ret[test.which] != test.exp { + t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which]) + } + } +} + +func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) { + var op OpCode + bencher := func(b *testing.B) { + code := make([]byte, analysisCodeSize) + b.SetBytes(analysisCodeSize) + for i := range code { + code[i] = byte(op) + } + bits := make(bitvec, len(code)/8+1+4) + b.ResetTimer() + for i := 0; i < b.N; i++ { + clear(bits) + eofCodeBitmapInternal(code, bits) + } + } + for op = PUSH1; op <= PUSH32; op++ { + bench.Run(op.String(), bencher) + } + op = JUMPDEST + bench.Run(op.String(), bencher) + op = STOP + bench.Run(op.String(), bencher) + op = RJUMPV + bench.Run(op.String(), bencher) + op = EOFCREATE + bench.Run(op.String(), bencher) +} + +func FuzzCodeAnalysis(f *testing.F) { + f.Add(common.FromHex("5e30303030")) + f.Fuzz(func(t *testing.T, data []byte) { + eofCodeBitmap(data) + }) +} diff --git a/core/vm/analysis_legacy_test.go b/core/vm/analysis_legacy_test.go index 7f5de225e2..471d2b4ffb 100644 --- a/core/vm/analysis_legacy_test.go +++ b/core/vm/analysis_legacy_test.go @@ -105,31 +105,3 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) { op = STOP bench.Run(op.String(), bencher) } - -func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) { - var op OpCode - bencher := func(b *testing.B) { - code := make([]byte, analysisCodeSize) - b.SetBytes(analysisCodeSize) - for i := range code { - code[i] = byte(op) - } - bits := make(bitvec, len(code)/8+1+4) - b.ResetTimer() - for i := 0; i < b.N; i++ { - clear(bits) - eofCodeBitmapInternal(code, bits) - } - } - for op = PUSH1; op <= PUSH32; op++ { - bench.Run(op.String(), bencher) - } - op = JUMPDEST - bench.Run(op.String(), bencher) - op = STOP - bench.Run(op.String(), bencher) - op = RJUMPV - bench.Run(op.String(), bencher) - op = EOFCREATE - bench.Run(op.String(), bencher) -} From d744516b7406f68b2532d3631cbf9b996861b4f9 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 12:58:22 +0200 Subject: [PATCH 02/47] core/vm: parse containers on call --- core/vm/contract.go | 31 +++++++++++++++++++++++-------- core/vm/evm.go | 28 ++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index cfda75b27e..fb5b12c2a9 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -52,10 +52,11 @@ type Contract struct { jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. analysis bitvec // Locally cached result of JUMPDEST analysis - Code []byte - CodeHash common.Hash - CodeAddr *common.Address - Input []byte + Code []byte + Container *Container + CodeHash common.Hash + CodeAddr *common.Address + Input []byte // is the execution frame represented by this object a contract deployment IsDeployment bool @@ -144,11 +145,12 @@ func (c *Contract) AsDelegate() *Contract { } // GetOp returns the n'th element in the contract's byte array -func (c *Contract) GetOp(n uint64) OpCode { - if n < uint64(len(c.Code)) { +func (c *Contract) GetOp(n uint64, s uint64) OpCode { + if c.IsEOF() && n < uint64(len(c.Container.codeSections[s])) { + return OpCode(c.Container.codeSections[s][n]) + } else if n < uint64(len(c.Code)) { return OpCode(c.Code[n]) } - return STOP } @@ -193,10 +195,23 @@ func (c *Contract) Value() *uint256.Int { return c.value } +// IsEOF returns whether the contract is EOF. +func (c *Contract) IsEOF() bool { + return c.Container != nil +} + +func (c *Contract) CodeAt(section uint64) []byte { + if c.Container == nil { + return c.Code + } + return c.Container.codeSections[section] +} + // SetCallCode sets the code of the contract and address of the backing data // object -func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, container *Container) { c.Code = code + c.Container = container c.CodeHash = hash c.CodeAddr = addr } diff --git a/core/vm/evm.go b/core/vm/evm.go index 616668d565..c717f55826 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -18,7 +18,9 @@ package vm import ( "errors" + "fmt" "math/big" + "strings" "sync/atomic" "github.com/ethereum/go-ethereum/common" @@ -223,7 +225,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -286,7 +288,8 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if witness := evm.StateDB.Witness(); witness != nil { witness.AddCode(evm.StateDB.GetCode(addrCopy)) } - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + code := evm.StateDB.GetCode(addrCopy) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -336,7 +339,8 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if witness := evm.StateDB.Witness(); witness != nil { witness.AddCode(evm.StateDB.GetCode(addrCopy)) } - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + code := evm.StateDB.GetCode(addrCopy) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -394,7 +398,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if witness := evm.StateDB.Witness(); witness != nil { witness.AddCode(evm.StateDB.GetCode(addrCopy)) } - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + code := evm.StateDB.GetCode(addrCopy) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) // 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. @@ -617,3 +622,18 @@ func (evm *EVM) GetVMContext() *tracing.VMContext { StateDB: evm.StateDB, } } + +// parseContainer tries to parse an EOF container if the Shanghai fork is active. It expects the code to already be validated. +func (evm *EVM) parseContainer(b []byte) *Container { + if evm.chainRules.IsPrague { + var c Container + if err := c.UnmarshalBinary(b, false); err != nil && strings.HasPrefix(err.Error(), "invalid magic") { + return nil + } else if err != nil { + // Code was already validated, so no other errors should be possible. + panic(fmt.Sprintf("unexpected error: %v\ncode: %s\n", err, common.Bytes2Hex(b))) + } + return &c + } + return nil +} From d23c5e5e62eed5a46e4304df16fa1cf387b952f7 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:07:15 +0200 Subject: [PATCH 03/47] core: special case on creation for eof code --- core/state_transition.go | 12 +++++++++--- core/vm/errors.go | 1 + core/vm/evm.go | 2 +- core/vm/instructions.go | 2 +- core/vm/runtime/runtime.go | 1 + 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index d285d03fe2..cd2c65ebd5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "math" "math/big" @@ -444,10 +445,15 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) if contractCreation { - ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value) + ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value, rules.IsPrague) + // Special case for EOF, if the initcode or deployed code is + // invalid, the tx is considered valid (so update nonce), but + // gas for initcode execution is not consumed. + // Only intrinsic creation transaction costs are charged. + if errors.Is(vmerr, vm.ErrInvalidEOFInitcode) { + st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) + } } 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, value) } diff --git a/core/vm/errors.go b/core/vm/errors.go index 839bf56a1a..0cfdcd7d74 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,6 +37,7 @@ var ( ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrInvalidEOFInitcode = errors.New("invalid eof initcode") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") // errStopToken is an internal token indicating interpreter loop termination, diff --git a/core/vm/evm.go b/core/vm/evm.go index c717f55826..009bb6aaf3 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -564,7 +564,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu } // Create creates a new contract using code as deployment code. -func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int, allowEOF bool) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 35d6393fba..2196a689f0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -682,7 +682,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation) - res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, &value) + res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, &value, false) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index f83ed682cd..84e21ef358 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -184,6 +184,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { input, cfg.GasLimit, uint256.MustFromBig(cfg.Value), + rules.IsPrague, ) return code, address, leftOverGas, err } From 2297ef65dd00a7caf8a736b2af40c1cee14268b0 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:21:09 +0200 Subject: [PATCH 04/47] core/vm: add ReturnStack etc to ScopeContext --- core/vm/evm.go | 17 +++++++--- core/vm/instructions_test.go | 26 +++++++-------- core/vm/interpreter.go | 55 ++++++++++++++++++++++++++----- eth/tracers/js/tracer_test.go | 2 +- eth/tracers/logger/logger_test.go | 2 +- 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 009bb6aaf3..143a984ac6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -226,7 +226,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } } @@ -290,7 +290,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, } code := evm.StateDB.GetCode(addrCopy) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } if err != nil { @@ -341,7 +341,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by } code := evm.StateDB.GetCode(addrCopy) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) - ret, err = evm.interpreter.Run(contract, input, false) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } if err != nil { @@ -403,7 +403,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // 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. - ret, err = evm.interpreter.Run(contract, input, true) + ret, err = evm.interpreter.Run(contract, input, true, false) gas = contract.Gas } if err != nil { @@ -533,7 +533,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) { - ret, err := evm.interpreter.Run(contract, nil, false) + // Charge the contract creation init gas in verkle mode + if evm.chainRules.IsEIP4762 { + if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + return nil, ErrOutOfGas + } + } + + ret, err := evm.interpreter.Run(contract, nil, false, true) if err != nil { return ret, err } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index a3f9ee81d1..a7ed102db2 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -116,7 +116,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -231,7 +231,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -258,7 +258,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -294,7 +294,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() - scope = &ScopeContext{nil, stack, nil} + scope = &ScopeContext{nil, stack, nil, 0, nil, false} evmInterpreter = NewEVMInterpreter(env) ) @@ -545,13 +545,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -575,7 +575,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -590,7 +590,7 @@ func TestOpTstore(t *testing.T) { to = common.Address{1} contractRef = contractRef{caller} contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0) - scopeContext = ScopeContext{mem, stack, contract} + scopeContext = ScopeContext{mem, stack, contract, 0, nil, false} value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) @@ -638,7 +638,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -705,7 +705,7 @@ func TestCreate2Addresses(t *testing.T) { stack.push(big.NewInt(int64(len(code)))) //size stack.push(big.NewInt(0)) // memstart stack.push(big.NewInt(0)) // value - gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0) + gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0, nil, false, 0) fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String()) */ expected := common.BytesToAddress(common.FromHex(tt.expected)) @@ -733,7 +733,7 @@ func TestRandom(t *testing.T) { pc = uint64(0) evmInterpreter = env.interpreter ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -775,7 +775,7 @@ func TestBlobHash(t *testing.T) { evmInterpreter = env.interpreter ) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -916,7 +916,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 793f398367..42b2483ac1 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -43,6 +43,10 @@ type ScopeContext struct { Memory *Memory Stack *Stack Contract *Contract + + CodeSection uint64 + ReturnStack ReturnStack + InitCodeMode bool } // MemoryData returns the underlying memory slice. Callers must not modify the contents @@ -89,10 +93,33 @@ func (ctx *ScopeContext) ContractCode() []byte { return ctx.Contract.Code } +type ReturnStack []*ReturnContext + +// Pop removes an element from the return stack +// Panics if the return stack is empty, which should +// never happen, since EOF code is verified for that. +func (ctx *ReturnStack) Pop() *ReturnContext { + item := (*ctx)[ctx.Len()-1] + *ctx = (*ctx)[:ctx.Len()-1] + return item +} + +// Len returns the length of the return stack +func (ctx *ReturnStack) Len() int { + return len(*ctx) +} + +type ReturnContext struct { + Section uint64 + Pc uint64 + StackHeight int +} + // EVMInterpreter represents an EVM interpreter type EVMInterpreter struct { - evm *EVM - table *JumpTable + evm *EVM + table *JumpTable + tableEOF *JumpTable hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes @@ -148,7 +175,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { } } evm.Config.ExtraEips = extraEips - return &EVMInterpreter{evm: evm, table: table} + return &EVMInterpreter{evm: evm, table: table, tableEOF: &pragueEOFInstructionSet} } // Run loops and evaluates the contract's code with the given input data and returns @@ -157,7 +184,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. -func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { +func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, isInitCode bool) (ret []byte, err error) { // Increment the call depth which is restricted to 1024 in.evm.depth++ defer func() { in.evm.depth-- }() @@ -179,13 +206,17 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } var ( + jt *JumpTable // current jump table op OpCode // current opcode mem = NewMemory() // bound memory stack = newstack() // local stack callContext = &ScopeContext{ - Memory: mem, - Stack: stack, - Contract: contract, + Memory: mem, + Stack: stack, + Contract: contract, + CodeSection: 0, + ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}}, + InitCodeMode: isInitCode, } // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC @@ -208,6 +239,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( }() contract.Input = input + if contract.IsEOF() { + jt = in.tableEOF + } else { + jt = in.table + } + if debug { defer func() { // this deferred method handles exit-with-error if err == nil { @@ -240,8 +277,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. - op = contract.GetOp(pc) - operation := in.table[op] + op = contract.GetOp(pc, callContext.CodeSection) + operation := jt[op] cost = operation.constantGas // For tracing // Validate stack if sLen := stack.len(); sLen < operation.minStack { diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 7122b3c90e..616e923ae4 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -77,7 +77,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) - ret, err := env.Interpreter().Run(contract, []byte{}, false) + ret, err := env.Interpreter().Run(contract, []byte{}, false, false) tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 137608f884..29308cec43 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -62,7 +62,7 @@ func TestStoreCapture(t *testing.T) { contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash logger.OnTxStart(env.GetVMContext(), nil, common.Address{}) - _, err := env.Interpreter().Run(contract, []byte{}, false) + _, err := env.Interpreter().Run(contract, []byte{}, false, false) if err != nil { t.Fatal(err) } From 8374793fec248352a94ab4a2424a9daaa9eb71cd Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:31:51 +0200 Subject: [PATCH 05/47] core/vm: add new eof-behavior to opDelegateCall --- core/vm/evm.go | 7 +++++-- core/vm/instructions.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 143a984ac6..7c979d96d3 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -311,7 +311,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // // 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(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64, fromEOF bool) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // NOTE: caller must, at all times be a contract. It should never happen @@ -334,12 +334,15 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { addrCopy := addr + code := evm.StateDB.GetCode(addrCopy) + if fromEOF && !hasEOFMagic(code) { + return nil, gas, errors.New("extDelegateCall to non-eof contract") + } // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() if witness := evm.StateDB.Witness(); witness != nil { witness.AddCode(evm.StateDB.GetCode(addrCopy)) } - code := evm.StateDB.GetCode(addrCopy) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 2196a689f0..9ca29a9a70 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -823,7 +823,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, false) if err != nil { temp.Clear() } else { From 3012db54ee515ac26f83a4bfd64ae0b68e83c3a1 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:40:50 +0200 Subject: [PATCH 06/47] core/vm: added new behavior to legacy opcodes --- core/vm/instructions.go | 52 ++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9ca29a9a70..c03322920a 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -323,6 +323,18 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte length = scope.Stack.pop() ) + if scope.Contract.IsEOF() { + 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(interpreter.returnData, dataOffset64, length64)) + return nil, nil + } + offset64, overflow := dataOffset.Uint64WithOverflow() if overflow { return nil, ErrReturnDataOutOfBounds @@ -344,12 +356,18 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if witness := interpreter.evm.StateDB.Witness(); witness != nil { witness.AddCode(interpreter.evm.StateDB.GetCode(address)) } - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) + // TODO this should not need to pull up the whole code + code := interpreter.evm.StateDB.GetCode(slot.Bytes20()) + if isEOFVersion1(code) { + slot.SetUint64(2) + } else { + slot.SetUint64(uint64(len(interpreter.evm.StateDB.GetCode(slot.Bytes20())))) + } return nil, nil } func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code)))) + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.CodeAt(scope.CodeSection))))) return nil, nil } @@ -364,7 +382,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ uint64CodeOffset = math.MaxUint64 } - codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + codeCopy := getData(scope.Contract.CodeAt(scope.CodeSection), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } @@ -376,6 +394,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) memOffset = stack.pop() codeOffset = stack.pop() length = stack.pop() + lengthU64 = length.Uint64() ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { @@ -386,8 +405,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if witness := interpreter.evm.StateDB.Witness(); witness != nil { witness.AddCode(code) } - codeCopy := getData(code, uint64CodeOffset, length.Uint64()) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + if isEOFVersion1(code) { + lengthU64 = 2 + } + codeCopy := getData(code, uint64CodeOffset, lengthU64) + scope.Memory.Set(memOffset.Uint64(), lengthU64, codeCopy) return nil, nil } @@ -424,7 +446,13 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { - slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + // TODO this should not need to pull up the whole code + code := interpreter.evm.StateDB.GetCode(address) + if HasEOFByte(code) { + slot.SetFromHex("0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5") + } else { + slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + } } return nil, nil } @@ -885,7 +913,7 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])} + return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.CodeAt(scope.CodeSection)[*pc])} } func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -964,12 +992,13 @@ func makeLog(size int) executionFunc { // opPush1 is a specialized version of pushN func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - codeLen = uint64(len(scope.Contract.Code)) + code = scope.Contract.CodeAt(scope.CodeSection) + codeLen = uint64(len(code)) integer = new(uint256.Int) ) *pc += 1 if *pc < codeLen { - scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + scope.Stack.push(integer.SetUint64(uint64(code[*pc]))) } else { scope.Stack.push(integer.Clear()) } @@ -980,13 +1009,14 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by func makePush(size uint64, pushByteSize int) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - codeLen = len(scope.Contract.Code) + code = scope.Contract.CodeAt(scope.CodeSection) + codeLen = len(code) start = min(codeLen, int(*pc+1)) end = min(codeLen, start+pushByteSize) ) scope.Stack.push(new(uint256.Int).SetBytes( common.RightPadBytes( - scope.Contract.Code[start:end], + code[start:end], pushByteSize, )), ) From 5bd220b2f0a1a08d222963674efdc9f13ae9b732 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:47:17 +0200 Subject: [PATCH 07/47] core/vm: added opSwapN,Exchange,DupN opcodes --- core/vm/eof_instructions.go | 26 +++++++++++++++++++++++--- core/vm/stack.go | 8 ++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 800d14d7b8..f37402489d 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -78,17 +78,37 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // opDupN implements the DUPN opcode func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + index = int(code[*pc+1]) + 1 + ) + scope.Stack.dup(index) + *pc += 1 // move past immediate + return nil, nil } // opSwapN implements the SWAPN opcode func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + index = int(code[*pc+1]) + 1 + ) + scope.Stack.swap(index + 1) + *pc += 1 // move past immediate + return nil, nil } // opExchange implements the EXCHANGE opcode func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + index = int(code[*pc+1]) + n = (index >> 4) + 1 + m = (index & 0x0F) + 1 + ) + scope.Stack.swapN(n+1, n+m+1) + *pc += 1 // move past immediate + return nil, nil } // opReturnDataLoad implements the RETURNDATALOAD opcode diff --git a/core/vm/stack.go b/core/vm/stack.go index 879dc9aa6d..22358b3a54 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -113,6 +113,14 @@ func (st *Stack) swap16() { st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17] } +func (st *Stack) swap(n int) { + st.swapN(n, 1) +} + +func (st *Stack) swapN(n, m int) { + st.data[st.len()-n], st.data[st.len()-m] = st.data[st.len()-m], st.data[st.len()-n] +} + func (st *Stack) dup(n int) { st.push(&st.data[st.len()-n]) } From 866ad956a43bb8a5aca1aabb1e541bd2a13974ac Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:49:12 +0200 Subject: [PATCH 08/47] core/vm: added RJUMP,RJUMPI,RJUMPV opcodes --- core/vm/eof_instructions.go | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index f37402489d..80b6736d31 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -18,17 +18,45 @@ package vm // opRjump implements the RJUMP opcode. func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + offset = parseInt16(code[*pc+1:]) + ) + // move pc past op and operand (+3), add relative offset, subtract 1 to + // account for interpreter loop. + *pc = uint64(int64(*pc+3) + int64(offset) - 1) + return nil, nil } // opRjumpi implements the RJUMPI opcode func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + condition := scope.Stack.pop() + if condition.BitLen() == 0 { + // Not branching, just skip over immediate argument. + *pc += 2 + return nil, nil + } + return opRjump(pc, interpreter, scope) } // opRjumpv implements the RJUMPV opcode func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + maxIndex = uint64(code[*pc+1]) + 1 + idx = scope.Stack.pop() + ) + if idx, overflow := idx.Uint64WithOverflow(); overflow || idx >= maxIndex { + // Index out-of-bounds, don't branch, just skip over immediate + // argument. + *pc += 1 + maxIndex*2 + return nil, nil + } + offset := parseInt16(code[*pc+2+2*idx.Uint64():]) + // move pc past op and count byte (2), move past count number of 16bit offsets (count*2), add relative offset, subtract 1 to + // account for interpreter loop. + *pc = uint64(int64(*pc+2+maxIndex*2) + int64(offset) - 1) + return nil, nil } // opCallf implements the CALLF opcode From 026306218b8f61e4c014e80456233b41f4a634d7 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:51:37 +0200 Subject: [PATCH 09/47] core/vm: added CALLF,RETF opcodes --- core/vm/eof_instructions.go | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 80b6736d31..c045c971c0 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -16,6 +16,13 @@ package vm +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/common/math" +) + // opRjump implements the RJUMP opcode. func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( @@ -61,12 +68,39 @@ func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // opCallf implements the CALLF opcode func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + idx = binary.BigEndian.Uint16(code[*pc+1:]) + typ = scope.Contract.Container.types[idx] + ) + if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 { + return nil, fmt.Errorf("stack overflow") + } + if scope.ReturnStack.Len() > 1024 { + return nil, fmt.Errorf("return stack overflow") + } + retCtx := &ReturnContext{ + Section: scope.CodeSection, + Pc: *pc + 3, + StackHeight: scope.Stack.len() - int(typ.inputs), + } + scope.ReturnStack = append(scope.ReturnStack, retCtx) + scope.CodeSection = uint64(idx) + *pc = uint64(math.MaxUint64) + return nil, nil } // opRetf implements the RETF opcode func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + retCtx := scope.ReturnStack.Pop() + scope.CodeSection = retCtx.Section + *pc = retCtx.Pc - 1 + + // If returning from top frame, exit cleanly. + if scope.ReturnStack.Len() == 0 { + return nil, errStopToken + } + return nil, nil } // opJumpf implements the JUMPF opcode From a58ba40cf6daa13ea5192ff8547fd45074fa032e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 13:52:26 +0200 Subject: [PATCH 10/47] core/vm: added JUMPF opcodes --- core/vm/eof_instructions.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index c045c971c0..d187342551 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -105,7 +105,17 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // opJumpf implements the JUMPF opcode func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + idx = binary.BigEndian.Uint16(code[*pc+1:]) + typ = scope.Contract.Container.types[idx] + ) + if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 { + return nil, fmt.Errorf("stack overflow") + } + scope.CodeSection = uint64(idx) + *pc = uint64(math.MaxUint64) + return nil, nil } // opEOFCreate implements the EOFCREATE opcode From 9fa14a1af4d029de723bfe7573db379f10085f3d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:06:25 +0200 Subject: [PATCH 11/47] core/vm: added EOFCREATE opcode --- core/vm/eof_instructions.go | 52 +++++++++++++++++++++++++++- core/vm/errors.go | 2 ++ core/vm/evm.go | 69 +++++++++++++++++++++++++++++-------- 3 files changed, 108 insertions(+), 15 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index d187342551..1b91819f87 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -21,6 +21,8 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/params" ) // opRjump implements the RJUMP opcode. @@ -120,7 +122,55 @@ func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // opEOFCreate implements the EOFCREATE opcode func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + idx = code[*pc+1] + value = scope.Stack.pop() + salt = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) + ) + if int(idx) >= len(scope.Contract.Container.subContainerCodes) { + return nil, fmt.Errorf("invalid subcontainer") + } + + // Deduct hashing charge + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + hashingCharge := (params.Keccak256WordGas) * ((uint64(len(scope.Contract.Container.subContainerCodes[idx])) + 31) / 32) + if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok { + return nil, ErrGasUintOverflow + } + if interpreter.evm.Config.Tracer != nil { + if interpreter.evm.Config.Tracer != nil { + interpreter.evm.Config.Tracer.OnOpcode(*pc, byte(EOFCREATE), 0, hashingCharge, scope, interpreter.returnData, interpreter.evm.depth, nil) + } + } + gas := scope.Contract.Gas + // Reuse last popped value from stack + stackvalue := size + // Apply EIP150 + gas -= gas / 64 + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + // Skip the immediate + *pc += 1 + res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerCodes[idx], gas, &value, &salt) + if suberr != nil { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.push(&stackvalue) + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil } // opReturnContract implements the RETURNCONTRACT opcode diff --git a/core/vm/errors.go b/core/vm/errors.go index 0cfdcd7d74..ef3d285a21 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -36,7 +36,9 @@ var ( ErrWriteProtection = errors.New("write protection") ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrLegacyCode = errors.New("invalid code: EOF contract must not deploy legacy code") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrInvalidEOF = errors.New("invalid eof") ErrInvalidEOFInitcode = errors.New("invalid eof initcode") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") diff --git a/core/vm/evm.go b/core/vm/evm.go index 7c979d96d3..73a3a78b63 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -435,7 +435,7 @@ func (c *codeAndHash) Hash() common.Hash { } // create creates a new contract using code as deployment code. -func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { +func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode, input []byte, allowEOF bool) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig()) defer func(startGas uint64) { @@ -450,6 +450,33 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } + + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. If + // the initcode is EOF, contract.Container will be set. + contract := NewContract(caller, AccountRef(address), value, gas) + contract.SetCodeOptionalHash(&address, codeAndHash) + contract.IsDeployment = true + + // Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail. + isInitcodeEOF := hasEOFMagic(codeAndHash.code) + if isInitcodeEOF { + if allowEOF { + // If the initcode is EOF, verify it is well-formed. + var c Container + if err := c.UnmarshalBinary(codeAndHash.code, isInitcodeEOF); err != nil { + return nil, common.Address{}, gas, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, err) + } + if err := c.ValidateCode(evm.interpreter.tableEOF, isInitcodeEOF); err != nil { + return nil, common.Address{}, gas, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, err) + } + contract.Container = &c + } else { + // Don't allow EOF contract to execute legacy initcode. + return nil, common.Address{}, gas, ErrLegacyCode + } + } + // Check for nonce overflow and then update caller nonce by 1. nonce := evm.StateDB.GetNonce(caller.Address()) if nonce+1 < nonce { return nil, common.Address{}, gas, ErrNonceUintOverflow @@ -517,13 +544,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. - contract := NewContract(caller, AccountRef(address), value, gas) - contract.SetCodeOptionalHash(&address, codeAndHash) - contract.IsDeployment = true - - ret, err = evm.initNewContract(contract, address, value) + ret, err = evm.initNewContract(contract, address, value, isInitcodeEOF) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { @@ -535,7 +556,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. -func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) { +func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int, isInitcodeEOF bool) ([]byte, error) { // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { @@ -543,7 +564,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu } } - ret, err := evm.interpreter.Run(contract, nil, false, true) + ret, err := evm.interpreter.Run(contract, nil, false, contract.IsDeployment) if err != nil { return ret, err } @@ -553,9 +574,22 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu return ret, ErrMaxCodeSizeExceeded } + // Reject legacy contract deployment from EOF. + if isInitcodeEOF && !hasEOFMagic(ret) { + return ret, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, ErrLegacyCode) + } + // Reject EOF deployment from legacy. + if isInitcodeEOF && hasEOFMagic(ret) { + return ret, ErrLegacyCode + } + // Reject code starting with 0xEF if EIP-3541 is enabled. - if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { - return ret, ErrInvalidCode + if len(ret) >= 1 && HasEOFByte(ret) { + if evm.chainRules.IsPrague && isInitcodeEOF { + // Don't reject EOF contracts after Shanghai + } else if evm.chainRules.IsLondon { + err = ErrInvalidCode + } } if !evm.chainRules.IsEIP4762 { @@ -576,7 +610,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu // Create creates a new contract using code as deployment code. func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int, allowEOF bool) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) - return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE) + return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE, nil, allowEOF) } // Create2 creates a new contract using code as deployment code. @@ -586,7 +620,14 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint2 func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, nil, false) +} + +// EOFCreate creates a new eof contract. +func (evm *EVM) EOFCreate(caller ContractRef, input []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + codeAndHash := &codeAndHash{code: subcontainer} + contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, input, true) } // ChainConfig returns the environment's chain configuration From 5992baf543fb1d3ca233196332147ba892ebc036 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:11:23 +0200 Subject: [PATCH 12/47] core/vm: added RETURNCONTRACT opcode --- core/vm/eof_instructions.go | 42 ++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 1b91819f87..63b6be366f 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -18,6 +18,7 @@ package vm import ( "encoding/binary" + "errors" "fmt" "github.com/ethereum/go-ethereum/common/math" @@ -175,7 +176,46 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( // opReturnContract implements the RETURNCONTRACT opcode func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + if !scope.InitCodeMode { + return nil, errors.New("returncontract in non-initcode mode") + } + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + idx = code[*pc+1] + offset = scope.Stack.pop() + size = scope.Stack.pop() + ) + if int(idx) >= len(scope.Contract.Container.subContainerCodes) { + return nil, fmt.Errorf("invalid subcontainer") + } + ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) + containerCode := scope.Contract.Container.subContainerCodes[idx] + if len(containerCode) == 0 { + return nil, errors.New("nonexistant subcontainer") + } + // Validate the subcontainer + var c Container + if err := c.UnmarshalSubContainer(containerCode, false); err != nil { + return nil, err + } + + // append the auxdata + c.data = append(c.data, ret...) + if len(c.data) < c.dataSize { + return nil, errors.New("incomplete aux data") + } + c.dataSize = len(c.data) + + // probably unneeded as subcontainers are deeply validated + if err := c.ValidateCode(interpreter.tableEOF, false); err != nil { + return nil, err + } + + // Restore context + retCtx := scope.ReturnStack.Pop() + scope.CodeSection = retCtx.Section + *pc = retCtx.Pc - 1 // account for interpreter loop + return c.MarshalBinary(), errStopToken } // opDataLoad implements the DATALOAD opcode From bb1885c78eb69fbd48166acaad3838adb1ff1a0c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:12:51 +0200 Subject: [PATCH 13/47] core/vm: added DATALOAD,DATALOADN,DATASIZE,DATACOPY opcode --- core/vm/eof_instructions.go | 39 +++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 63b6be366f..df20864268 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) // opRjump implements the RJUMP opcode. @@ -220,22 +221,52 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte // opDataLoad implements the DATALOAD opcode func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + stackItem = scope.Stack.pop() + offset, overflow = stackItem.Uint64WithOverflow() + ) + if overflow { + stackItem.Clear() + scope.Stack.push(&stackItem) + } else { + data := getData(scope.Contract.Container.data, offset, 32) + scope.Stack.push(stackItem.SetBytes(data)) + } + return nil, nil } // opDataLoadN implements the DATALOADN opcode func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + code = scope.Contract.CodeAt(scope.CodeSection) + offset = uint64(binary.BigEndian.Uint16(code[*pc+1:])) + ) + data := getData(scope.Contract.Container.data, offset, 32) + scope.Stack.push(new(uint256.Int).SetBytes(data)) + *pc += 2 // move past 2 byte immediate + return nil, nil } // opDataSize implements the DATASIZE opcode func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + length := len(scope.Contract.Container.data) + item := uint256.NewInt(uint64(length)) + scope.Stack.push(item) + return nil, nil } // opDataCopy implements the DATACOPY opcode func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + memOffset = scope.Stack.pop() + offset = scope.Stack.pop() + size = scope.Stack.pop() + ) + // These values are checked for overflow during memory expansion calculation + // (the memorySize function on the opcode). + data := getData(scope.Contract.Container.data, offset.Uint64(), size.Uint64()) + scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) + return nil, nil } // opDupN implements the DUPN opcode From c7db447b1faa44bf68f95b9eb69bdad6719a987e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:13:37 +0200 Subject: [PATCH 14/47] core/vm: added RETURNDATALOAD opcode --- core/vm/eof_instructions.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index df20864268..acf9506360 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -306,7 +306,15 @@ func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // opReturnDataLoad implements the RETURNDATALOAD opcode func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + offset = scope.Stack.pop() + ) + offset64, overflow := offset.Uint64WithOverflow() + if overflow { + offset64 = math.MaxUint64 + } + scope.Stack.push(offset.SetBytes(getData(interpreter.returnData, offset64, 32))) + return nil, nil } // opExtCall implements the EOFCREATE opcode From d206fba16d2d8c8da423b166070ef074f5a202fa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:16:14 +0200 Subject: [PATCH 15/47] core/vm: added EXTCALL,EXTDELEGATECALL,EXTSTATICCALL opcode --- core/vm/eof_instructions.go | 131 +++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 3 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index acf9506360..c06071f6d5 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/params" @@ -319,15 +320,139 @@ func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte // opExtCall implements the EOFCREATE opcode func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + stack := scope.Stack + // Use all available gas + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, inOffset, inSize, value := stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + if addr.ByteLen() > 20 { + return nil, errors.New("address space extension") + } + // safe a memory alloc + temp := addr + // Get the arguments from the memory. + args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) + + if interpreter.readOnly && !value.IsZero() { + return nil, ErrWriteProtection + } + + var ( + ret []byte + returnGas uint64 + err error + ) + if interpreter.evm.callGasTemp == 0 { + // zero temp call gas indicates a min retained gas error + ret, returnGas, err = nil, 0, ErrExecutionReverted + } else { + ret, returnGas, err = interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value) + } + + if errors.Is(err, ErrExecutionReverted) || errors.Is(err, ErrInsufficientBalance) || errors.Is(err, ErrDepth) { + temp.SetOne() + } else if err != nil { + temp.SetUint64(2) + } else { + temp.Clear() + } + stack.push(&temp) + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil } // opExtDelegateCall implements the EXTDELEGATECALL opcode func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + stack := scope.Stack + // Use all available gas + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + if addr.ByteLen() > 20 { + return nil, errors.New("address space extension") + } + // safe a memory alloc + temp := addr + // Get arguments from the memory. + args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) + + // Check that we're only calling non-legacy contracts + var ( + err error + ret []byte + returnGas uint64 + ) + code := interpreter.evm.StateDB.GetCode(toAddr) + if !hasEOFMagic(code) { + // Delegate-calling a non-eof contract should return 1 + err = ErrExecutionReverted + ret = nil + returnGas = gas + } else if interpreter.evm.callGasTemp == 0 { + // zero temp call gas indicates a min retained gas error + ret, returnGas, err = nil, 0, ErrExecutionReverted + } else { + ret, returnGas, err = interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, true) + } + + if err == ErrExecutionReverted || err == ErrDepth { + temp.SetOne() + } else if err != nil { + temp.SetUint64(2) + } else { + temp.Clear() + } + stack.push(&temp) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil } // opExtStaticCall implements the EXTSTATICCALL opcode func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + stack := scope.Stack + // Use all available gas + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + if addr.ByteLen() > 20 { + return nil, errors.New("address space extension") + } + // safe a memory alloc + temp := addr + // Get arguments from the memory. + args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) + + var ( + ret []byte + returnGas uint64 + err error + ) + if interpreter.evm.callGasTemp == 0 { + // zero temp call gas indicates a min retained gas error + ret, returnGas, err = nil, 0, ErrExecutionReverted + } else { + ret, returnGas, err = interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) + } + + if err == ErrExecutionReverted || err == ErrDepth { + temp.SetOne() + } else if err != nil { + temp.SetUint64(2) + } else { + temp.Clear() + } + stack.push(&temp) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil } From 3d82fb13a323ee3e031bc9a7eba03a8381796f73 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:22:47 +0200 Subject: [PATCH 16/47] core/vm: add gas computation for EOFCREATE, EXT{CALL,DELEGATECALL,STATICCALL} --- core/vm/gas.go | 30 ++++++++++++++++ core/vm/gas_table.go | 74 ++++++++++++++++++++++++++++++++++++--- params/protocol_params.go | 2 ++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/core/vm/gas.go b/core/vm/gas.go index 5fe589bce6..55a6aa163d 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -17,6 +17,7 @@ package vm import ( + "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -52,3 +53,32 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u return callCost.Uint64(), nil } + +// extCallGas returns the actual gas cost for ext*call operations. +// +// EOF v1 includes EIP-150 rules (all but 1/64) with a floor of MIN_RETAINED_GAS (5000) +// and a minimum returned value of MIN_CALLE_GASS (2300). +// There is also no call gas, so all available gas is used. +// +// If the minimum retained gas constraint is violated, zero gas and no error is returned +func extCallGas(availableGas, base uint64) (uint64, error) { + if availableGas < base { + return 0, ErrOutOfGas + } + availableGas = availableGas - base + if availableGas < params.ExtCallMinRetainedGas { + return 0, nil + } + + retainedGas := availableGas / 64 + if retainedGas < params.ExtCallMinRetainedGas { + retainedGas = params.ExtCallMinRetainedGas + } + gas := availableGas - retainedGas + + if gas < params.ExtCallMinCalleeGas { + return 0, nil + } else { + return gas, nil + } +} diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f3a9808251..7531cc5288 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -504,18 +504,84 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - panic("not implemented") + var ( + gas uint64 + transfersValue = !stack.Back(3).IsZero() + address = common.Address(stack.Back(0).Bytes20()) + overflow bool + ) + if transfersValue { + if evm.StateDB.Empty(address) { + gas += params.CallNewAccountGas + } + if evm.chainRules.IsEIP4762 { + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) + if overflow { + return 0, ErrGasUintOverflow + } + } else { + gas += params.CallValueTransferGas + } + } + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + + evm.callGasTemp, err = extCallGas(contract.Gas, gas) + if err != nil { + return 0, err + } + + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + + return gas, nil } func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - panic("not implemented") + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = extCallGas(contract.Gas, gas) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil } + func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - panic("not implemented") + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = extCallGas(contract.Gas, gas) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil } // gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be // deducted in the opcode itself, since it depends on the immediate func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - panic("not implemented") + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + // hashing charge needs to be deducted in the opcode itself, since it depends on the immediate + return gas, nil } diff --git a/params/protocol_params.go b/params/protocol_params.go index 638f58a339..32b0512adb 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -89,6 +89,8 @@ const ( CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. + ExtCallMinRetainedGas uint64 = 5000 // For EXT*CALL this is the minimum gas that the EIP158 1/64th rule must retain + ExtCallMinCalleeGas uint64 = 2300 // For EXT*CALL this is the minimum gas that must be passed to the callee, ignoring 63/64 TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) From ce4a1c51adacce37802c0887f909f08a1ac7d941 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:34:53 +0200 Subject: [PATCH 17/47] core: modify eoa check in statetransition --- core/state_transition.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index cd2c65ebd5..95ca92f290 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "errors" "fmt" "math" @@ -301,10 +302,10 @@ func (st *StateTransition) preCheck() error { } if !msg.SkipFromEOACheck { // Make sure the sender is an EOA - codeHash := st.state.GetCodeHash(msg.From) - if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { + code := st.state.GetCode(msg.From) + if 0 < len(code) && !bytes.HasPrefix(code, []byte{0xef, 0x01, 0x00}) { return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, - msg.From.Hex(), codeHash) + msg.From.Hex(), st.state.GetCodeHash(msg.From)) } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) From 55e2e277bf62fb3b68d48e12453b4ca9754306be Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:40:56 +0200 Subject: [PATCH 18/47] core: fix a bug in state transition --- core/state_transition.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/state_transition.go b/core/state_transition.go index 95ca92f290..bd9ba179e1 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -441,6 +441,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // - reset transient storage(eip 1153) st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + if !contractCreation { + // Increment the nonce for the next transaction + st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) + } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err From 1d5144465f1ac8012177800186b7b3c0b210e100 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 15:41:59 +0200 Subject: [PATCH 19/47] core/vm: fix create rules --- core/vm/evm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 73a3a78b63..f5563a031a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -579,16 +579,16 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu return ret, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, ErrLegacyCode) } // Reject EOF deployment from legacy. - if isInitcodeEOF && hasEOFMagic(ret) { + if !isInitcodeEOF && hasEOFMagic(ret) { return ret, ErrLegacyCode } // Reject code starting with 0xEF if EIP-3541 is enabled. if len(ret) >= 1 && HasEOFByte(ret) { if evm.chainRules.IsPrague && isInitcodeEOF { - // Don't reject EOF contracts after Shanghai + // Don't reject EOF contracts after Prague } else if evm.chainRules.IsLondon { - err = ErrInvalidCode + return ret, ErrInvalidCode } } From 3e56fde23bfbbfa5eed54b88d01fffbb93471367 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 15:48:08 +0200 Subject: [PATCH 20/47] tests: changes needed to pass tests, drop before merging --- cmd/evm/internal/t8ntool/transition.go | 26 ++++++++++++++++++++++++++ tests/state_test_util.go | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index fa052f5954..793539c1da 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -196,6 +196,9 @@ func Transition(ctx *cli.Context) error { if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { return err } + if err := applyEOFChecks(&prestate, chainConfig); err != nil { + return err + } // Run the test and aggregate the result s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer) if err != nil { @@ -285,6 +288,29 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { return nil } +func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error { + // Sanity check pre-allocated EOF code to not panic in state transition. + if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { + for addr, acc := range prestate.Pre { + if vm.HasEOFByte(acc.Code) { + var ( + c vm.Container + err error + ) + err = c.UnmarshalBinary(acc.Code, false) + if err == nil { + jt := vm.NewPragueEOFInstructionSetForTesting() + err = c.ValidateCode(&jt, false) + } + if err != nil { + return NewError(ErrorConfig, fmt.Errorf("code at %s considered invalid: %v", addr, err)) + } + } + } + } + return nil +} + type Alloc map[common.Address]types.Account func (g Alloc) OnRoot(common.Hash) {} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index cf0ce9777f..a03a622ead 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -298,6 +298,13 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if tracer := vmconfig.Tracer; tracer != nil && tracer.OnTxStart != nil { tracer.OnTxStart(evm.GetVMContext(), nil, msg.From) } + + if config.IsPrague(new(big.Int), 0) { + for i := int(block.Number().Uint64() - 1); i >= 0; i-- { + core.ProcessParentBlockHash(vmTestBlockHash(uint64(i)), evm, st.StateDB) + } + } + // Execute the message. snapshot := st.StateDB.Snapshot() gaspool := new(core.GasPool) From d0dfef2eb4b77ec6004d59c1af7348bc820612cc Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 16:29:04 +0200 Subject: [PATCH 21/47] core/vm: fix eofcreate auxdata --- core/vm/evm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index f5563a031a..51f8b92339 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -544,7 +544,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) - ret, err = evm.initNewContract(contract, address, value, isInitcodeEOF) + ret, err = evm.initNewContract(contract, address, value, input, isInitcodeEOF) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { @@ -556,7 +556,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. -func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int, isInitcodeEOF bool) ([]byte, error) { +func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int, input []byte, isInitcodeEOF bool) ([]byte, error) { // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { @@ -564,7 +564,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu } } - ret, err := evm.interpreter.Run(contract, nil, false, contract.IsDeployment) + ret, err := evm.interpreter.Run(contract, input, false, contract.IsDeployment) if err != nil { return ret, err } @@ -627,7 +627,7 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * func (evm *EVM) EOFCreate(caller ContractRef, input []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: subcontainer} contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, input, true) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, EOFCREATE, input, true) } // ChainConfig returns the environment's chain configuration From 1b300b26b27207cbb01e5658deb6f39e4f608c95 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 4 Oct 2024 05:26:38 +0200 Subject: [PATCH 22/47] core/vm: fix rebasing issue --- core/vm/evm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 51f8b92339..19d8ee68e6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -559,7 +559,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int, input []byte, isInitcodeEOF bool) ([]byte, error) { // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { return nil, ErrOutOfGas } } From d2af47a16dc17c60e7fbb503c19bb9df0dc64ba2 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 4 Oct 2024 07:50:57 +0200 Subject: [PATCH 23/47] core/vm: fix rebasing issue --- core/vm/evm.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 19d8ee68e6..70d13d00bd 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -557,13 +557,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int, input []byte, isInitcodeEOF bool) ([]byte, error) { - // Charge the contract creation init gas in verkle mode - if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { - return nil, ErrOutOfGas - } - } - ret, err := evm.interpreter.Run(contract, input, false, contract.IsDeployment) if err != nil { return ret, err From a73a8e785180e93d97490932e9e41c16327d8daa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sat, 5 Oct 2024 02:44:51 +0200 Subject: [PATCH 24/47] happy lint, happy life --- core/vm/evm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 70d13d00bd..bf11ffb7a6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -492,7 +492,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck) } - gas = gas - statelessGas + contract.Gas = contract.Gas - statelessGas } // We add this to the access list _before_ taking a snapshot. Even if the @@ -540,7 +540,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit) } - gas = gas - statelessGas + contract.Gas = contract.Gas - statelessGas } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) From e027042d01a7903f126fef15b088029721b0a0d4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 11 Oct 2024 12:41:09 +0200 Subject: [PATCH 25/47] core: addressed review comments --- cmd/evm/internal/t8ntool/transition.go | 33 +++++++++++++------------- core/state_transition.go | 14 ++++------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 793539c1da..b1fb8e4c7c 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -288,23 +288,24 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { return nil } +// applyEOFChecks does a sanity check of the prestate EOF code validity, to avoid panic during state transition. func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error { - // Sanity check pre-allocated EOF code to not panic in state transition. - if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { - for addr, acc := range prestate.Pre { - if vm.HasEOFByte(acc.Code) { - var ( - c vm.Container - err error - ) - err = c.UnmarshalBinary(acc.Code, false) - if err == nil { - jt := vm.NewPragueEOFInstructionSetForTesting() - err = c.ValidateCode(&jt, false) - } - if err != nil { - return NewError(ErrorConfig, fmt.Errorf("code at %s considered invalid: %v", addr, err)) - } + if !chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { + return nil + } + for addr, acc := range prestate.Pre { + if vm.HasEOFByte(acc.Code) { + var ( + c vm.Container + err error + ) + err = c.UnmarshalBinary(acc.Code, false) + if err == nil { + jt := vm.NewPragueEOFInstructionSetForTesting() + err = c.ValidateCode(&jt, false) + } + if err != nil { + return NewError(ErrorConfig, fmt.Errorf("code at %s considered invalid: %v", addr, err)) } } } diff --git a/core/state_transition.go b/core/state_transition.go index bd9ba179e1..ad1248a574 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,7 +17,6 @@ package core import ( - "bytes" "errors" "fmt" "math" @@ -303,9 +302,9 @@ func (st *StateTransition) preCheck() error { if !msg.SkipFromEOACheck { // Make sure the sender is an EOA code := st.state.GetCode(msg.From) - if 0 < len(code) && !bytes.HasPrefix(code, []byte{0xef, 0x01, 0x00}) { - return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, - msg.From.Hex(), st.state.GetCodeHash(msg.From)) + if len(code) > 0 { + return fmt.Errorf("%w: address %v, codeLen: %d", ErrSenderNoEOA, + msg.From.Hex(), len(code)) } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) @@ -441,11 +440,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // - reset transient storage(eip 1153) st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) - if !contractCreation { - // Increment the nonce for the next transaction - st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) - } - var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err @@ -460,6 +454,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) } } 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, value) } From 8b40d4e36f2cf08923b8a8c9e0fa40d331df8259 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 11 Oct 2024 14:15:48 +0200 Subject: [PATCH 26/47] core: fix new error string --- core/state_processor_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 9678f2828c..b332762813 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -342,7 +342,7 @@ func TestStateProcessorErrors(t *testing.T) { txs: []*types.Transaction{ mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), }, - want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1", + want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codeLen: 4", }, } { block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) From 1b9940486ee0e8e2e9d4ab8f77382fa1894ace6e Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Tue, 15 Oct 2024 10:11:44 -0600 Subject: [PATCH 27/47] Restore state test transction context In state tests when running prague system contracts the origin remains as the system account. Restore the prior tx context after running system contracts. --- core/state_processor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/state_processor.go b/core/state_processor.go index c4e2bcf1e5..54b98a1b44 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -249,6 +249,7 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state. } } + oldContext := vmenv.TxContext msg := &Message{ From: params.SystemAddress, GasLimit: 30_000_000, @@ -262,6 +263,7 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state. statedb.AddAddressToAccessList(params.HistoryStorageAddress) _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) statedb.Finalise(true) + vmenv.Reset(oldContext, statedb) } // ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by From ff7ac5134824ee62b5514947b0adc55c03a2458d Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Tue, 15 Oct 2024 15:01:27 -0600 Subject: [PATCH 28/47] limit scope to state tests --- core/state_processor.go | 2 -- tests/state_test_util.go | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 54b98a1b44..c4e2bcf1e5 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -249,7 +249,6 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state. } } - oldContext := vmenv.TxContext msg := &Message{ From: params.SystemAddress, GasLimit: 30_000_000, @@ -263,7 +262,6 @@ func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state. statedb.AddAddressToAccessList(params.HistoryStorageAddress) _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) statedb.Finalise(true) - vmenv.Reset(oldContext, statedb) } // ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by diff --git a/tests/state_test_util.go b/tests/state_test_util.go index a03a622ead..1165463b02 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -299,11 +299,13 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh tracer.OnTxStart(evm.GetVMContext(), nil, msg.From) } + oldContext := evm.TxContext if config.IsPrague(new(big.Int), 0) { for i := int(block.Number().Uint64() - 1); i >= 0; i-- { core.ProcessParentBlockHash(vmTestBlockHash(uint64(i)), evm, st.StateDB) } } + evm.Reset(oldContext, st.StateDB) // Execute the message. snapshot := st.StateDB.Snapshot() From 257e27c8edfcc7dda319bfa32bb95715e183d930 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Wed, 16 Oct 2024 12:34:24 -0600 Subject: [PATCH 29/47] Move EOF activation to Osaka Add support for the Osaka fork and move all EOF activation and side effects to that fork. --- cmd/evm/eofparse.go | 2 +- cmd/evm/eofparse_test.go | 2 +- cmd/evm/internal/t8ntool/transition.go | 2 +- core/genesis_test.go | 1 + core/state_transition.go | 2 +- core/vm/eof_validation_test.go | 16 +++++----- core/vm/evm.go | 6 ++-- core/vm/interpreter.go | 2 +- core/vm/jump_table.go | 8 ++--- core/vm/jump_table_export.go | 2 ++ core/vm/runtime/runtime.go | 2 +- eth/tracers/api.go | 4 +++ params/config.go | 24 ++++++++++++-- params/forks/forks.go | 1 + tests/init.go | 44 ++++++++++++++++++++++++++ 15 files changed, 95 insertions(+), 23 deletions(-) diff --git a/cmd/evm/eofparse.go b/cmd/evm/eofparse.go index 2122270942..06525672d3 100644 --- a/cmd/evm/eofparse.go +++ b/cmd/evm/eofparse.go @@ -32,7 +32,7 @@ import ( ) func init() { - jt = vm.NewPragueEOFInstructionSetForTesting() + jt = vm.NewOsakaEOFInstructionSetForTesting() } var ( diff --git a/cmd/evm/eofparse_test.go b/cmd/evm/eofparse_test.go index 9b17039f5b..d2ca38283d 100644 --- a/cmd/evm/eofparse_test.go +++ b/cmd/evm/eofparse_test.go @@ -43,7 +43,7 @@ func FuzzEofParsing(f *testing.F) { // And do the fuzzing f.Fuzz(func(t *testing.T, data []byte) { var ( - jt = vm.NewPragueEOFInstructionSetForTesting() + jt = vm.NewOsakaEOFInstructionSetForTesting() c vm.Container ) cpy := common.CopyBytes(data) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index b1fb8e4c7c..1ce1764c37 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -301,7 +301,7 @@ func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error { ) err = c.UnmarshalBinary(acc.Code, false) if err == nil { - jt := vm.NewPragueEOFInstructionSetForTesting() + jt := vm.NewOsakaEOFInstructionSetForTesting() err = c.ValidateCode(&jt, false) } if err != nil { diff --git a/core/genesis_test.go b/core/genesis_test.go index 0fee874138..685617945b 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -277,6 +277,7 @@ func TestVerkleGenesisCommit(t *testing.T) { ShanghaiTime: &verkleTime, CancunTime: &verkleTime, PragueTime: &verkleTime, + OsakaTime: &verkleTime, VerkleTime: &verkleTime, TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, diff --git a/core/state_transition.go b/core/state_transition.go index ad1248a574..a927fb074b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -445,7 +445,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) if contractCreation { - ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value, rules.IsPrague) + ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value, rules.IsOsaka) // Special case for EOF, if the initcode or deployed code is // invalid, the tx is considered valid (so update nonce), but // gas for initcode execution is not consumed. diff --git a/core/vm/eof_validation_test.go b/core/vm/eof_validation_test.go index 6680ca3a5d..4ce8d6fc80 100644 --- a/core/vm/eof_validation_test.go +++ b/core/vm/eof_validation_test.go @@ -251,7 +251,7 @@ func TestValidateCode(t *testing.T) { data: make([]byte, 0), subContainers: make([]*Container, 0), } - _, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet, false) + _, err := validateCode(test.code, test.section, container, &osakaEOFInstructionSet, false) if !errors.Is(err, test.err) { t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err) } @@ -277,7 +277,7 @@ func BenchmarkRJUMPI(b *testing.B) { } b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := validateCode(code, 0, container, &pragueEOFInstructionSet, false) + _, err := validateCode(code, 0, container, &osakaEOFInstructionSet, false) if err != nil { b.Fatal(err) } @@ -309,7 +309,7 @@ func BenchmarkRJUMPV(b *testing.B) { } b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := validateCode(code, 0, container, &pragueEOFInstructionSet, false) + _, err := validateCode(code, 0, container, &osakaEOFInstructionSet, false) if err != nil { b.Fatal(err) } @@ -357,7 +357,7 @@ func BenchmarkEOFValidation(b *testing.B) { if err := container2.UnmarshalBinary(bin, true); err != nil { b.Fatal(err) } - if err := container2.ValidateCode(&pragueEOFInstructionSet, false); err != nil { + if err := container2.ValidateCode(&osakaEOFInstructionSet, false); err != nil { b.Fatal(err) } } @@ -412,7 +412,7 @@ func BenchmarkEOFValidation2(b *testing.B) { if err := container2.UnmarshalBinary(bin, true); err != nil { b.Fatal(err) } - if err := container2.ValidateCode(&pragueEOFInstructionSet, false); err != nil { + if err := container2.ValidateCode(&osakaEOFInstructionSet, false); err != nil { b.Fatal(err) } } @@ -468,7 +468,7 @@ func BenchmarkEOFValidation3(b *testing.B) { if err := container2.UnmarshalBinary(bin, true); err != nil { b.Fatal(err) } - if err := container2.ValidateCode(&pragueEOFInstructionSet, false); err != nil { + if err := container2.ValidateCode(&osakaEOFInstructionSet, false); err != nil { b.Fatal(err) } } @@ -494,7 +494,7 @@ func BenchmarkRJUMPI_2(b *testing.B) { } b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := validateCode(code, 0, container, &pragueEOFInstructionSet, false) + _, err := validateCode(code, 0, container, &osakaEOFInstructionSet, false) if err != nil { b.Fatal(err) } @@ -512,6 +512,6 @@ func FuzzValidate(f *testing.F) { f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) { var container Container container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack}) - validateCode(code, 0, &container, &pragueEOFInstructionSet, true) + validateCode(code, 0, &container, &osakaEOFInstructionSet, true) }) } diff --git a/core/vm/evm.go b/core/vm/evm.go index bf11ffb7a6..62e0eb61f6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -578,8 +578,8 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu // Reject code starting with 0xEF if EIP-3541 is enabled. if len(ret) >= 1 && HasEOFByte(ret) { - if evm.chainRules.IsPrague && isInitcodeEOF { - // Don't reject EOF contracts after Prague + if evm.chainRules.IsOsaka && isInitcodeEOF { + // Don't reject EOF contracts after Osaka } else if evm.chainRules.IsLondon { return ret, ErrInvalidCode } @@ -669,7 +669,7 @@ func (evm *EVM) GetVMContext() *tracing.VMContext { // parseContainer tries to parse an EOF container if the Shanghai fork is active. It expects the code to already be validated. func (evm *EVM) parseContainer(b []byte) *Container { - if evm.chainRules.IsPrague { + if evm.chainRules.IsOsaka { var c Container if err := c.UnmarshalBinary(b, false); err != nil && strings.HasPrefix(err.Error(), "invalid magic") { return nil diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 42b2483ac1..5a96c11f79 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -175,7 +175,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { } } evm.Config.ExtraEips = extraEips - return &EVMInterpreter{evm: evm, table: table, tableEOF: &pragueEOFInstructionSet} + return &EVMInterpreter{evm: evm, table: table, tableEOF: &osakaEOFInstructionSet} } // Run loops and evaluates the contract's code with the given input data and returns diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 658014f24c..45e01b4bcb 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -60,8 +60,8 @@ var ( mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() cancunInstructionSet = newCancunInstructionSet() + osakaEOFInstructionSet = newOsakaEOFInstructionSet() verkleInstructionSet = newVerkleInstructionSet() - pragueEOFInstructionSet = newPragueEOFInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -91,11 +91,11 @@ func newVerkleInstructionSet() JumpTable { return validate(instructionSet) } -func NewPragueEOFInstructionSetForTesting() JumpTable { - return newPragueEOFInstructionSet() +func NewOsakaEOFInstructionSetForTesting() JumpTable { + return newOsakaEOFInstructionSet() } -func newPragueEOFInstructionSet() JumpTable { +func newOsakaEOFInstructionSet() JumpTable { instructionSet := newCancunInstructionSet() enableEOF(&instructionSet) return validate(instructionSet) diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index b74109da0a..731530190b 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) { switch { case rules.IsVerkle: return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") + case rules.IsOsaka: + return newCancunInstructionSet(), errors.New("osaka-fork not defined yet") case rules.IsPrague: return newCancunInstructionSet(), errors.New("prague-fork not defined yet") case rules.IsCancun: diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 84e21ef358..f68126df18 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -184,7 +184,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { input, cfg.GasLimit, uint256.MustFromBig(cfg.Value), - rules.IsPrague, + rules.IsOsaka, ) return code, address, leftOverGas, err } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 189afa48d4..21a4864018 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1098,6 +1098,10 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) copy.PragueTime = timestamp canon = false } + if timestamp := override.OsakaTime; timestamp != nil { + copy.OsakaTime = timestamp + canon = false + } if timestamp := override.VerkleTime; timestamp != nil { copy.VerkleTime = timestamp canon = false diff --git a/params/config.go b/params/config.go index 9ecf465bb6..58716c1f2f 100644 --- a/params/config.go +++ b/params/config.go @@ -134,6 +134,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + OsakaTime: nil, VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: true, @@ -185,6 +186,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + OsakaTime: nil, VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: false, @@ -215,6 +217,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + OsakaTime: nil, VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: false, @@ -245,6 +248,7 @@ var ( ShanghaiTime: newUint64(0), CancunTime: newUint64(0), PragueTime: nil, + OsakaTime: nil, VerkleTime: nil, TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, @@ -274,7 +278,7 @@ var ( MergeNetsplitBlock: nil, ShanghaiTime: nil, CancunTime: nil, - PragueTime: nil, + OsakaTime: nil, VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: false, @@ -325,6 +329,7 @@ type ChainConfig struct { ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) + OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka) VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) // TerminalTotalDifficulty is the amount of total difficulty reached by @@ -450,6 +455,9 @@ func (c *ChainConfig) Description() string { if c.PragueTime != nil { banner += fmt.Sprintf(" - Prague: @%-10v\n", *c.PragueTime) } + if c.OsakaTime != nil { + banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime) + } if c.VerkleTime != nil { banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) } @@ -551,6 +559,11 @@ func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) } +// IsOsaka returns whether time is either equal to the Prague fork time or greater. +func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time) +} + // IsVerkle returns whether time is either equal to the Verkle fork time or greater. func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) @@ -615,6 +628,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "shanghaiTime", timestamp: c.ShanghaiTime}, {name: "cancunTime", timestamp: c.CancunTime, optional: true}, {name: "pragueTime", timestamp: c.PragueTime, optional: true}, + {name: "osakaTime", timestamp: c.OsakaTime, optional: true}, {name: "verkleTime", timestamp: c.VerkleTime, optional: true}, } { if lastFork.name != "" { @@ -719,6 +733,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.PragueTime, newcfg.PragueTime, headTimestamp) { return newTimestampCompatError("Prague fork timestamp", c.PragueTime, newcfg.PragueTime) } + if isForkTimestampIncompatible(c.OsakaTime, newcfg.OsakaTime, headTimestamp) { + return newTimestampCompatError("Osaka fork timestamp", c.OsakaTime, newcfg.OsakaTime) + } if isForkTimestampIncompatible(c.VerkleTime, newcfg.VerkleTime, headTimestamp) { return newTimestampCompatError("Verkle fork timestamp", c.VerkleTime, newcfg.VerkleTime) } @@ -741,6 +758,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork { london := c.LondonBlock switch { + case c.IsOsaka(london, time): + return forks.Osaka case c.IsPrague(london, time): return forks.Prague case c.IsCancun(london, time): @@ -892,7 +911,7 @@ type Rules struct { IsEIP2929, IsEIP4762 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool - IsMerge, IsShanghai, IsCancun, IsPrague bool + IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool IsVerkle bool } @@ -922,6 +941,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), + IsOsaka: isMerge && c.IsOsaka(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, } diff --git a/params/forks/forks.go b/params/forks/forks.go index 4f50ff5aed..2d44e13b04 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -39,4 +39,5 @@ const ( Shanghai Cancun Prague + Osaka ) diff --git a/tests/init.go b/tests/init.go index 4bb83f9300..8429f38e44 100644 --- a/tests/init.go +++ b/tests/init.go @@ -396,6 +396,50 @@ var Forks = map[string]*params.ChainConfig{ PragueTime: u64(15_000), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, }, + "Osaka": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + }, + "PragueToOsakaAtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(15_000), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + }, } // AvailableForks returns the set of defined fork names From 1156ca42f21d8bc4b439572003a5a87f90410938 Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Fri, 25 Oct 2024 12:27:23 -0600 Subject: [PATCH 30/47] Limit EXTCODECOPY to eof bytes When copying EOF contracts, make sure EXTCODECOPY only has access to the EOF magic, not just two bytes total. --- core/vm/instructions.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index c03322920a..6b31270ad7 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -395,6 +395,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) codeOffset = stack.pop() length = stack.pop() lengthU64 = length.Uint64() + codeCopy []byte ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { @@ -406,9 +407,10 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) witness.AddCode(code) } if isEOFVersion1(code) { - lengthU64 = 2 + codeCopy = getData(eofMagic, uint64CodeOffset, lengthU64) + } else { + codeCopy = getData(code, uint64CodeOffset, lengthU64) } - codeCopy := getData(code, uint64CodeOffset, lengthU64) scope.Memory.Set(memOffset.Uint64(), lengthU64, codeCopy) return nil, nil From 6fe254e378abaf5ef7de286de00f91fdf4b9b159 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 4 Feb 2025 10:20:10 +0100 Subject: [PATCH 31/47] all: fix merging issues --- cmd/evm/internal/t8ntool/transition.go | 4 +-- core/vm/eof_instructions.go | 2 +- core/vm/instructions_test.go | 48 ++++++++++++-------------- core/vm/interpreter.go | 2 +- eth/tracers/js/tracer_test.go | 2 +- eth/tracers/logger/logger_test.go | 4 +-- tests/state_test_util.go | 9 ----- 7 files changed, 30 insertions(+), 41 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 0d358b8be6..24997def03 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -152,7 +152,7 @@ func Transition(ctx *cli.Context) error { } if err := applyEOFChecks(&prestate, chainConfig); err != nil { return err - } + } // Configure tracer if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing @@ -281,7 +281,7 @@ func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error { ) err = c.UnmarshalBinary(acc.Code, false) if err == nil { - jt := vm.NewPragueEOFInstructionSetForTesting() + jt := vm.NewEOFInstructionSetForTesting() err = c.ValidateCode(&jt, false) } if err != nil { diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index c06071f6d5..a0ed9ada83 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -20,9 +20,9 @@ import ( "encoding/binary" "errors" "fmt" + "math" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 091dc013ea..c3c28f23f1 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -115,7 +115,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) + opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -229,7 +229,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) + opAddmod(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -255,7 +255,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) + opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -289,10 +289,9 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( - env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack = newstack() - scope = &ScopeContext{nil, stack, nil, 0, nil, false} - evmInterpreter = NewEVMInterpreter(env) + evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + scope = &ScopeContext{nil, stack, nil, 0, nil, false} ) // convert args intArgs := make([]*uint256.Int, len(args)) @@ -537,13 +536,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -564,23 +563,22 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } func TestOpTstore(t *testing.T) { var ( - statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) - env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) - stack = newstack() - mem = NewMemory() - evmInterpreter = NewEVMInterpreter(env) - caller = common.Address{} - to = common.Address{1} - contractRef = contractRef{caller} - contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0) - scopeContext = ScopeContext{mem, stack, contract, 0, nil, false} - value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + evm = NewEVM(BlockContext{}, statedb, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + caller = common.Address{} + to = common.Address{1} + contractRef = contractRef{caller} + contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0) + scopeContext = ScopeContext{mem, stack, contract, 0, nil, false} + value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) // Add a stateObject for the caller and the contract being called @@ -624,7 +622,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) + opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -718,7 +716,7 @@ func TestRandom(t *testing.T) { stack = newstack() pc = uint64(0) ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) + opRandom(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -760,7 +758,7 @@ func TestBlobHash(t *testing.T) { ) evm.SetTxContext(TxContext{BlobHashes: tt.hashes}) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) + opBlobHash(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -900,7 +898,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) + opMcopy(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 8b01c68139..ea08500de0 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -177,7 +177,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { } } evm.Config.ExtraEips = extraEips - return &EVMInterpreter{evm: evm, table: table, tableEOF: &pragueEOFInstructionSet} + return &EVMInterpreter{evm: evm, table: table, tableEOF: &eofInstructionSet} } // Run loops and evaluates the contract's code with the given input data and returns diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 4c6f6590f0..e833572d1f 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -78,7 +78,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice}), contract.Caller()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) - ret, err := env.Interpreter().Run(contract, []byte{}, false, false) + ret, err := evm.Interpreter().Run(contract, []byte{}, false, false) tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index fd58359239..a84c9dc89c 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -63,8 +63,8 @@ func TestStoreCapture(t *testing.T) { ) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash - logger.OnTxStart(env.GetVMContext(), nil, common.Address{}) - _, err := env.Interpreter().Run(contract, []byte{}, false, false) + logger.OnTxStart(evm.GetVMContext(), nil, common.Address{}) + _, err := evm.Interpreter().Run(contract, []byte{}, false, false) if err != nil { t.Fatal(err) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 9483f40d41..e658b62ebf 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -319,15 +319,6 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if tracer := vmconfig.Tracer; tracer != nil && tracer.OnTxStart != nil { tracer.OnTxStart(evm.GetVMContext(), nil, msg.From) } - - oldContext := evm.TxContext - if config.IsPrague(new(big.Int), 0) { - for i := int(block.Number().Uint64() - 1); i >= 0; i-- { - core.ProcessParentBlockHash(vmTestBlockHash(uint64(i)), evm, st.StateDB) - } - } - evm.Reset(oldContext, st.StateDB) - // Execute the message. snapshot := st.StateDB.Snapshot() gaspool := new(core.GasPool) From 1c931f0f8fd384733b8b9897bec3bcf851d46f23 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 4 Feb 2025 10:26:34 +0100 Subject: [PATCH 32/47] core/vm: revert merge errors --- core/vm/evm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 1678ab9fe2..ee0be3b454 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -227,7 +227,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract.IsSystemCall = isSystemCall(caller) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) + contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } @@ -288,7 +288,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) code := evm.StateDB.GetCode(addrCopy) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) + contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } @@ -339,7 +339,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by } // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) + contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), code, evm.parseContainer(code)) ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } @@ -395,7 +395,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) code := evm.StateDB.GetCode(addrCopy) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, evm.parseContainer(code)) + contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), code, evm.parseContainer(code)) // 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. From 10cd083c78f5a2152c9512fb1cdbae9ec95e5f36 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 4 Feb 2025 10:31:42 +0100 Subject: [PATCH 33/47] core/vm: revert merge errors --- core/vm/instructions.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index dca9c57b9f..e3af3cb217 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -352,10 +352,6 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() - address := slot.Bytes20() - if witness := interpreter.evm.StateDB.Witness(); witness != nil { - witness.AddCode(interpreter.evm.StateDB.GetCode(address)) - } // TODO this should not need to pull up the whole code code := interpreter.evm.StateDB.GetCode(slot.Bytes20()) if isEOFVersion1(code) { @@ -403,9 +399,6 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } addr := common.Address(a.Bytes20()) code := interpreter.evm.StateDB.GetCode(addr) - if witness := interpreter.evm.StateDB.Witness(); witness != nil { - witness.AddCode(code) - } if isEOFVersion1(code) { codeCopy = getData(eofMagic, uint64CodeOffset, lengthU64) } else { From 30b642bc5155ca17f9c446193875cb7ed534042c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 4 Feb 2025 10:41:19 +0100 Subject: [PATCH 34/47] applied suggestion --- core/state_transition.go | 2 +- core/vm/evm.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 57e0293867..aa6a946a74 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,8 +17,8 @@ package core import ( - "errors" "bytes" + "errors" "fmt" "math" "math/big" diff --git a/core/vm/evm.go b/core/vm/evm.go index ee0be3b454..6123f3e447 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -485,7 +485,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractCollisionCheck) } - contract.Gas = contract.Gas - statelessGas + gas = gas - statelessGas } // We add this to the access list _before_ taking a snapshot. Even if the @@ -533,9 +533,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit) } - contract.Gas = contract.Gas - statelessGas + gas = gas - statelessGas } evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) + contract.Gas = gas ret, err = evm.initNewContract(contract, address, value, input, isInitcodeEOF) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { From 1846cc9ef48ce364da57839c83f2d54f92d8d389 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 7 Feb 2025 09:28:50 +0100 Subject: [PATCH 35/47] core/tracing: add InvalidEoF hook --- core/state_transition.go | 2 +- core/tracing/hooks.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/state_transition.go b/core/state_transition.go index ee5ec85a27..bbc94ed0a3 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -491,7 +491,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // gas for initcode execution is not consumed. // Only intrinsic creation transaction costs are charged. if errors.Is(vmerr, vm.ErrInvalidEOFInitcode) { - st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) + st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1, tracing.NonceChangeInvalidEOF) } } else { // Increment the nonce for the next transaction. diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 4002b57207..68bfb2fedc 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -367,4 +367,8 @@ const ( // NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure. // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). NonceChangeRevert NonceChangeReason = 6 + + // NonceChangeInvalidEOF is emitted when the nonce is changed when a new contract is created, + // but the creation fails because of an EOF error. + NonceChangeInvalidEOF NonceChangeReason = 6 ) From b0dedd720bb2aca01c14943b0706a2a1f7a93fb9 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 7 Feb 2025 09:32:52 +0100 Subject: [PATCH 36/47] tests: fixup --- tests/init.go | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/tests/init.go b/tests/init.go index 428fd0ed33..ee8d354224 100644 --- a/tests/init.go +++ b/tests/init.go @@ -454,50 +454,6 @@ var Forks = map[string]*params.ChainConfig{ OsakaTime: u64(15_000), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, }, - "Osaka": { - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - ArrowGlacierBlock: big.NewInt(0), - MergeNetsplitBlock: big.NewInt(0), - TerminalTotalDifficulty: big.NewInt(0), - ShanghaiTime: u64(0), - CancunTime: u64(0), - PragueTime: u64(0), - OsakaTime: u64(0), - DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, - }, - "PragueToOsakaAtTime15k": { - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - ArrowGlacierBlock: big.NewInt(0), - MergeNetsplitBlock: big.NewInt(0), - TerminalTotalDifficulty: big.NewInt(0), - ShanghaiTime: u64(0), - CancunTime: u64(0), - PragueTime: u64(0), - OsakaTime: u64(15_000), - DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, - }, } // AvailableForks returns the set of defined fork names From 5bfacf4ba2e8695ec9993cc1ce79597e1143b8ff Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Mon, 14 Oct 2024 16:38:35 -0600 Subject: [PATCH 37/47] Move EOF interpreter to use same PC semantics as legacy EVM Update the EOF container parsing to allow the PC to use the same semantics as the legacy EVM. Also, a new test container maker is necessary to handle the particulars of the unit tests, mostly unrelated to --- core/vm/contract.go | 13 +--- core/vm/eof.go | 107 +++++++++++++++++++++++---------- core/vm/eof_instructions.go | 66 +++++++++----------- core/vm/eof_test.go | 88 +++++++++++++++++++-------- core/vm/eof_validation.go | 8 +-- core/vm/eof_validation_test.go | 104 ++++++++++++++++++-------------- core/vm/instructions.go | 14 ++--- core/vm/interpreter.go | 5 +- statetest | Bin 0 -> 16 bytes 9 files changed, 241 insertions(+), 164 deletions(-) create mode 100644 statetest diff --git a/core/vm/contract.go b/core/vm/contract.go index 8ab4cb8f47..dd53f934c5 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -146,10 +146,8 @@ func (c *Contract) AsDelegate() *Contract { } // GetOp returns the n'th element in the contract's byte array -func (c *Contract) GetOp(n uint64, s uint64) OpCode { - if c.IsEOF() && n < uint64(len(c.Container.codeSections[s])) { - return OpCode(c.Container.codeSections[s][n]) - } else if n < uint64(len(c.Code)) { +func (c *Contract) GetOp(n uint64) OpCode { + if n < uint64(len(c.Code)) { return OpCode(c.Code[n]) } return STOP @@ -201,13 +199,6 @@ func (c *Contract) IsEOF() bool { return c.Container != nil } -func (c *Contract) CodeAt(section uint64) []byte { - if c.Container == nil { - return c.Code - } - return c.Container.codeSections[section] -} - // SetCallCode sets the code of the contract and address of the backing data // object func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, container *Container) { diff --git a/core/vm/eof.go b/core/vm/eof.go index a5406283d5..2bef6f0a75 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -66,12 +66,15 @@ func isEOFVersion1(code []byte) bool { // Container is an EOF container object. type Container struct { - types []*functionMetadata - codeSections [][]byte - subContainers []*Container - subContainerCodes [][]byte - data []byte - dataSize int // might be more than len(data) + types []*functionMetadata + codeSectionOffsets []int + codeSectionEnd int + subContainers []*Container + subContainerOffsets []int + subContainerEnd int + dataOffest int + dataSize int // might be more than len(data) + rawContainer []byte } // functionMetadata is an EOF function signature. @@ -105,6 +108,46 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error { return nil } +func (c *Container) codeSectionSize(s int) int { + if s >= len(c.codeSectionOffsets) || s < 0 { + return 0 + } else if s == len(c.codeSectionOffsets)-1 { + return c.codeSectionEnd - c.codeSectionOffsets[s] + } + return c.codeSectionOffsets[s+1] - c.codeSectionOffsets[s] +} + +func (c *Container) codeSectionBytes(s int) []byte { + if s >= len(c.codeSectionOffsets) || s < 0 { + return c.rawContainer[0:0] + } else if s == len(c.codeSectionOffsets)-1 { + return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionEnd] + } + return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionOffsets[s+1]] +} + +func (c *Container) subContainerSize(s int) int { + if s >= len(c.subContainerOffsets) || s < 0 { + return 0 + } else if s == len(c.subContainerOffsets)-1 { + return c.subContainerEnd - c.subContainerOffsets[s] + } + return c.subContainerOffsets[s+1] - c.subContainerOffsets[s] +} + +func (c *Container) subContainerBytes(s int) []byte { + if s >= len(c.subContainerOffsets) || s < 0 { + return c.rawContainer[0:0] + } else if s == len(c.subContainerOffsets)-1 { + return c.rawContainer[c.subContainerOffsets[s]:c.subContainerEnd] + } + return c.rawContainer[c.subContainerOffsets[s]:c.subContainerOffsets[s+1]] +} + +func (c *Container) dataLen() int { + return len(c.rawContainer) - c.dataOffest +} + // MarshalBinary encodes an EOF container into binary format. func (c *Container) MarshalBinary() []byte { // Build EOF prefix. @@ -116,9 +159,9 @@ func (c *Container) MarshalBinary() []byte { b = append(b, kindTypes) b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4)) b = append(b, kindCode) - b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections))) - for _, codeSection := range c.codeSections { - b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection))) + b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionOffsets))) + for s := range c.codeSectionOffsets { + b = binary.BigEndian.AppendUint16(b, uint16(c.codeSectionSize(s))) } var encodedContainer [][]byte if len(c.subContainers) != 0 { @@ -138,13 +181,13 @@ func (c *Container) MarshalBinary() []byte { for _, ty := range c.types { b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...) } - for _, code := range c.codeSections { - b = append(b, code...) + for s := range c.codeSectionOffsets { + b = append(b, c.codeSectionBytes(s)...) } for _, section := range encodedContainer { b = append(b, section...) } - b = append(b, c.data...) + b = append(b, c.rawContainer[c.dataOffest:]...) return b } @@ -282,21 +325,22 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) // Parse code sections. idx += typesSize - codeSections := make([][]byte, len(codeSizes)) + codeSectionOffsets := make([]int, len(codeSizes)) for i, size := range codeSizes { if size == 0 { return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i) } - codeSections[i] = b[idx : idx+size] + codeSectionOffsets[i] = idx idx += size } - c.codeSections = codeSections + c.codeSectionOffsets = codeSectionOffsets + c.codeSectionEnd = idx // Parse the optional container sizes. if len(containerSizes) != 0 { if len(containerSizes) > maxContainerSections { return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes)) } - subContainerCodes := make([][]byte, 0, len(containerSizes)) + subContainerOffsets := make([]int, 0, len(containerSizes)) subContainers := make([]*Container, 0, len(containerSizes)) for i, size := range containerSizes { if size == 0 || idx+size > len(b) { @@ -311,23 +355,22 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) return err } subContainers = append(subContainers, subC) - subContainerCodes = append(subContainerCodes, b[idx:end]) + subContainerOffsets = append(subContainerOffsets, idx) idx += size } c.subContainers = subContainers - c.subContainerCodes = subContainerCodes + c.subContainerEnd = idx + c.subContainerOffsets = subContainerOffsets } //Parse data section. - end := len(b) - if !isInitcode { - end = min(idx+dataSize, len(b)) - } if topLevel && len(b) != idx+dataSize { return errTruncatedTopLevelContainer } - c.data = b[idx:end] + c.dataOffest = idx + + c.rawContainer = b return nil } @@ -355,7 +398,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error { // should not mean 2 and 3 should be visited twice var ( index = toVisit[0] - code = c.codeSections[index] + code = c.codeSectionBytes(index) ) if _, ok := visited[index]; !ok { res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate) @@ -387,7 +430,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error { toVisit = toVisit[1:] } // Make sure every code section is visited at least once. - if len(visited) != len(c.codeSections) { + if len(visited) != len(c.codeSectionOffsets) { return errUnreachableCode } for idx, container := range c.subContainers { @@ -472,11 +515,11 @@ func (c *Container) String() string { fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4), fmt.Sprintf(" - KindCode: %02x", kindCode), fmt.Sprintf(" - KindData: %02x", kindData), - fmt.Sprintf(" - DataSize: %04x", len(c.data)), - fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)), + fmt.Sprintf(" - DataSize: %04x", c.dataLen()), + fmt.Sprintf(" - Number of code sections: %d", len(c.codeSectionOffsets)), } - for i, code := range c.codeSections { - output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code))) + for i := range c.codeSectionOffsets { + output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, c.codeSectionSize(i))) } output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers))) @@ -490,12 +533,12 @@ func (c *Container) String() string { output = append(output, fmt.Sprintf(" - Type %v: %x", i, []byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)})) } - for i, code := range c.codeSections { - output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code)) + for i := range c.codeSectionOffsets { + output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionBytes(i))) } for i, section := range c.subContainers { output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary())) } - output = append(output, fmt.Sprintf(" - Data: %#x", c.data)) + output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffest:])) return strings.Join(output, "\n") } diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index a0ed9ada83..4fc5c2c568 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -31,8 +31,7 @@ import ( // opRjump implements the RJUMP opcode. func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - offset = parseInt16(code[*pc+1:]) + offset = parseInt16(scope.Contract.Code[*pc+1:]) ) // move pc past op and operand (+3), add relative offset, subtract 1 to // account for interpreter loop. @@ -54,8 +53,7 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // opRjumpv implements the RJUMPV opcode func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - maxIndex = uint64(code[*pc+1]) + 1 + maxIndex = uint64(scope.Contract.Code[*pc+1]) + 1 idx = scope.Stack.pop() ) if idx, overflow := idx.Uint64WithOverflow(); overflow || idx >= maxIndex { @@ -64,7 +62,7 @@ func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b *pc += 1 + maxIndex*2 return nil, nil } - offset := parseInt16(code[*pc+2+2*idx.Uint64():]) + offset := parseInt16(scope.Contract.Code[*pc+2+2*idx.Uint64():]) // move pc past op and count byte (2), move past count number of 16bit offsets (count*2), add relative offset, subtract 1 to // account for interpreter loop. *pc = uint64(int64(*pc+2+maxIndex*2) + int64(offset) - 1) @@ -74,9 +72,8 @@ func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // opCallf implements the CALLF opcode func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - idx = binary.BigEndian.Uint16(code[*pc+1:]) - typ = scope.Contract.Container.types[idx] + idx = binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:]) + typ = scope.Contract.Container.types[idx] ) if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 { return nil, fmt.Errorf("stack overflow") @@ -91,7 +88,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } scope.ReturnStack = append(scope.ReturnStack, retCtx) scope.CodeSection = uint64(idx) - *pc = uint64(math.MaxUint64) + *pc = uint64(scope.Contract.Container.codeSectionOffsets[idx]) - 1 return nil, nil } @@ -111,15 +108,14 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // opJumpf implements the JUMPF opcode func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - idx = binary.BigEndian.Uint16(code[*pc+1:]) - typ = scope.Contract.Container.types[idx] + idx = binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:]) + typ = scope.Contract.Container.types[idx] ) if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 { return nil, fmt.Errorf("stack overflow") } scope.CodeSection = uint64(idx) - *pc = uint64(math.MaxUint64) + *pc = uint64(scope.Contract.Container.codeSectionOffsets[idx]) - 1 return nil, nil } @@ -129,20 +125,19 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( return nil, ErrWriteProtection } var ( - code = scope.Contract.CodeAt(scope.CodeSection) - idx = code[*pc+1] + idx = scope.Contract.Code[*pc+1] value = scope.Stack.pop() salt = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) ) - if int(idx) >= len(scope.Contract.Container.subContainerCodes) { + if int(idx) >= len(scope.Contract.Container.subContainerOffsets) { return nil, fmt.Errorf("invalid subcontainer") } // Deduct hashing charge // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow - hashingCharge := (params.Keccak256WordGas) * ((uint64(len(scope.Contract.Container.subContainerCodes[idx])) + 31) / 32) + hashingCharge := (params.Keccak256WordGas) * ((uint64(scope.Contract.Container.subContainerSize(int(idx))) + 31) / 32) if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok { return nil, ErrGasUintOverflow } @@ -159,7 +154,7 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) // Skip the immediate *pc += 1 - res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerCodes[idx], gas, &value, &salt) + res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerBytes(int(idx)), gas, &value, &salt) if suberr != nil { stackvalue.Clear() } else { @@ -182,16 +177,15 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte return nil, errors.New("returncontract in non-initcode mode") } var ( - code = scope.Contract.CodeAt(scope.CodeSection) - idx = code[*pc+1] + idx = scope.Contract.Code[*pc+1] offset = scope.Stack.pop() size = scope.Stack.pop() ) - if int(idx) >= len(scope.Contract.Container.subContainerCodes) { + if int(idx) >= len(scope.Contract.Container.subContainerOffsets) { return nil, fmt.Errorf("invalid subcontainer") } ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) - containerCode := scope.Contract.Container.subContainerCodes[idx] + containerCode := scope.Contract.Container.subContainerBytes(int(idx)) if len(containerCode) == 0 { return nil, errors.New("nonexistant subcontainer") } @@ -202,11 +196,12 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte } // append the auxdata - c.data = append(c.data, ret...) - if len(c.data) < c.dataSize { + c.rawContainer = append(c.rawContainer, ret...) + newDataSize := c.dataLen() + if newDataSize < c.dataSize { return nil, errors.New("incomplete aux data") } - c.dataSize = len(c.data) + c.dataSize = newDataSize // probably unneeded as subcontainers are deeply validated if err := c.ValidateCode(interpreter.tableEOF, false); err != nil { @@ -230,7 +225,7 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ stackItem.Clear() scope.Stack.push(&stackItem) } else { - data := getData(scope.Contract.Container.data, offset, 32) + data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32) scope.Stack.push(stackItem.SetBytes(data)) } return nil, nil @@ -239,10 +234,9 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // opDataLoadN implements the DATALOADN opcode func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - offset = uint64(binary.BigEndian.Uint16(code[*pc+1:])) + offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])) ) - data := getData(scope.Contract.Container.data, offset, 32) + data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32) scope.Stack.push(new(uint256.Int).SetBytes(data)) *pc += 2 // move past 2 byte immediate return nil, nil @@ -250,8 +244,7 @@ func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( // opDataSize implements the DATASIZE opcode func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - length := len(scope.Contract.Container.data) - item := uint256.NewInt(uint64(length)) + item := uint256.NewInt(uint64(scope.Contract.Container.dataLen())) scope.Stack.push(item) return nil, nil } @@ -265,7 +258,7 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ ) // These values are checked for overflow during memory expansion calculation // (the memorySize function on the opcode). - data := getData(scope.Contract.Container.data, offset.Uint64(), size.Uint64()) + data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset.Uint64(), size.Uint64()) scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) return nil, nil } @@ -273,8 +266,7 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // opDupN implements the DUPN opcode func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - index = int(code[*pc+1]) + 1 + index = int(scope.Contract.Code[*pc+1]) + 1 ) scope.Stack.dup(index) *pc += 1 // move past immediate @@ -284,8 +276,7 @@ func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // opSwapN implements the SWAPN opcode func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - index = int(code[*pc+1]) + 1 + index = int(scope.Contract.Code[*pc+1]) + 1 ) scope.Stack.swap(index + 1) *pc += 1 // move past immediate @@ -295,8 +286,7 @@ func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // opExchange implements the EXCHANGE opcode func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - index = int(code[*pc+1]) + index = int(scope.Contract.Code[*pc+1]) n = (index >> 4) + 1 m = (index & 0x0F) + 1 ) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 0a9cf638ce..707e87e584 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -24,6 +24,52 @@ import ( "github.com/ethereum/go-ethereum/common" ) +func MakeTestContainer( + types []*functionMetadata, + codeSections [][]byte, + subContainers []*Container, + data []byte, + dataSize int) Container { + + testBytes := make([]byte, 0, 16*1024) + + codeSectionOffsets := make([]int, 0, len(codeSections)) + idx := 0 + for _, code := range codeSections { + codeSectionOffsets = append(codeSectionOffsets, idx) + idx += len(code) + testBytes = append(testBytes, code...) + } + codeSectionEnd := idx + + var subContainerOffsets []int + subContainerEnd := 0 + if len(subContainers) > 0 { + subContainerOffsets = make([]int, len(subContainers)) + for _, subContainer := range subContainers { + containerBytes := subContainer.MarshalBinary() + subContainerOffsets = append(subContainerOffsets, idx) + idx += len(containerBytes) + testBytes = append(testBytes, containerBytes...) + } + subContainerEnd = idx + } + + testBytes = append(testBytes, data...) + + return Container{ + types: types, + codeSectionOffsets: codeSectionOffsets, + codeSectionEnd: codeSectionEnd, + subContainers: subContainers, + subContainerOffsets: subContainerOffsets, + subContainerEnd: subContainerEnd, + dataOffest: subContainerEnd, + dataSize: dataSize, + rawContainer: testBytes, + } +} + func TestEOFMarshaling(t *testing.T) { for i, test := range []struct { want Container @@ -31,18 +77,12 @@ func TestEOFMarshaling(t *testing.T) { }{ { want: Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - codeSections: [][]byte{common.Hex2Bytes("604200")}, - data: []byte{0x01, 0x02, 0x03}, - dataSize: 3, - }, - }, - { - want: Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - codeSections: [][]byte{common.Hex2Bytes("604200")}, - data: []byte{0x01, 0x02, 0x03}, - dataSize: 3, + types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + codeSectionOffsets: []int{19}, // 604200 + codeSectionEnd: 22, + dataOffest: 22, + dataSize: 3, + rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"), }, }, { @@ -52,12 +92,10 @@ func TestEOFMarshaling(t *testing.T) { {inputs: 2, outputs: 3, maxStackHeight: 4}, {inputs: 1, outputs: 1, maxStackHeight: 1}, }, - codeSections: [][]byte{ - common.Hex2Bytes("604200"), - common.Hex2Bytes("6042604200"), - common.Hex2Bytes("00"), - }, - data: []byte{}, + codeSectionOffsets: []int{31, 34, 39}, // 604200, 6042604200, 00 + codeSectionEnd: 40, + dataOffest: 40, + rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"), }, }, } { @@ -80,13 +118,13 @@ func TestEOFSubcontainer(t *testing.T) { if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil { t.Fatal(err) } - container := Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - codeSections: [][]byte{common.Hex2Bytes("604200")}, - subContainers: []*Container{subcontainer}, - data: []byte{0x01, 0x02, 0x03}, - dataSize: 3, - } + container := MakeTestContainer( + []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + [][]byte{common.Hex2Bytes("604200")}, + []*Container{subcontainer}, + []byte{0x01, 0x02, 0x03}, + 3, + ) var ( b = container.MarshalBinary() got Container diff --git a/core/vm/eof_validation.go b/core/vm/eof_validation.go index 514f9fb58c..6334ff9c6b 100644 --- a/core/vm/eof_validation.go +++ b/core/vm/eof_validation.go @@ -148,8 +148,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, case DATALOADN: arg, _ := parseUint16(code[i+1:]) // TODO why are we checking this? We should just pad - if arg+32 > len(container.data) { - return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i) + if arg+32 > container.dataLen() { + return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, container.dataLen(), i) } case RETURNCONTRACT: if !isInitCode { @@ -176,8 +176,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, if arg >= len(container.subContainers) { return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i) } - if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize { - return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i) + if ct := container.subContainers[arg]; ct.dataLen() != ct.dataSize { + return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, ct.dataLen(), ct.dataSize, i) } if visitedSubcontainers == nil { visitedSubcontainers = make(map[int]int) diff --git a/core/vm/eof_validation_test.go b/core/vm/eof_validation_test.go index f262744a21..424000a4c4 100644 --- a/core/vm/eof_validation_test.go +++ b/core/vm/eof_validation_test.go @@ -246,12 +246,14 @@ func TestValidateCode(t *testing.T) { metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}}, }, } { - container := &Container{ - types: test.metadata, - data: make([]byte, 0), - subContainers: make([]*Container, 0), - } - _, err := validateCode(test.code, test.section, container, &eofInstructionSet, false) + container := MakeTestContainer( + test.metadata, + [][]byte{test.code}, + []*Container{}, + []byte{}, + 0, + ) + _, err := validateCode(test.code, test.section, &container, &eofInstructionSet, false) if !errors.Is(err, test.err) { t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err) } @@ -270,14 +272,16 @@ func BenchmarkRJUMPI(b *testing.B) { code = append(code, snippet...) } code = append(code, byte(STOP)) - container := &Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - data: make([]byte, 0), - subContainers: make([]*Container, 0), - } + container := MakeTestContainer( + []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + [][]byte{code}, + []*Container{}, + []byte{}, + 0, + ) b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := validateCode(code, 0, container, &eofInstructionSet, false) + _, err := validateCode(code, 0, &container, &eofInstructionSet, false) if err != nil { b.Fatal(err) } @@ -302,14 +306,16 @@ func BenchmarkRJUMPV(b *testing.B) { } code = append(code, byte(PUSH0)) code = append(code, byte(STOP)) - container := &Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - data: make([]byte, 0), - subContainers: make([]*Container, 0), - } + container := MakeTestContainer( + []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + [][]byte{code}, + []*Container{}, + []byte{}, + 0, + ) b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := validateCode(code, 0, container, &eofInstructionSet, false) + _, err := validateCode(code, 0, &container, &eofInstructionSet, false) if err != nil { b.Fatal(err) } @@ -322,30 +328,32 @@ func BenchmarkRJUMPV(b *testing.B) { // - or code to again call into 1024 code sections. // We can't have all code sections calling each other, otherwise we would exceed 48KB. func BenchmarkEOFValidation(b *testing.B) { - var container Container - var code []byte maxSections := 1024 + types := make([]*functionMetadata, maxSections) + codeSections := make([][]byte, maxSections) + var code []byte for i := 0; i < maxSections; i++ { code = append(code, byte(CALLF)) code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1) } // First container - container.codeSections = append(container.codeSections, append(code, byte(STOP))) - container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) + codeSections = append(codeSections, append(code, byte(STOP))) + types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) inner := []byte{ byte(RETF), } for i := 0; i < 1023; i++ { - container.codeSections = append(container.codeSections, inner) - container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0}) + codeSections = append(codeSections, inner) + types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0}) } for i := 0; i < 12; i++ { - container.codeSections[i+1] = append(code, byte(RETF)) + codeSections[i+1] = append(code, byte(RETF)) } + container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0) bin := container.MarshalBinary() if len(bin) > 48*1024 { b.Fatal("Exceeds 48Kb") @@ -368,17 +376,18 @@ func BenchmarkEOFValidation(b *testing.B) { // - contain calls to some other code sections. // We can't have all code sections calling each other, otherwise we would exceed 48KB. func BenchmarkEOFValidation2(b *testing.B) { - var container Container - var code []byte maxSections := 1024 + types := make([]*functionMetadata, maxSections) + codeSections := make([][]byte, maxSections) + var code []byte for i := 0; i < maxSections; i++ { code = append(code, byte(CALLF)) code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1) } code = append(code, byte(STOP)) // First container - container.codeSections = append(container.codeSections, code) - container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) + codeSections = append(codeSections, code) + types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) inner := []byte{ byte(CALLF), 0x03, 0xE8, @@ -397,10 +406,11 @@ func BenchmarkEOFValidation2(b *testing.B) { } for i := 0; i < 1023; i++ { - container.codeSections = append(container.codeSections, inner) - container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0}) + codeSections = append(codeSections, inner) + types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0}) } + container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0) bin := container.MarshalBinary() if len(bin) > 48*1024 { b.Fatal("Exceeds 48Kb") @@ -424,7 +434,9 @@ func BenchmarkEOFValidation2(b *testing.B) { // - contain calls to other code sections // We can't have all code sections calling each other, otherwise we would exceed 48KB. func BenchmarkEOFValidation3(b *testing.B) { - var container Container + maxSections := 1024 + types := make([]*functionMetadata, maxSections) + codeSections := make([][]byte, maxSections) var code []byte snippet := []byte{ byte(PUSH0), @@ -437,25 +449,25 @@ func BenchmarkEOFValidation3(b *testing.B) { } code = append(code, snippet...) // First container, calls into all other containers - maxSections := 1024 for i := 0; i < maxSections; i++ { code = append(code, byte(CALLF)) code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1) } code = append(code, byte(STOP)) - container.codeSections = append(container.codeSections, code) - container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1}) + codeSections = append(codeSections, code) + types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1}) // Other containers for i := 0; i < 1023; i++ { - container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)}) - container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0}) + codeSections = append(codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)}) + types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0}) } // Other containers for i := 0; i < 68; i++ { - container.codeSections[i+1] = append(snippet, byte(RETF)) - container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1} + codeSections[i+1] = append(snippet, byte(RETF)) + types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1} } + container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0) bin := container.MarshalBinary() if len(bin) > 48*1024 { b.Fatal("Exceeds 48Kb") @@ -487,14 +499,16 @@ func BenchmarkRJUMPI_2(b *testing.B) { code = binary.BigEndian.AppendUint16(code, uint16(x)) } code = append(code, byte(STOP)) - container := &Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - data: make([]byte, 0), - subContainers: make([]*Container, 0), - } + container := MakeTestContainer( + []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + [][]byte{code}, + []*Container{}, + []byte{}, + 0, + ) b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := validateCode(code, 0, container, &eofInstructionSet, false) + _, err := validateCode(code, 0, &container, &eofInstructionSet, false) if err != nil { b.Fatal(err) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a297008bdd..508f73de0c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -363,7 +363,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.CodeAt(scope.CodeSection))))) + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code)))) return nil, nil } @@ -378,7 +378,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ uint64CodeOffset = math.MaxUint64 } - codeCopy := getData(scope.Contract.CodeAt(scope.CodeSection), uint64CodeOffset, length.Uint64()) + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } @@ -909,7 +909,7 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.CodeAt(scope.CodeSection)[*pc])} + return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])} } func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -988,13 +988,12 @@ func makeLog(size int) executionFunc { // opPush1 is a specialized version of pushN func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - codeLen = uint64(len(code)) + codeLen = uint64(len(scope.Contract.Code)) integer = new(uint256.Int) ) *pc += 1 if *pc < codeLen { - scope.Stack.push(integer.SetUint64(uint64(code[*pc]))) + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) } else { scope.Stack.push(integer.Clear()) } @@ -1005,8 +1004,7 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by func makePush(size uint64, pushByteSize int) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.CodeAt(scope.CodeSection) - codeLen = len(code) + codeLen = len(scope.Contract.Code) start = min(codeLen, int(*pc+1)) end = min(codeLen, start+pushByteSize) ) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index ea08500de0..5ba4e07623 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -232,6 +232,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i res []byte // result of the opcode execution function debug = in.evm.Config.Tracer != nil ) + // Don't move this deferred function, it's placed before the OnOpcode-deferred method, // so that it gets executed _after_: the OnOpcode needs the stacks before // they are returned to the pools @@ -243,6 +244,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i if contract.IsEOF() { jt = in.tableEOF + // Set EOF entrypoint + pc = uint64(contract.Container.codeSectionOffsets[0]) } else { jt = in.table } @@ -279,7 +282,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. - op = contract.GetOp(pc, callContext.CodeSection) + op = contract.GetOp(pc) operation := jt[op] cost = operation.constantGas // For tracing // Validate stack diff --git a/statetest b/statetest new file mode 100644 index 0000000000000000000000000000000000000000..19be488d3a6d6d30e65373e881e190cf6f6e03c8 GIT binary patch literal 16 XcmYe#S1{BwGFB)lN=!~=U|;|MB{2i) literal 0 HcmV?d00001 From 644823db48a1a8b88bee92d0c4e76c3960039ac6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 7 Feb 2025 14:07:03 +0100 Subject: [PATCH 38/47] core/vm: clean up code and containerOffset handling --- core/vm/eof.go | 105 +++++++++++++++++------------------- core/vm/eof_instructions.go | 13 ++--- core/vm/eof_test.go | 17 ++---- statetest | Bin 16 -> 0 bytes 4 files changed, 61 insertions(+), 74 deletions(-) delete mode 100644 statetest diff --git a/core/vm/eof.go b/core/vm/eof.go index 2bef6f0a75..5a7d104aeb 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -67,13 +67,10 @@ func isEOFVersion1(code []byte) bool { // Container is an EOF container object. type Container struct { types []*functionMetadata - codeSectionOffsets []int - codeSectionEnd int + codeSectionOffsets []int // contains all the offsets of the codeSections. The last item marks the end subContainers []*Container - subContainerOffsets []int - subContainerEnd int - dataOffest int - dataSize int // might be more than len(data) + subContainerOffsets []int // contains all the offsets of the subContainers. The last item marks the end + dataSize int // might be more than len(data) rawContainer []byte } @@ -108,48 +105,44 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error { return nil } -func (c *Container) codeSectionSize(s int) int { - if s >= len(c.codeSectionOffsets) || s < 0 { - return 0 - } else if s == len(c.codeSectionOffsets)-1 { - return c.codeSectionEnd - c.codeSectionOffsets[s] - } - return c.codeSectionOffsets[s+1] - c.codeSectionOffsets[s] -} - -func (c *Container) codeSectionBytes(s int) []byte { - if s >= len(c.codeSectionOffsets) || s < 0 { +// codeSectionAt returns the code section at index. +// returns an empty slice if the index is out of bounds. +func (c *Container) codeSectionAt(index int) []byte { + if index >= len(c.codeSectionOffsets)-1 || index < 0 { return c.rawContainer[0:0] - } else if s == len(c.codeSectionOffsets)-1 { - return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionEnd] } - return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionOffsets[s+1]] + return c.rawContainer[c.codeSectionOffsets[index]:c.codeSectionOffsets[index+1]] } -func (c *Container) subContainerSize(s int) int { - if s >= len(c.subContainerOffsets) || s < 0 { - return 0 - } else if s == len(c.subContainerOffsets)-1 { - return c.subContainerEnd - c.subContainerOffsets[s] - } - return c.subContainerOffsets[s+1] - c.subContainerOffsets[s] -} - -func (c *Container) subContainerBytes(s int) []byte { - if s >= len(c.subContainerOffsets) || s < 0 { +// subContainerAt returns the sub container at index. +// returns an empty slice if the index is out of bounds. +func (c *Container) subContainerAt(index int) []byte { + if index >= len(c.subContainerOffsets)-1 || index < 0 { return c.rawContainer[0:0] - } else if s == len(c.subContainerOffsets)-1 { - return c.rawContainer[c.subContainerOffsets[s]:c.subContainerEnd] } - return c.rawContainer[c.subContainerOffsets[s]:c.subContainerOffsets[s+1]] + return c.rawContainer[c.subContainerOffsets[index]:c.subContainerOffsets[index+1]] +} + +func (c *Container) dataOffset() int { + if len(c.subContainerOffsets) > 0 { + return c.subContainerOffsets[len(c.subContainerOffsets)-1] + } + return c.codeSectionOffsets[len(c.codeSectionOffsets)-1] } func (c *Container) dataLen() int { - return len(c.rawContainer) - c.dataOffest + return len(c.rawContainer) - c.dataOffset() +} + +func (c *Container) getDataAt(offset, length uint64) []byte { + return getData(c.rawContainer, uint64(c.dataOffset())+offset, length) } // MarshalBinary encodes an EOF container into binary format. func (c *Container) MarshalBinary() []byte { + // Drop the end markers + codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1] + // Build EOF prefix. b := make([]byte, 2) copy(b, eofMagic) @@ -159,9 +152,9 @@ func (c *Container) MarshalBinary() []byte { b = append(b, kindTypes) b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4)) b = append(b, kindCode) - b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionOffsets))) - for s := range c.codeSectionOffsets { - b = binary.BigEndian.AppendUint16(b, uint16(c.codeSectionSize(s))) + b = binary.BigEndian.AppendUint16(b, uint16(len(codeSectionOffsets))) + for s := range codeSectionOffsets { + b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionAt(s)))) } var encodedContainer [][]byte if len(c.subContainers) != 0 { @@ -181,13 +174,13 @@ func (c *Container) MarshalBinary() []byte { for _, ty := range c.types { b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...) } - for s := range c.codeSectionOffsets { - b = append(b, c.codeSectionBytes(s)...) + for s := range codeSectionOffsets { + b = append(b, c.codeSectionAt(s)...) } for _, section := range encodedContainer { b = append(b, section...) } - b = append(b, c.rawContainer[c.dataOffest:]...) + b = append(b, c.rawContainer[c.dataOffset():]...) return b } @@ -333,8 +326,8 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) codeSectionOffsets[i] = idx idx += size } - c.codeSectionOffsets = codeSectionOffsets - c.codeSectionEnd = idx + // add the end marker to the codeSection offsets + c.codeSectionOffsets = append(codeSectionOffsets, idx) // Parse the optional container sizes. if len(containerSizes) != 0 { if len(containerSizes) > maxContainerSections { @@ -360,15 +353,14 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) idx += size } c.subContainers = subContainers - c.subContainerEnd = idx - c.subContainerOffsets = subContainerOffsets + // add the end marker to the subContainer offsets + c.subContainerOffsets = append(subContainerOffsets, idx) } //Parse data section. if topLevel && len(b) != idx+dataSize { return errTruncatedTopLevelContainer } - c.dataOffest = idx c.rawContainer = b @@ -398,7 +390,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error { // should not mean 2 and 3 should be visited twice var ( index = toVisit[0] - code = c.codeSectionBytes(index) + code = c.codeSectionAt(index) ) if _, ok := visited[index]; !ok { res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate) @@ -430,7 +422,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error { toVisit = toVisit[1:] } // Make sure every code section is visited at least once. - if len(visited) != len(c.codeSectionOffsets) { + if len(visited) != len(c.codeSectionOffsets)-1 { return errUnreachableCode } for idx, container := range c.subContainers { @@ -507,6 +499,8 @@ func sum(list []int) (s int) { } func (c *Container) String() string { + // Drop the end markers + codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1] var output = []string{ "Header", fmt.Sprintf(" - EOFMagic: %02x", eofMagic), @@ -514,14 +508,13 @@ func (c *Container) String() string { fmt.Sprintf(" - KindType: %02x", kindTypes), fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4), fmt.Sprintf(" - KindCode: %02x", kindCode), + fmt.Sprintf(" - Number of code sections: %d", len(codeSectionOffsets)), fmt.Sprintf(" - KindData: %02x", kindData), - fmt.Sprintf(" - DataSize: %04x", c.dataLen()), - fmt.Sprintf(" - Number of code sections: %d", len(c.codeSectionOffsets)), + fmt.Sprintf(" - DataSize: %04x", c.dataSize), } - for i := range c.codeSectionOffsets { - output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, c.codeSectionSize(i))) + for i := range codeSectionOffsets { + output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(c.codeSectionAt(i)))) } - output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers))) if len(c.subContainers) > 0 { for i, section := range c.subContainers { @@ -533,12 +526,12 @@ func (c *Container) String() string { output = append(output, fmt.Sprintf(" - Type %v: %x", i, []byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)})) } - for i := range c.codeSectionOffsets { - output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionBytes(i))) + for i := range codeSectionOffsets { + output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionAt(i))) } for i, section := range c.subContainers { output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary())) } - output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffest:])) + output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffset():])) return strings.Join(output, "\n") } diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 4fc5c2c568..380f4a2401 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -134,10 +134,11 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if int(idx) >= len(scope.Contract.Container.subContainerOffsets) { return nil, fmt.Errorf("invalid subcontainer") } + subContainer := scope.Contract.Container.subContainerAt(int(idx)) // Deduct hashing charge // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow - hashingCharge := (params.Keccak256WordGas) * ((uint64(scope.Contract.Container.subContainerSize(int(idx))) + 31) / 32) + hashingCharge := (params.Keccak256WordGas) * ((uint64(len(subContainer)) + 31) / 32) if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok { return nil, ErrGasUintOverflow } @@ -154,7 +155,7 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) // Skip the immediate *pc += 1 - res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerBytes(int(idx)), gas, &value, &salt) + res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, subContainer, gas, &value, &salt) if suberr != nil { stackvalue.Clear() } else { @@ -185,7 +186,7 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte return nil, fmt.Errorf("invalid subcontainer") } ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) - containerCode := scope.Contract.Container.subContainerBytes(int(idx)) + containerCode := scope.Contract.Container.subContainerAt(int(idx)) if len(containerCode) == 0 { return nil, errors.New("nonexistant subcontainer") } @@ -225,7 +226,7 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ stackItem.Clear() scope.Stack.push(&stackItem) } else { - data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32) + data := scope.Contract.Container.getDataAt(offset, 32) scope.Stack.push(stackItem.SetBytes(data)) } return nil, nil @@ -236,7 +237,7 @@ func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( var ( offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])) ) - data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32) + data := scope.Contract.Container.getDataAt(offset, 32) scope.Stack.push(new(uint256.Int).SetBytes(data)) *pc += 2 // move past 2 byte immediate return nil, nil @@ -258,7 +259,7 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ ) // These values are checked for overflow during memory expansion calculation // (the memorySize function on the opcode). - data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset.Uint64(), size.Uint64()) + data := scope.Contract.Container.getDataAt(offset.Uint64(), size.Uint64()) scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) return nil, nil } diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 707e87e584..fd4fbe30b1 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -40,10 +40,9 @@ func MakeTestContainer( idx += len(code) testBytes = append(testBytes, code...) } - codeSectionEnd := idx + codeSectionOffsets = append(codeSectionOffsets, idx) var subContainerOffsets []int - subContainerEnd := 0 if len(subContainers) > 0 { subContainerOffsets = make([]int, len(subContainers)) for _, subContainer := range subContainers { @@ -52,7 +51,8 @@ func MakeTestContainer( idx += len(containerBytes) testBytes = append(testBytes, containerBytes...) } - subContainerEnd = idx + // set the subContainer end marker + subContainerOffsets = append(subContainerOffsets, idx) } testBytes = append(testBytes, data...) @@ -60,11 +60,8 @@ func MakeTestContainer( return Container{ types: types, codeSectionOffsets: codeSectionOffsets, - codeSectionEnd: codeSectionEnd, subContainers: subContainers, subContainerOffsets: subContainerOffsets, - subContainerEnd: subContainerEnd, - dataOffest: subContainerEnd, dataSize: dataSize, rawContainer: testBytes, } @@ -78,9 +75,7 @@ func TestEOFMarshaling(t *testing.T) { { want: Container{ types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - codeSectionOffsets: []int{19}, // 604200 - codeSectionEnd: 22, - dataOffest: 22, + codeSectionOffsets: []int{19, 22}, // 604200, endMarker dataSize: 3, rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"), }, @@ -92,9 +87,7 @@ func TestEOFMarshaling(t *testing.T) { {inputs: 2, outputs: 3, maxStackHeight: 4}, {inputs: 1, outputs: 1, maxStackHeight: 1}, }, - codeSectionOffsets: []int{31, 34, 39}, // 604200, 6042604200, 00 - codeSectionEnd: 40, - dataOffest: 40, + codeSectionOffsets: []int{31, 34, 39, 40}, // 604200, 6042604200, 00, endMarker rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"), }, }, diff --git a/statetest b/statetest deleted file mode 100644 index 19be488d3a6d6d30e65373e881e190cf6f6e03c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmYe#S1{BwGFB)lN=!~=U|;|MB{2i) From 8a84cd1b97b45099f7f0c9eaa395409be41c6b08 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 7 Feb 2025 14:15:20 +0100 Subject: [PATCH 39/47] core/vm: fix off-by-one error --- core/vm/eof_instructions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 380f4a2401..7b1dcaf969 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -131,7 +131,7 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) ) - if int(idx) >= len(scope.Contract.Container.subContainerOffsets) { + if int(idx) >= len(scope.Contract.Container.subContainerOffsets)-1 { return nil, fmt.Errorf("invalid subcontainer") } subContainer := scope.Contract.Container.subContainerAt(int(idx)) @@ -182,7 +182,7 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte offset = scope.Stack.pop() size = scope.Stack.pop() ) - if int(idx) >= len(scope.Contract.Container.subContainerOffsets) { + if int(idx) >= len(scope.Contract.Container.subContainerOffsets)-1 { return nil, fmt.Errorf("invalid subcontainer") } ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) From 545fcb144a87d76d21b8bb284aa605eb08a08b5c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 13 Feb 2025 11:05:09 +0100 Subject: [PATCH 40/47] core/vm: fix overflows in DATACOPY and DATALOAD opcodes --- core/vm/eof_instructions.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 7b1dcaf969..052f29da49 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -222,7 +222,7 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ stackItem = scope.Stack.pop() offset, overflow = stackItem.Uint64WithOverflow() ) - if overflow { + if overflow || offset > uint64(scope.Contract.Container.dataSize) { stackItem.Clear() scope.Stack.push(&stackItem) } else { @@ -253,13 +253,17 @@ func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // opDataCopy implements the DATACOPY opcode func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = scope.Stack.pop() - offset = scope.Stack.pop() - size = scope.Stack.pop() + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + size = scope.Stack.pop() ) - // These values are checked for overflow during memory expansion calculation - // (the memorySize function on the opcode). - data := scope.Contract.Container.getDataAt(offset.Uint64(), size.Uint64()) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() + if overflow || dataOffset64 > uint64(scope.Contract.Container.dataSize) { + // Setting to dataSize makes this a zero byte read + dataOffset64 = uint64(scope.Contract.Container.dataSize) + } + // memOffset and size are checked for overflow during memory expansion calculation. + data := scope.Contract.Container.getDataAt(dataOffset64, size.Uint64()) scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) return nil, nil } From d0e54f39f8ad2c34f18fca315bad53050fb9ac19 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 13 Feb 2025 14:32:48 +0100 Subject: [PATCH 41/47] params: add osaka blob schedule --- consensus/misc/eip4844/eip4844.go | 8 ++++++++ params/config.go | 9 +++++++++ tests/init.go | 10 ++++++++++ 3 files changed, 27 insertions(+) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 4a2754b55c..9a2f4728f5 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -87,6 +87,8 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int { frac = config.BlobScheduleConfig.Prague.UpdateFraction case forks.Cancun: frac = config.BlobScheduleConfig.Cancun.UpdateFraction + case forks.Osaka: + frac = config.BlobScheduleConfig.Osaka.UpdateFraction default: panic("calculating blob fee on unsupported fork") } @@ -107,6 +109,8 @@ func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { return s.Prague.Max case cfg.IsCancun(london, time) && s.Cancun != nil: return s.Cancun.Max + case cfg.IsOsaka(london, time) && s.Osaka != nil: + return s.Osaka.Max default: return 0 } @@ -129,6 +133,8 @@ func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int { return s.Prague.Max case s.Cancun != nil: return s.Cancun.Max + case s.Osaka != nil: + return s.Osaka.Max default: return 0 } @@ -148,6 +154,8 @@ func targetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { return s.Prague.Target case cfg.IsCancun(london, time) && s.Cancun != nil: return s.Cancun.Target + case cfg.IsOsaka(london, time) && s.Osaka != nil: + return s.Osaka.Target default: return 0 } diff --git a/params/config.go b/params/config.go index cb12098bc9..53f097c79a 100644 --- a/params/config.go +++ b/params/config.go @@ -317,10 +317,17 @@ var ( Max: 9, UpdateFraction: 5007716, } + // DefaultOsakaBlobConfig is the default blob configuration for the Osaka fork. + DefaultOsakaBlobConfig = &BlobConfig{ + Target: 6, + Max: 9, + UpdateFraction: 5007716, + } // DefaultBlobSchedule is the latest configured blob schedule for test chains. DefaultBlobSchedule = &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, } ) @@ -501,6 +508,7 @@ type BlobConfig struct { type BlobScheduleConfig struct { Cancun *BlobConfig `json:"cancun,omitempty"` Prague *BlobConfig `json:"prague,omitempty"` + Osaka *BlobConfig `json:"osaka,omitempty"` Verkle *BlobConfig `json:"verkle,omitempty"` } @@ -732,6 +740,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { }{ {name: "cancun", timestamp: c.CancunTime, config: bsc.Cancun}, {name: "prague", timestamp: c.PragueTime, config: bsc.Prague}, + {name: "osaka", timestamp: c.OsakaTime, config: bsc.Osaka}, } { if cur.config != nil { if err := cur.config.validate(); err != nil { diff --git a/tests/init.go b/tests/init.go index ee8d354224..a8bc424fa2 100644 --- a/tests/init.go +++ b/tests/init.go @@ -431,6 +431,11 @@ var Forks = map[string]*params.ChainConfig{ PragueTime: u64(0), OsakaTime: u64(0), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + }, }, "PragueToOsakaAtTime15k": { ChainID: big.NewInt(1), @@ -453,6 +458,11 @@ var Forks = map[string]*params.ChainConfig{ PragueTime: u64(0), OsakaTime: u64(15_000), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + }, }, } From 1bb727ba75101baf344151acfd67c77c9d3e5497 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 13 Feb 2025 16:37:36 +0100 Subject: [PATCH 42/47] core/vm: copy container --- core/vm/eof_instructions.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 052f29da49..a8ff8ed8da 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -196,8 +196,13 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte return nil, err } - // append the auxdata - c.rawContainer = append(c.rawContainer, ret...) + if size.Uint64() > 0 { + // copy the container, so we don't rug parents referencing this data + oldContainer := c.rawContainer + c.rawContainer = make([]byte, 0, len(oldContainer)+len(ret)) + c.rawContainer = append(c.rawContainer, oldContainer...) + c.rawContainer = append(c.rawContainer, ret...) + } newDataSize := c.dataLen() if newDataSize < c.dataSize { return nil, errors.New("incomplete aux data") From a03d1cdc1c626caedfdf63b45eb27921495f0488 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 19 Feb 2025 18:00:54 +0100 Subject: [PATCH 43/47] core/tracing: add CodeSection and functionDepth to traces --- core/tracing/hooks.go | 2 ++ core/vm/interpreter.go | 14 ++++++++++++++ eth/tracers/logger/logger.go | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 68bfb2fedc..d31d8e268c 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -43,6 +43,8 @@ type OpContext interface { CallValue() *uint256.Int CallInput() []byte ContractCode() []byte + CurrentCodeSection() uint64 + ReturnStackDepth() uint64 } // StateDB gives tracers access to the whole state. diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 5ba4e07623..c6c5e08600 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -93,6 +93,20 @@ func (ctx *ScopeContext) ContractCode() []byte { return ctx.Contract.Code } +// CodeSection returns the current code section of the contract being executed. +func (ctx *ScopeContext) CurrentCodeSection() uint64 { + return ctx.CodeSection +} + +// ReturnStackDepth returns the depth of the return stack. +func (ctx *ScopeContext) ReturnStackDepth() uint64 { + if ctx.Contract.IsEOF() { + return uint64(ctx.ReturnStack.Len() + 1) + } else { + return 0 + } +} + type ReturnStack []*ReturnContext // Pop removes an element from the return stack diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 596ee97146..6c9890d85b 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -57,6 +57,7 @@ type Config struct { // current internal state prior to the execution of the statement. type StructLog struct { Pc uint64 `json:"pc"` + Section uint64 `json:"section,omitempty"` Op vm.OpCode `json:"op"` Gas uint64 `json:"gas"` GasCost uint64 `json:"gasCost"` @@ -66,6 +67,7 @@ type StructLog struct { ReturnData []byte `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` + FunctionDepth uint64 `json:"functionDepth,omitempty"` RefundCounter uint64 `json:"refund"` Err error `json:"-"` } @@ -274,7 +276,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope stack = scope.StackData() stackLen = len(stack) ) - log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err} + log := StructLog{pc, scope.CurrentCodeSection(), op, gas, cost, nil, len(memory), nil, nil, nil, depth, scope.ReturnStackDepth(), l.env.StateDB.GetRefund(), err} if l.cfg.EnableMemory { log.Memory = memory } From 6feef351919c3a9d50e240da57ddec75a827e14d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 19 Feb 2025 18:03:34 +0100 Subject: [PATCH 44/47] core/tracing: go generate --- eth/tracers/logger/gen_structlog.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index b406cb3445..7d5b635bea 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -18,6 +18,7 @@ var _ = (*structLogMarshaling)(nil) func (s StructLog) MarshalJSON() ([]byte, error) { type StructLog struct { Pc uint64 `json:"pc"` + Section uint64 `json:"section,omitempty"` Op vm.OpCode `json:"op"` Gas math.HexOrDecimal64 `json:"gas"` GasCost math.HexOrDecimal64 `json:"gasCost"` @@ -27,6 +28,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { ReturnData hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` + FunctionDepth uint64 `json:"functionDepth,omitempty"` RefundCounter uint64 `json:"refund"` Err error `json:"-"` OpName string `json:"opName"` @@ -34,6 +36,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { } var enc StructLog enc.Pc = s.Pc + enc.Section = s.Section enc.Op = s.Op enc.Gas = math.HexOrDecimal64(s.Gas) enc.GasCost = math.HexOrDecimal64(s.GasCost) @@ -48,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth + enc.FunctionDepth = s.FunctionDepth enc.RefundCounter = s.RefundCounter enc.Err = s.Err enc.OpName = s.OpName() @@ -59,6 +63,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { func (s *StructLog) UnmarshalJSON(input []byte) error { type StructLog struct { Pc *uint64 `json:"pc"` + Section *uint64 `json:"section,omitempty"` Op *vm.OpCode `json:"op"` Gas *math.HexOrDecimal64 `json:"gas"` GasCost *math.HexOrDecimal64 `json:"gasCost"` @@ -68,6 +73,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { ReturnData *hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` + FunctionDepth *uint64 `json:"functionDepth,omitempty"` RefundCounter *uint64 `json:"refund"` Err error `json:"-"` } @@ -78,6 +84,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { if dec.Pc != nil { s.Pc = *dec.Pc } + if dec.Section != nil { + s.Section = *dec.Section + } if dec.Op != nil { s.Op = *dec.Op } @@ -108,6 +117,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { if dec.Depth != nil { s.Depth = *dec.Depth } + if dec.FunctionDepth != nil { + s.FunctionDepth = *dec.FunctionDepth + } if dec.RefundCounter != nil { s.RefundCounter = *dec.RefundCounter } From a4c40781a817630d182945a8d2896c4d5be74b59 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 20 Feb 2025 21:28:47 +0100 Subject: [PATCH 45/47] core/vm: fix merge bug --- core/vm/eof_test.go | 1 - core/vm/evm.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index fd4fbe30b1..721bb1a114 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -30,7 +30,6 @@ func MakeTestContainer( subContainers []*Container, data []byte, dataSize int) Container { - testBytes := make([]byte, 0, 16*1024) codeSectionOffsets := make([]int, 0, len(codeSections)) diff --git a/core/vm/evm.go b/core/vm/evm.go index e131fcf69a..9541f676e4 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -343,7 +343,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, return nil, gas, errors.New("extDelegateCall to non-eof contract") } // Initialise a new contract and make initialise the delegate values - contract := NewContract(originCaller, caller, nil, gas, evm.jumpDests) + contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.Container = evm.parseContainer(code) contract.SetCallCode(evm.resolveCodeHash(addr), code) ret, err = evm.interpreter.Run(contract, input, false, false) From 97bb2a5d5ac097a11798229126bf88de2cca0f0f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 21 Feb 2025 09:37:29 +0100 Subject: [PATCH 46/47] cmd/evm/internal/t8ntool: fix eof checks --- cmd/evm/internal/t8ntool/transition.go | 4 ++-- core/vm/eof.go | 10 +++++----- core/vm/eof_instructions.go | 2 +- core/vm/evm.go | 10 +++++----- core/vm/instructions.go | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 24997def03..6ad598f063 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -270,11 +270,11 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { // applyEOFChecks does a sanity check of the prestate EOF code validity, to avoid panic during state transition. func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error { - if !chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { + if !chainConfig.IsOsaka(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { return nil } for addr, acc := range prestate.Pre { - if vm.HasEOFByte(acc.Code) { + if vm.HasEOFMagic(acc.Code) { var ( c vm.Container err error diff --git a/core/vm/eof.go b/core/vm/eof.go index 5a7d104aeb..b100d0f962 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -48,13 +48,13 @@ const ( var eofMagic = []byte{0xef, 0x00} -// HasEOFByte returns true if code starts with 0xEF byte -func HasEOFByte(code []byte) bool { +// hasEOFByte returns true if code starts with 0xEF byte +func hasEOFByte(code []byte) bool { return len(code) != 0 && code[0] == eofFormatByte } -// hasEOFMagic returns true if code starts with magic defined by EIP-3540 -func hasEOFMagic(code []byte) bool { +// HasEOFMagic returns true if code starts with magic defined by EIP-3540 +func HasEOFMagic(code []byte) bool { return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)]) } @@ -196,7 +196,7 @@ func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error { } func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error { - if !hasEOFMagic(b) { + if !HasEOFMagic(b) { return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic) } if len(b) < 14 { diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 32149da9c8..acaf8ec134 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -387,7 +387,7 @@ func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCont returnGas uint64 ) code := interpreter.evm.StateDB.GetCode(toAddr) - if !hasEOFMagic(code) { + if !HasEOFMagic(code) { // Delegate-calling a non-eof contract should return 1 err = ErrExecutionReverted ret = nil diff --git a/core/vm/evm.go b/core/vm/evm.go index 9541f676e4..5b1d38e7df 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -339,7 +339,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { code := evm.StateDB.GetCode(addr) - if fromEOF && !hasEOFMagic(code) { + if fromEOF && !HasEOFMagic(code) { return nil, gas, errors.New("extDelegateCall to non-eof contract") } // Initialise a new contract and make initialise the delegate values @@ -442,7 +442,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui contract.IsDeployment = true // Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail. - isInitcodeEOF := hasEOFMagic(code) + isInitcodeEOF := HasEOFMagic(code) if isInitcodeEOF { if allowEOF { // If the initcode is EOF, verify it is well-formed. @@ -556,16 +556,16 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, isIn } // Reject legacy contract deployment from EOF. - if isInitcodeEOF && !hasEOFMagic(ret) { + if isInitcodeEOF && !HasEOFMagic(ret) { return ret, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, ErrLegacyCode) } // Reject EOF deployment from legacy. - if !isInitcodeEOF && hasEOFMagic(ret) { + if !isInitcodeEOF && HasEOFMagic(ret) { return ret, ErrLegacyCode } // Reject code starting with 0xEF if EIP-3541 is enabled. - if len(ret) >= 1 && HasEOFByte(ret) { + if len(ret) >= 1 && hasEOFByte(ret) { if evm.chainRules.IsOsaka && isInitcodeEOF { // Don't reject EOF contracts after Osaka } else if evm.chainRules.IsLondon { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index c2cad4cbcc..23a4374de4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -442,7 +442,7 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } else { // TODO this should not need to pull up the whole code code := interpreter.evm.StateDB.GetCode(address) - if HasEOFByte(code) { + if HasEOFMagic(code) { slot.SetFromHex("0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5") } else { slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) From c015dc8b71aeadf7583190b37026734d2ffd64c3 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sat, 22 Feb 2025 22:30:08 +0100 Subject: [PATCH 47/47] core/vm: fix regression --- core/vm/evm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 5b1d38e7df..a4d8379077 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -532,7 +532,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui contract.IsDeployment = true contract.Gas = gas - ret, err = evm.initNewContract(contract, address, isInitcodeEOF) + ret, err = evm.initNewContract(contract, input, address, isInitcodeEOF) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { @@ -544,8 +544,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // initNewContract runs a new contract's creation code, performs checks on the // resulting code that is to be deployed, and consumes necessary gas. -func (evm *EVM) initNewContract(contract *Contract, address common.Address, isInitcodeEOF bool) ([]byte, error) { - ret, err := evm.interpreter.Run(contract, nil, false, contract.IsDeployment) +func (evm *EVM) initNewContract(contract *Contract, input []byte, address common.Address, isInitcodeEOF bool) ([]byte, error) { + ret, err := evm.interpreter.Run(contract, input, false, contract.IsDeployment) if err != nil { return ret, err }