// 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 ( "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) { 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) { 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) { 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 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] ) 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) { 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) { 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 func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { 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 func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { 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 func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { 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) { 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) { 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) { 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 func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { 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) { 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) { 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 func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { 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) { 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) { 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) { 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 }