diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index e946ccddd5..6ad598f063 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -150,6 +150,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 + } // Configure tracer if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing @@ -265,6 +268,30 @@ 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 { + if !chainConfig.IsOsaka(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) { + return nil + } + for addr, acc := range prestate.Pre { + if vm.HasEOFMagic(acc.Code) { + var ( + c vm.Container + err error + ) + err = c.UnmarshalBinary(acc.Code, false) + if err == nil { + jt := vm.NewEOFInstructionSetForTesting() + 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/core/state_transition.go b/core/state_transition.go index 0f9ee9eea5..0d4702ff21 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -18,6 +18,7 @@ package core import ( "bytes" + "errors" "fmt" "math" "math/big" @@ -483,7 +484,14 @@ func (st *stateTransition) execute() (*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(msg.From, msg.Data, st.gasRemaining, value) + ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, 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. + // Only intrinsic creation transaction costs are charged. + if errors.Is(vmerr, vm.ErrInvalidEOFInitcode) { + st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeInvalidEOF) + } } else { // Increment the nonce for the next transaction. st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 0485f7a3eb..38ff9f17ed 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. @@ -371,4 +373,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 ) 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) -} diff --git a/core/vm/contract.go b/core/vm/contract.go index 0eaa91d959..4f6656f5f1 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -34,9 +34,10 @@ 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 - Input []byte + Code []byte + Container *Container + CodeHash common.Hash + Input []byte // is the execution frame represented by this object a contract deployment IsDeployment bool @@ -113,7 +114,6 @@ func (c *Contract) GetOp(n uint64) OpCode { if n < uint64(len(c.Code)) { return OpCode(c.Code[n]) } - return STOP } @@ -158,7 +158,13 @@ func (c *Contract) Value() *uint256.Int { return c.value } -// SetCallCode sets the code of the contract, +// IsEOF returns whether the contract is EOF. +func (c *Contract) IsEOF() bool { + return c.Container != nil +} + +// SetCallCode sets the code of the contract and address of the backing data +// object func (c *Contract) SetCallCode(hash common.Hash, code []byte) { c.Code = code c.CodeHash = hash diff --git a/core/vm/eof.go b/core/vm/eof.go index a5406283d5..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)]) } @@ -66,12 +66,12 @@ 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 // contains all the offsets of the codeSections. The last item marks the end + subContainers []*Container + 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 } // functionMetadata is an EOF function signature. @@ -105,8 +105,44 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error { return nil } +// 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] + } + return c.rawContainer[c.codeSectionOffsets[index]:c.codeSectionOffsets[index+1]] +} + +// 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] + } + 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.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) @@ -116,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.codeSections))) - for _, codeSection := range c.codeSections { - b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection))) + 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 { @@ -138,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 _, code := range c.codeSections { - b = append(b, code...) + for s := range codeSectionOffsets { + b = append(b, c.codeSectionAt(s)...) } for _, section := range encodedContainer { b = append(b, section...) } - b = append(b, c.data...) + b = append(b, c.rawContainer[c.dataOffset():]...) return b } @@ -160,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 { @@ -282,21 +318,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 + // 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 { 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 +348,21 @@ 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 + // add the end marker to the subContainer offsets + c.subContainerOffsets = append(subContainerOffsets, idx) } //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.rawContainer = b return nil } @@ -355,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.codeSections[index] + code = c.codeSectionAt(index) ) if _, ok := visited[index]; !ok { res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate) @@ -387,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.codeSections) { + if len(visited) != len(c.codeSectionOffsets)-1 { return errUnreachableCode } for idx, container := range c.subContainers { @@ -464,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), @@ -471,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", len(c.data)), - fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)), + fmt.Sprintf(" - DataSize: %04x", c.dataSize), } - for i, code := range c.codeSections { - output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code))) + 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 { @@ -490,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, code := range c.codeSections { - output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code)) + 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.data)) + 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 800d14d7b8..acaf8ec134 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -16,97 +16,443 @@ package vm +import ( + "encoding/binary" + "errors" + "fmt" + "math" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + // opRjump implements the RJUMP opcode. func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + offset = parseInt16(scope.Contract.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 ( + maxIndex = uint64(scope.Contract.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(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) + return nil, nil } // opCallf implements the CALLF opcode func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + 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") + } + 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(scope.Contract.Container.codeSectionOffsets[idx]) - 1 + 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 func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + 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(scope.Contract.Container.codeSectionOffsets[idx]) - 1 + return nil, nil } // 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 ( + 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.subContainerOffsets)-1 { + 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(len(subContainer)) + 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.Address(), input, subContainer, 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 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 ( + idx = scope.Contract.Code[*pc+1] + offset = scope.Stack.pop() + size = scope.Stack.pop() + ) + if int(idx) >= len(scope.Contract.Container.subContainerOffsets)-1 { + return nil, fmt.Errorf("invalid subcontainer") + } + ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) + containerCode := scope.Contract.Container.subContainerAt(int(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 + } + + 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") + } + c.dataSize = newDataSize + + // 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 func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + stackItem = scope.Stack.pop() + offset, overflow = stackItem.Uint64WithOverflow() + ) + if overflow || offset > uint64(scope.Contract.Container.dataSize) { + stackItem.Clear() + scope.Stack.push(&stackItem) + } else { + data := scope.Contract.Container.getDataAt(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 ( + offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])) + ) + 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 } // opDataSize implements the DATASIZE opcode func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + item := uint256.NewInt(uint64(scope.Contract.Container.dataLen())) + 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() + dataOffset = scope.Stack.pop() + size = scope.Stack.pop() + ) + 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 } // opDupN implements the DUPN opcode func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") + var ( + index = int(scope.Contract.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 ( + index = int(scope.Contract.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 ( + index = int(scope.Contract.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 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 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.Address(), 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.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value, 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.Address(), 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 } diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 0a9cf638ce..721bb1a114 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -24,6 +24,48 @@ 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...) + } + codeSectionOffsets = append(codeSectionOffsets, idx) + + var subContainerOffsets []int + 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...) + } + // set the subContainer end marker + subContainerOffsets = append(subContainerOffsets, idx) + } + + testBytes = append(testBytes, data...) + + return Container{ + types: types, + codeSectionOffsets: codeSectionOffsets, + subContainers: subContainers, + subContainerOffsets: subContainerOffsets, + dataSize: dataSize, + rawContainer: testBytes, + } +} + func TestEOFMarshaling(t *testing.T) { for i, test := range []struct { want Container @@ -31,18 +73,10 @@ 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, 22}, // 604200, endMarker + dataSize: 3, + rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"), }, }, { @@ -52,12 +86,8 @@ 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, 40}, // 604200, 6042604200, 00, endMarker + rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"), }, }, } { @@ -80,13 +110,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 f7b0e78f26..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, &pragueInstructionSet, 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") @@ -357,7 +365,7 @@ func BenchmarkEOFValidation(b *testing.B) { if err := container2.UnmarshalBinary(bin, true); err != nil { b.Fatal(err) } - if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil { + if err := container2.ValidateCode(&eofInstructionSet, false); err != nil { b.Fatal(err) } } @@ -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") @@ -412,7 +422,7 @@ func BenchmarkEOFValidation2(b *testing.B) { if err := container2.UnmarshalBinary(bin, true); err != nil { b.Fatal(err) } - if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil { + if err := container2.ValidateCode(&eofInstructionSet, false); err != nil { b.Fatal(err) } } @@ -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") @@ -468,7 +480,7 @@ func BenchmarkEOFValidation3(b *testing.B) { if err := container2.UnmarshalBinary(bin, true); err != nil { b.Fatal(err) } - if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil { + if err := container2.ValidateCode(&eofInstructionSet, false); err != nil { b.Fatal(err) } } @@ -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, &pragueInstructionSet, false) + _, err := validateCode(code, 0, &container, &eofInstructionSet, false) if err != nil { b.Fatal(err) } @@ -512,6 +526,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, &pragueInstructionSet, true) + validateCode(code, 0, &container, &eofInstructionSet, true) }) } diff --git a/core/vm/errors.go b/core/vm/errors.go index e33c9fcb85..0638620942 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -36,7 +36,10 @@ 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") // errStopToken is an internal token indicating interpreter loop termination, diff --git a/core/vm/evm.go b/core/vm/evm.go index c28dcb2554..a4d8379077 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" @@ -235,7 +237,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g contract := NewContract(caller, addr, value, gas, evm.jumpDests) contract.IsSystemCall = isSystemCall(caller) contract.SetCallCode(evm.resolveCodeHash(addr), code) - ret, err = evm.interpreter.Run(contract, input, false) + contract.Container = evm.parseContainer(code) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } } @@ -293,8 +296,10 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt // 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, caller, value, gas, evm.jumpDests) - contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - ret, err = evm.interpreter.Run(contract, input, false) + code := evm.StateDB.GetCode(addr) + contract.SetCallCode(evm.resolveCodeHash(addr), code) + contract.Container = evm.parseContainer(code) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } if err != nil { @@ -314,7 +319,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt // // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. -func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int, fromEOF bool) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // DELEGATECALL inherits value from parent call @@ -333,12 +338,15 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { + code := evm.StateDB.GetCode(addr) + if fromEOF && !HasEOFMagic(code) { + return nil, gas, errors.New("extDelegateCall to non-eof contract") + } // Initialise a new contract and make initialise the delegate values - // - // Note: The value refers to the original value from the parent call. contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) - contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - ret, err = evm.interpreter.Run(contract, input, false) + contract.Container = evm.parseContainer(code) + contract.SetCallCode(evm.resolveCodeHash(addr), code) + ret, err = evm.interpreter.Run(contract, input, false, false) gas = contract.Gas } if err != nil { @@ -388,12 +396,13 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // 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, addr, new(uint256.Int), gas, evm.jumpDests) - contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - + code := evm.StateDB.GetCode(addr) + contract.SetCallCode(evm.resolveCodeHash(addr), code) + contract.Container = 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. - ret, err = evm.interpreter.Run(contract, input, true) + ret, err = evm.interpreter.Run(contract, input, true, false) gas = contract.Gas } if err != nil { @@ -410,7 +419,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b } // create creates a new contract using code as deployment code. -func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { +func (evm *EVM) create(caller common.Address, code []byte, 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, code, gas, value.ToBig()) defer func(startGas uint64) { @@ -425,6 +434,32 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui if !evm.Context.CanTransfer(evm.StateDB, caller, 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, address, value, gas, evm.jumpDests) + contract.IsDeployment = true + + // Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail. + isInitcodeEOF := HasEOFMagic(code) + if isInitcodeEOF { + if allowEOF { + // If the initcode is EOF, verify it is well-formed. + var c Container + if err := c.UnmarshalBinary(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) if nonce+1 < nonce { return nil, common.Address{}, gas, ErrNonceUintOverflow @@ -491,17 +526,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui gas = gas - statelessGas } evm.Context.Transfer(evm.StateDB, caller, 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, address, value, gas, evm.jumpDests) - // Explicitly set the code to a null hash to prevent caching of jump analysis // for the initialization code. contract.SetCallCode(common.Hash{}, code) contract.IsDeployment = true + contract.Gas = gas - ret, err = evm.initNewContract(contract, address) + ret, err = evm.initNewContract(contract, input, address, isInitcodeEOF) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { @@ -513,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) ([]byte, error) { - ret, err := evm.interpreter.Run(contract, nil, false) +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 } @@ -524,9 +555,22 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b 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.IsOsaka && isInitcodeEOF { + // Don't reject EOF contracts after Osaka + } else if evm.chainRules.IsLondon { + return ret, ErrInvalidCode + } } if !evm.chainRules.IsEIP4762 { @@ -545,9 +589,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } // Create creates a new contract using code as deployment code. -func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int, allowEOF bool) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) - return evm.create(caller, code, gas, value, contractAddr, CREATE) + return evm.create(caller, code, gas, value, contractAddr, CREATE, nil, allowEOF) } // Create2 creates a new contract using code as deployment code. @@ -556,7 +600,13 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(code)) - return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) + return evm.create(caller, code, gas, endowment, contractAddr, CREATE2, nil, false) +} + +// EOFCreate creates a new eof contract. +func (evm *EVM) EOFCreate(caller common.Address, input []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(subcontainer)) + return evm.create(caller, subcontainer, gas, endowment, contractAddr, EOFCREATE, input, true) } // resolveCode returns the code associated with the provided account. After @@ -630,3 +680,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.IsOsaka { + 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 +} 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 55855727b5..6fbbdf4cd2 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/core/vm/instructions.go b/core/vm/instructions.go index 1785ffc139..23a4374de4 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 @@ -340,7 +352,13 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() - 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(code))) + } return nil, nil } @@ -372,6 +390,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) memOffset = stack.pop() codeOffset = stack.pop() length = stack.pop() + lengthU64 = length.Uint64() + codeCopy []byte ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { @@ -379,9 +399,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } addr := common.Address(a.Bytes20()) code := interpreter.evm.StateDB.GetCode(addr) - codeCopy := getData(code, uint64CodeOffset, length.Uint64()) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - + if isEOFVersion1(code) { + codeCopy = getData(eofMagic, uint64CodeOffset, lengthU64) + } else { + codeCopy = getData(code, uint64CodeOffset, lengthU64) + } + scope.Memory.Set(memOffset.Uint64(), lengthU64, codeCopy) return nil, nil } @@ -417,7 +440,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 HasEOFMagic(code) { + slot.SetFromHex("0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5") + } else { + slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + } } return nil, nil } @@ -677,7 +706,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.Address(), input, gas, &value) + res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract.Address(), 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 @@ -818,7 +847,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.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value) + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value, false) if err != nil { temp.Clear() } else { diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0902d17c54..d30aabede4 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -107,7 +107,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, evm.interpreter, &ScopeContext{nil, stack, nil}) + 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)) } @@ -221,7 +221,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + 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) @@ -247,7 +247,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + 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)} } @@ -283,7 +283,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() - scope = &ScopeContext{nil, stack, nil} + scope = &ScopeContext{nil, stack, nil, 0, nil, false} ) // convert args intArgs := make([]*uint256.Int, len(args)) @@ -528,13 +528,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + 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, evm.interpreter, &ScopeContext{mem, stack, nil}) + 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") } @@ -555,7 +555,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -568,7 +568,7 @@ func TestOpTstore(t *testing.T) { caller = common.Address{} to = common.Address{1} contract = NewContract(caller, to, new(uint256.Int), 0, nil) - scopeContext = ScopeContext{mem, stack, contract} + scopeContext = ScopeContext{mem, stack, contract, 0, nil, false} value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) @@ -613,7 +613,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false}) } } @@ -680,7 +680,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)) @@ -707,7 +707,7 @@ func TestRandom(t *testing.T) { stack = newstack() pc = uint64(0) ) - opRandom(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + 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)) } @@ -749,7 +749,7 @@ func TestBlobHash(t *testing.T) { ) evm.SetTxContext(TxContext{BlobHashes: tt.hashes}) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evm.interpreter, &ScopeContext{nil, stack, nil}) + 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)) } @@ -889,7 +889,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evm.interpreter, &ScopeContext{mem, stack, nil}) + 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 a0038d1aa8..c6c5e08600 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,47 @@ 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 +// 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 @@ -150,7 +191,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { } } evm.Config.ExtraEips = extraEips - return &EVMInterpreter{evm: evm, table: table} + return &EVMInterpreter{evm: evm, table: table, tableEOF: &eofInstructionSet} } // Run loops and evaluates the contract's code with the given input data and returns @@ -159,7 +200,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-- }() @@ -181,13 +222,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 @@ -201,6 +246,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( 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 @@ -210,6 +256,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( }() contract.Input = input + if contract.IsEOF() { + jt = in.tableEOF + // Set EOF entrypoint + pc = uint64(contract.Container.codeSectionOffsets[0]) + } else { + jt = in.table + } + if debug { defer func() { // this deferred method handles exit-with-error if err == nil { @@ -243,7 +297,7 @@ 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] + operation := jt[op] cost = operation.constantGas // For tracing // Validate stack if sLen := stack.len(); sLen < operation.minStack { diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 9d984291f2..fd4281be12 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -181,6 +181,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { input, cfg.GasLimit, uint256.MustFromBig(cfg.Value), + rules.IsOsaka, ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas}, err) 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]) } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index dbfc7308f7..36a72ccfc8 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -65,7 +65,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 := evm.Interpreter().Run(contract, []byte{}, 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/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 } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 02ff8146fb..ec60abd613 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 } diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index b1e38bf627..3743896966 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -48,7 +48,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(evm.GetVMContext(), nil, common.Address{}) - _, err := evm.Interpreter().Run(contract, []byte{}, false) + _, err := evm.Interpreter().Run(contract, []byte{}, false, false) if err != nil { t.Fatal(err) } diff --git a/params/protocol_params.go b/params/protocol_params.go index 6b06dadaef..ad2acb94ef 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)