From 83fddabf3a2e24373686e8fce04ad63919dee3d5 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 17 Dec 2021 13:44:05 +0100 Subject: [PATCH 01/49] core/vm: Make INVALID a defined opcode (#24017) * core/vm: Define 0xfe opcode as INVALID * core/vm: Remove opInvalid as opUndefined handles it Co-authored-by: Alex Beregszaszi # Conflicts: # eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json --- accounts/abi/bind/backends/simulated_test.go | 2 +- core/vm/opcodes.go | 3 +++ eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index ce4ccce6e90..7b91ec3d907 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -495,7 +495,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) { GasPrice: u256.Num0, Value: nil, Data: common.Hex2Bytes("b9b046f9"), - }, 0, errors.New("invalid opcode: opcode 0xfe not defined"), nil}, + }, 0, errors.New("invalid opcode: INVALID"), nil}, {"Valid", ethereum.CallMsg{ From: addr, diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 410514b6491..59078085d70 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -217,6 +217,7 @@ const ( CREATE2 STATICCALL OpCode = 0xfa REVERT OpCode = 0xfd + INVALID OpCode = 0xfe SELFDESTRUCT OpCode = 0xff ) @@ -383,6 +384,7 @@ var opCodeToString = map[OpCode]string{ CREATE2: "CREATE2", STATICCALL: "STATICCALL", REVERT: "REVERT", + INVALID: "INVALID", SELFDESTRUCT: "SELFDESTRUCT", PUSH: "PUSH", @@ -542,6 +544,7 @@ var stringToOp = map[string]OpCode{ "RETURN": RETURN, "CALLCODE": CALLCODE, "REVERT": REVERT, + "INVALID": INVALID, "SELFDESTRUCT": SELFDESTRUCT, } diff --git a/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json b/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json index 7627c8c23d6..ec2ceb426fd 100644 --- a/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json +++ b/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json @@ -59,7 +59,7 @@ "result": { "calls": [ { - "error": "invalid opcode: opcode 0xfe not defined", + "error": "invalid opcode: INVALID", "from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", "gas": "0x75fe3", "gasUsed": "0x75fe3", From 3a6ea15d9ef4d25b02fb75011ccac47f41644eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 3 Dec 2021 11:04:54 +0100 Subject: [PATCH 02/49] core/vm: fill gaps in jump table with opUndefined (#24031) --- core/vm/instructions.go | 4 ++++ core/vm/jump_table.go | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 41a49f29eb4..3c4d6bfbd84 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -842,6 +842,10 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b return ret, nil } +func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])} +} + func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { return nil, nil } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index b4d185ac962..e3fb667b83c 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -258,7 +258,7 @@ func newHomesteadInstructionSet() JumpTable { // newFrontierInstructionSet returns the frontier instructions // that can be executed during the frontier phase. func newFrontierInstructionSet() JumpTable { - return JumpTable{ + tbl := JumpTable{ STOP: { execute: opStop, constantGas: 0, @@ -1463,4 +1463,13 @@ func newFrontierInstructionSet() JumpTable { writes: true, }, } + + // Fill all unassigned slots with opUndefined. + for i, entry := range tbl { + if entry == nil { + tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} + } + } + + return tbl } From 4b34b5d2aa601c2bd306ec0061b57216ceb564dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 29 Nov 2021 14:46:24 +0100 Subject: [PATCH 03/49] core/vm: simplify error handling in interpreter loop (#23952) * core/vm: break loop on any error * core/vm: move ErrExecutionReverted to opRevert() * core/vm: use "stop token" to stop the loop * core/vm: unconditionally pc++ in the loop * core/vm: set return data in instruction impls --- core/vm/errors.go | 4 ++++ core/vm/instructions.go | 24 ++++++++++++++++-------- core/vm/interpreter.go | 24 +++++++++--------------- core/vm/jump_table.go | 19 +------------------ 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/core/vm/errors.go b/core/vm/errors.go index 63cac917e51..253974d2344 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -42,6 +42,10 @@ var ( ErrReturnStackExceeded = errors.New("return stack limit reached") ErrInvalidCode = errors.New("invalid code") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + + // errStopToken is an internal token indicating interpreter loop termination, + // never returned to outside callers. + errStopToken = errors.New("stop token") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 3c4d6bfbd84..f6e3e45898e 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -586,7 +586,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } return nil, ErrInvalidJump } - *pc = pos.Uint64() + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop return nil, nil } @@ -609,9 +609,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } return nil, ErrInvalidJump } - *pc = pos.Uint64() - } else { - *pc++ + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop } return nil, nil } @@ -667,8 +665,10 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b scope.Contract.Gas += returnGas 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 } @@ -699,8 +699,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] scope.Contract.Gas += returnGas 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 } @@ -735,6 +737,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -769,6 +772,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -798,6 +802,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } @@ -827,19 +832,22 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) scope.Contract.Gas += returnGas + interpreter.returnData = ret return ret, nil } func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { offset, size := scope.Stack.Pop(), scope.Stack.Pop() ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) - return ret, nil + return ret, errStopToken } func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { offset, size := scope.Stack.Pop(), scope.Stack.Pop() ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) - return ret, nil + + interpreter.returnData = ret + return ret, ErrExecutionReverted } func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -847,7 +855,7 @@ func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( } func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - return nil, nil + return nil, errStopToken } func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -860,7 +868,7 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] } interpreter.evm.IntraBlockState().AddBalance(beneficiaryAddr, balance) interpreter.evm.IntraBlockState().Suicide(callerAddr) - return nil, nil + return nil, errStopToken } // following functions are used by the instruction jump table diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 49c7ef25da0..732c65afac7 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -349,24 +349,18 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // execute the operation res, err = operation.execute(&pc, in, callContext) - // if the operation clears the return data (e.g. it has returning data) - // set the last return to the result of the operation. - if operation.returns { - in.returnData = res - } - switch { - case err != nil: - return nil, err - case operation.reverts: - return res, ErrExecutionReverted - case operation.halts: - return res, nil - case !operation.jumps: - pc++ + if err != nil { + break } + pc++ } - return nil, nil + + if err == errStopToken { + err = nil // clear stop token error + } + + return res, err } func (vm *VM) setReadonly(outerReadonly bool) func() { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index e3fb667b83c..fe6c83dd6d9 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -48,11 +48,7 @@ type operation struct { // memorySize returns the memory size required for the operation memorySize memorySizeFunc - halts bool // indicates whether the operation should halt further execution - jumps bool // indicates whether the program counter should not increment - writes bool // determines whether this a state modifying operation - reverts bool // determines whether the operation reverts state (implicitly halts) - returns bool // determines whether the operations sets the return data content + writes bool // determines whether this a state modifying operation } var ( @@ -164,7 +160,6 @@ func newConstantinopleInstructionSet() JumpTable { numPush: 1, memorySize: memoryCreate2, writes: true, - returns: true, } return instructionSet } @@ -182,7 +177,6 @@ func newByzantiumInstructionSet() JumpTable { numPop: 6, numPush: 1, memorySize: memoryStaticCall, - returns: true, } instructionSet[RETURNDATASIZE] = &operation{ execute: opReturnDataSize, @@ -210,8 +204,6 @@ func newByzantiumInstructionSet() JumpTable { numPop: 2, numPush: 0, memorySize: memoryRevert, - reverts: true, - returns: true, } return instructionSet } @@ -250,7 +242,6 @@ func newHomesteadInstructionSet() JumpTable { numPop: 6, numPush: 1, memorySize: memoryDelegateCall, - returns: true, } return instructionSet } @@ -266,7 +257,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(0, 0), numPop: 0, numPush: 0, - halts: true, }, ADD: { execute: opAdd, @@ -675,7 +665,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(1, 0), numPop: 1, numPush: 0, - jumps: true, }, JUMPI: { execute: opJumpi, @@ -684,7 +673,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(2, 0), numPop: 2, numPush: 0, - jumps: true, }, PC: { execute: opPc, @@ -1418,7 +1406,6 @@ func newFrontierInstructionSet() JumpTable { numPush: 1, memorySize: memoryCreate, writes: true, - returns: true, }, CALL: { execute: opCall, @@ -1429,7 +1416,6 @@ func newFrontierInstructionSet() JumpTable { numPop: 7, numPush: 1, memorySize: memoryCall, - returns: true, }, CALLCODE: { execute: opCallCode, @@ -1440,7 +1426,6 @@ func newFrontierInstructionSet() JumpTable { numPop: 7, numPush: 1, memorySize: memoryCall, - returns: true, }, RETURN: { execute: opReturn, @@ -1450,7 +1435,6 @@ func newFrontierInstructionSet() JumpTable { numPop: 2, numPush: 0, memorySize: memoryReturn, - halts: true, }, SELFDESTRUCT: { execute: opSuicide, @@ -1459,7 +1443,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(1, 0), numPop: 1, numPush: 0, - halts: true, writes: true, }, } From c5a912298f6ac6e8096087df8429171faed9cbc6 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Tue, 29 Nov 2022 16:33:04 +0200 Subject: [PATCH 04/49] cmd/hack: temp fix build by breaking interpreter loop [WiP] --- core/vm/absint_cfg_proof_check.go | 10 +++++----- core/vm/absint_cfg_proof_gen.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/vm/absint_cfg_proof_check.go b/core/vm/absint_cfg_proof_check.go index ac16f618d25..58663a1c34e 100644 --- a/core/vm/absint_cfg_proof_check.go +++ b/core/vm/absint_cfg_proof_check.go @@ -9,8 +9,8 @@ import ( ) type CfgOpSem struct { - reverts bool - halts bool + // reverts bool + // halts bool isPush bool isDup bool isSwap bool @@ -32,8 +32,8 @@ func NewCfgAbsSem() *CfgAbsSem { continue } opsem := CfgOpSem{} - opsem.reverts = op.reverts - opsem.halts = op.halts + // opsem.reverts = op.reverts + // opsem.halts = op.halts opsem.isPush = op.isPush opsem.isDup = op.isDup opsem.isSwap = op.isSwap @@ -87,7 +87,7 @@ func resolveCheck(sem *CfgAbsSem, code []byte, st0 *astate, pc0 int) (map[int]bo succs := make(map[int]bool) jumps := make(map[int]bool) - if opsem == nil || opsem.halts || opsem.reverts { + if opsem == nil { return succs, jumps, nil } diff --git a/core/vm/absint_cfg_proof_gen.go b/core/vm/absint_cfg_proof_gen.go index 216bed304c3..938517ac9b4 100644 --- a/core/vm/absint_cfg_proof_gen.go +++ b/core/vm/absint_cfg_proof_gen.go @@ -87,7 +87,7 @@ func toProgram(code []byte) *Program { op := OpCode(code[pc]) stmt.opcode = op stmt.operation = jt[op] - stmt.ends = stmt.operation == nil || stmt.operation.halts || stmt.operation.reverts + stmt.ends = stmt.operation == nil //fmt.Printf("%v %v %v", pc, stmt.opcode, stmt.operation.valid) if op.IsPush() { From 80c6fe2e132ad35d9ae2803ea4dcc15795b0731c Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 11 May 2021 19:32:52 +0200 Subject: [PATCH 05/49] core/vm: implement EOF EIP-3540: EOF v1 --- core/vm/contract.go | 61 ++++++++++++--- core/vm/eips.go | 5 ++ core/vm/eof.go | 169 ++++++++++++++++++++++++++++++++++++++++ core/vm/eof_test.go | 156 +++++++++++++++++++++++++++++++++++++ core/vm/errors.go | 18 ++++- core/vm/evm.go | 36 +++++++-- core/vm/instructions.go | 22 +++--- core/vm/interpreter.go | 2 +- 8 files changed, 442 insertions(+), 27 deletions(-) create mode 100644 core/vm/eof.go create mode 100644 core/vm/eof_test.go diff --git a/core/vm/contract.go b/core/vm/contract.go index fe9153631a7..a0c3d4cbb61 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -48,9 +48,12 @@ type Contract struct { CallerAddress common.Address caller ContractRef self ContractRef - jumpdests map[common.Hash][]uint64 // Aggregated result of JUMPDEST analysis. - analysis []uint64 // Locally cached result of JUMPDEST analysis - skipAnalysis bool + + header EOF1Header + + jumpdests map[common.Hash][]uint64 // Aggregated result of JUMPDEST analysis. + analysis []uint64 // Locally cached result of JUMPDEST analysis + skipAnalysis bool Code []byte CodeHash common.Hash @@ -89,11 +92,11 @@ func (c *Contract) validJumpdest(dest *uint256.Int) (bool, bool) { udest, overflow := dest.Uint64WithOverflow() // PC cannot go beyond len(code) and certainly can't be bigger than 64bits. // Don't bother checking for JUMPDEST in that case. - if overflow || udest >= uint64(len(c.Code)) { + if overflow || udest >= c.CodeSize() { return false, false } // Only JUMPDESTs allowed for destinations - if OpCode(c.Code[udest]) != JUMPDEST { + if OpCode(c.Code[c.CodeBeginOffset()+udest]) != JUMPDEST { return false, false } if c.skipAnalysis { @@ -118,7 +121,8 @@ func (c *Contract) isCode(udest uint64) bool { if !exist { // Do the analysis and save in parent context // We do not need to store it in c.analysis - analysis = codeBitmap(c.Code) + code := c.Code[c.CodeBeginOffset():c.CodeEndOffset()] + analysis = codeBitmap(code) c.jumpdests[c.CodeHash] = analysis } // Also stash it in current contract for faster access @@ -131,7 +135,8 @@ func (c *Contract) isCode(udest uint64) bool { // we don't have to recalculate it for every JUMP instruction in the execution // However, we don't save it within the parent context if c.analysis == nil { - c.analysis = codeBitmap(c.Code) + code := c.Code[c.CodeBeginOffset():c.CodeEndOffset()] + c.analysis = codeBitmap(code) } return isCodeFromAnalysis(c.analysis, udest) @@ -156,7 +161,7 @@ func (c *Contract) GetOp(n uint64) OpCode { // GetByte returns the n'th byte in the contract's byte array func (c *Contract) GetByte(n uint64) byte { - if n < uint64(len(c.Code)) { + if n < c.CodeEndOffset() { return c.Code[n] } @@ -190,18 +195,54 @@ func (c *Contract) Value() *uint256.Int { return c.value } +// IsLegacy returns true if contract is not EOF +func (c *Contract) IsLegacy() bool { + // EOF1 doesn't allow contracts without code section + return c.header.codeSize == 0 +} + +// CodeBeginOffset returns starting offset of the code section +func (c *Contract) CodeBeginOffset() uint64 { + if c.IsLegacy() { + return 0 + } + return c.header.CodeBeginOffset() +} + +// CodeEndOffset returns offset of the code section end +func (c *Contract) CodeEndOffset() uint64 { + if c.IsLegacy() { + return uint64(len(c.Code)) + } + return c.header.CodeEndOffset() +} + +// CodeSize returns the size of the code (if legacy) or code section (if EOF) +func (c *Contract) CodeSize() uint64 { + if c.IsLegacy() { + return uint64(len(c.Code)) + } + return uint64(c.header.codeSize) +} + // SetCallCode sets the code of the contract and address of the backing data // object -func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, header *EOF1Header) { c.Code = code c.CodeHash = hash c.CodeAddr = addr + + c.header.codeSize = header.codeSize + c.header.dataSize = header.dataSize } // SetCodeOptionalHash can be used to provide code, but it's optional to provide hash. // In case hash is not provided, the jumpdest analysis will not be saved to the parent context -func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) { +func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash, header *EOF1Header) { c.Code = codeAndHash.code c.CodeHash = codeAndHash.hash c.CodeAddr = addr + + c.header.codeSize = header.codeSize + c.header.dataSize = header.dataSize } diff --git a/core/vm/eips.go b/core/vm/eips.go index cdee7ae3045..fdf7dbd81d0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -34,6 +34,7 @@ var activators = map[int]func(*JumpTable){ 2200: enable2200, 1884: enable1884, 1344: enable1344, + 3540: enable3540, } // EnableEIP enables the given EIP on the config. @@ -197,3 +198,7 @@ func enable3860(jt *JumpTable) { jt[CREATE].dynamicGas = gasCreateEip3860 jt[CREATE2].dynamicGas = gasCreate2Eip3860 } + +func enable3540(jt *JumpTable) { + // Do nothing. +} diff --git a/core/vm/eof.go b/core/vm/eof.go new file mode 100644 index 00000000000..95df5c502cf --- /dev/null +++ b/core/vm/eof.go @@ -0,0 +1,169 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package vm + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type eofVersion int +type headerSection byte + +const ( + eofFormatByte byte = 0xEF + eof1Version eofVersion = 1 + kindTerminator headerSection = 0 + kindCode headerSection = 1 + kindData headerSection = 2 + + versionOffset int = 2 + sectionHeaderStart int = 3 +) + +var eofMagic []byte = []byte{0xEF, 0x00} + +type EOF1Header struct { + codeSize uint16 // Size of code section. Cannot be 0 for EOF1 code. Equals 0 for legacy code. + dataSize uint16 // Size of data section. Equals 0 if data section is absent in EOF1 code. Equals 0 for legacy code. +} + +// 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 { + return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)]) +} + +// isEOFVersion1 returns true if the code's version byte equals eof1Version. It +// does not verify the EOF magic is valid. +func isEOFVersion1(code []byte) bool { + return versionOffset < len(code) && code[versionOffset] == byte(eof1Version) +} + +// readSectionSize returns the size of the section at the offset i. +func readSectionSize(code []byte, i int) (uint16, error) { + if len(code) < i+2 { + return 0, fmt.Errorf("section size missing") + } + return binary.BigEndian.Uint16(code[i : i+2]), nil +} + +// readEOF1Header parses EOF1-formatted code header +func readEOF1Header(code []byte) (EOF1Header, error) { + if !hasEOFMagic(code) || !isEOFVersion1(code) { + return EOF1Header{}, ErrEOF1InvalidVersion + } + var ( + i = sectionHeaderStart + err error + codeRead int + dataRead int + header EOF1Header + ) +outer: + for i < len(code) { + switch headerSection(code[i]) { + case kindTerminator: + i += 1 + break outer + case kindCode: + // Only 1 code section is allowed. + if codeRead != 0 { + return EOF1Header{}, ErrEOF1MultipleCodeSections + } + // Size must be present. + if header.codeSize, err = readSectionSize(code, i+1); err != nil { + return EOF1Header{}, ErrEOF1CodeSectionSizeMissing + } + // Size must not be 0. + if header.codeSize == 0 { + return EOF1Header{}, ErrEOF1EmptyCodeSection + } + i += 3 + codeRead += 1 + case kindData: + // Data section is allowed only after code section. + if codeRead == 0 { + return EOF1Header{}, ErrEOF1DataSectionBeforeCodeSection + } + // Only 1 data section is allowed. + if dataRead != 0 { + return EOF1Header{}, ErrEOF1MultipleDataSections + } + // Size must be present. + if header.dataSize, err = readSectionSize(code, i+1); err != nil { + return EOF1Header{}, ErrEOF1DataSectionSizeMissing + } + // Data section size must not be 0. + if header.dataSize == 0 { + return EOF1Header{}, ErrEOF1EmptyDataSection + } + i += 3 + dataRead += 1 + default: + return EOF1Header{}, ErrEOF1UnknownSection + } + } + // 1 code section is required. + if codeRead != 1 { + return EOF1Header{}, ErrEOF1CodeSectionMissing + } + // Declared section sizes must correspond to real size (trailing bytes are not allowed.) + if i+int(header.codeSize)+int(header.dataSize) != len(code) { + return EOF1Header{}, ErrEOF1InvalidTotalSize + } + + return header, nil +} + +// validateEOF returns true if code has valid format +func validateEOF(code []byte) bool { + _, err := readEOF1Header(code) + return err == nil +} + +// readValidEOF1Header parses EOF1-formatted code header, assuming that it is already validated +func readValidEOF1Header(code []byte) EOF1Header { + var header EOF1Header + codeSizeOffset := sectionHeaderStart + 1 + header.codeSize = binary.BigEndian.Uint16(code[codeSizeOffset : codeSizeOffset+2]) + if code[codeSizeOffset+2] == 2 { + dataSizeOffset := codeSizeOffset + 3 + header.dataSize = binary.BigEndian.Uint16(code[dataSizeOffset : dataSizeOffset+2]) + } + return header +} + +// CodeBeginOffset returns starting offset of the code section +func (header *EOF1Header) CodeBeginOffset() uint64 { + if header.dataSize == 0 { + // len(magic) + version + code_section_id + code_section_size + terminator + return 7 + } + // len(magic) + version + code_section_id + code_section_size + data_section_id + data_section_size + terminator + return 10 +} + +// CodeEndOffset returns offset of the code section end +func (header *EOF1Header) CodeEndOffset() uint64 { + return header.CodeBeginOffset() + uint64(header.codeSize) +} diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go new file mode 100644 index 00000000000..c13970f6ac1 --- /dev/null +++ b/core/vm/eof_test.go @@ -0,0 +1,156 @@ +// Copyright 2021 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" +) + +type eof1Test struct { + code string + codeSize uint16 + dataSize uint16 +} + +var eof1ValidTests = []eof1Test{ + {"EF00010100010000", 1, 0}, + {"EF0001010002006000", 2, 0}, + {"EF0001010002020001006000AA", 2, 1}, + {"EF0001010002020004006000AABBCCDD", 2, 4}, + {"EF00010100040200020060006001AABB", 4, 2}, + {"EF000101000602000400600060016002AABBCCDD", 6, 4}, +} + +type eof1InvalidTest struct { + code string + error string +} + +// Codes starting with something else other than magic +var notEOFTests = []string{ + // valid: "EF0001010002020004006000AABBCCDD", + "", + "FE", // invalid first byte + "FE0001010002020004006000AABBCCDD", // valid except first byte of magic + "EF", // incomplete magic + "EF01", // not correct magic + "EF0101010002020004006000AABBCCDD", // valid except second byte of magic +} + +// Codes starting with magic, but the rest is invalid +var eof1InvalidTests = []eof1InvalidTest{ + // valid: {"EF0001010002020004006000AABBCCDD", nil}, + {"EF00", ErrEOF1InvalidVersion.Error()}, // no version + {"EF0000", ErrEOF1InvalidVersion.Error()}, // invalid version + {"EF0002", ErrEOF1InvalidVersion.Error()}, // invalid version + {"EF0000010002020004006000AABBCCDD", ErrEOF1InvalidVersion.Error()}, // valid except version + {"EF0001", ErrEOF1CodeSectionMissing.Error()}, // no header + {"EF000100", ErrEOF1CodeSectionMissing.Error()}, // no code section + {"EF000101", ErrEOF1CodeSectionSizeMissing.Error()}, // no code section size + {"EF00010100", ErrEOF1CodeSectionSizeMissing.Error()}, // code section size incomplete + {"EF0001010002", ErrEOF1InvalidTotalSize.Error()}, // no section terminator + {"EF000101000200", ErrEOF1InvalidTotalSize.Error()}, // no code section contents + {"EF00010100020060", ErrEOF1InvalidTotalSize.Error()}, // not complete code section contents + {"EF0001010002006000DEADBEEF", ErrEOF1InvalidTotalSize.Error()}, // trailing bytes after code + {"EF00010100020100020060006000", ErrEOF1MultipleCodeSections.Error()}, // two code sections + {"EF000101000000", ErrEOF1EmptyCodeSection.Error()}, // 0 size code section + {"EF000101000002000200AABB", ErrEOF1EmptyCodeSection.Error()}, // 0 size code section, with non-0 data section + {"EF000102000401000200AABBCCDD6000", ErrEOF1DataSectionBeforeCodeSection.Error()}, // data section before code section + {"EF0001020004AABBCCDD", ErrEOF1DataSectionBeforeCodeSection.Error()}, // data section without code section + {"EF000101000202", ErrEOF1DataSectionSizeMissing.Error()}, // no data section size + {"EF00010100020200", ErrEOF1DataSectionSizeMissing.Error()}, // data section size incomplete + {"EF0001010002020004", ErrEOF1InvalidTotalSize.Error()}, // no section terminator + {"EF0001010002020004006000", ErrEOF1InvalidTotalSize.Error()}, // no data section contents + {"EF0001010002020004006000AABBCC", ErrEOF1InvalidTotalSize.Error()}, // not complete data section contents + {"EF0001010002020004006000AABBCCDDEE", ErrEOF1InvalidTotalSize.Error()}, // trailing bytes after data + {"EF0001010002020000006000", ErrEOF1EmptyDataSection.Error()}, // 0 size data section + {"EF0001010002020004020004006000AABBCCDDAABBCCDD", ErrEOF1MultipleDataSections.Error()}, // two data sections + {"EF0001010002030004006000AABBCCDD", ErrEOF1UnknownSection.Error()}, // section id = 3 +} + +func TestHasEOFMagic(t *testing.T) { + for _, test := range notEOFTests { + if hasEOFMagic(common.Hex2Bytes(test)) { + t.Errorf("code %v expected to be not EOF", test) + } + } + + for _, test := range eof1ValidTests { + if !hasEOFMagic(common.Hex2Bytes(test.code)) { + t.Errorf("code %v expected to be EOF", test.code) + } + } + + // invalid but still EOF + for _, test := range eof1InvalidTests { + if !hasEOFMagic(common.Hex2Bytes(test.code)) { + t.Errorf("code %v expected to be EOF", test.code) + } + } +} + +func TestReadEOF1Header(t *testing.T) { + for _, test := range eof1ValidTests { + header, err := readEOF1Header(common.Hex2Bytes(test.code)) + if err != nil { + t.Errorf("code %v validation failure, error: %v", test.code, err) + } + if header.codeSize != test.codeSize { + t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize) + } + if header.dataSize != test.dataSize { + t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) + } + } + + for _, test := range eof1InvalidTests { + _, err := readEOF1Header(common.Hex2Bytes(test.code)) + if err == nil { + t.Errorf("code %v expected to be invalid", test.code) + } else if err.Error() != test.error { + t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.error, err.Error()) + } + } +} + +func TestValidateEOF(t *testing.T) { + for _, test := range eof1ValidTests { + if !validateEOF(common.Hex2Bytes(test.code)) { + t.Errorf("code %v expected to be valid", test.code) + } + } + + for _, test := range eof1InvalidTests { + if validateEOF(common.Hex2Bytes(test.code)) { + t.Errorf("code %v expected to be invalid", test.code) + } + } +} + +func TestReadValidEOF1Header(t *testing.T) { + for _, test := range eof1ValidTests { + header := readValidEOF1Header(common.Hex2Bytes(test.code)) + if header.codeSize != test.codeSize { + t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize) + } + if header.dataSize != test.dataSize { + t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) + } + } +} diff --git a/core/vm/errors.go b/core/vm/errors.go index 253974d2344..52c8789e807 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -40,14 +40,30 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidRetsub = errors.New("invalid retsub") ErrReturnStackExceeded = errors.New("return stack limit reached") - ErrInvalidCode = errors.New("invalid code") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrInvalidCodeFormat = errors.New("invalid code: format validation failed") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. errStopToken = errors.New("stop token") ) +// EOF1 validation errors +var ( + ErrEOF1InvalidVersion = errors.New("invalid version byte") + ErrEOF1MultipleCodeSections = errors.New("multiple code sections") + ErrEOF1CodeSectionSizeMissing = errors.New("can't read code section size") + ErrEOF1EmptyCodeSection = errors.New("code section size is 0") + ErrEOF1DataSectionBeforeCodeSection = errors.New("data section before code section") + ErrEOF1MultipleDataSections = errors.New("multiple data sections") + ErrEOF1DataSectionSizeMissing = errors.New("can't read data section size") + ErrEOF1EmptyDataSection = errors.New("data section size is 0") + ErrEOF1UnknownSection = errors.New("unknown section id") + ErrEOF1CodeSectionMissing = errors.New("no code section") + ErrEOF1InvalidTotalSize = errors.New("invalid total size") +) + // ErrStackUnderflow wraps an evm error when the items on the stack less // than the minimal requirement. type ErrStackUnderflow struct { diff --git a/core/vm/evm.go b/core/vm/evm.go index 5f4be15cdf9..e79f163fad0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -246,6 +246,12 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. addrCopy := addr + + var header EOF1Header + if evm.chainRules.IsShanghai && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // 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. codeHash := evm.intraBlockState.GetCodeHash(addrCopy) @@ -257,7 +263,7 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, } else { contract = NewContract(caller, AccountRef(addrCopy), value, gas, evm.config.SkipAnalysis) } - contract.SetCallCode(&addrCopy, codeHash, code) + contract.SetCallCode(&addrCopy, codeHash, code, &header) readOnly := false if callType == STATICCALLT { readOnly = true @@ -368,6 +374,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.config.HasEip3860(evm.chainRules) && len(codeAndHash.code) > params.MaxInitCodeSize { return nil, address, gas, ErrMaxInitCodeSizeExceeded } + // Try to read code header if it claims to be EOF-formatted. + var header EOF1Header + if evm.chainRules.IsShanghai && hasEOFMagic(codeAndHash.code) { + var err error + header, err = readEOF1Header(codeAndHash.code) + if err != nil { + return nil, common.Address{}, gas, ErrInvalidCodeFormat + } + } // Create a new account on the state snapshot := evm.intraBlockState.Snapshot() evm.intraBlockState.CreateAccount(address, true) @@ -379,7 +394,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(address), value, gas, evm.config.SkipAnalysis) - contract.SetCodeOptionalHash(&address, codeAndHash) + contract.SetCodeOptionalHash(&address, codeAndHash, &header) if evm.config.NoRecursion && evm.depth > 0 { return nil, address, gas, nil @@ -390,12 +405,23 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // check whether the max code size has been exceeded maxCodeSizeExceeded := evm.chainRules.IsSpuriousDragon && len(ret) > params.MaxCodeSize && !evm.chainRules.IsAura - // Reject code starting with 0xEF if EIP-3541 is enabled. - if err == nil && !maxCodeSizeExceeded { - if evm.chainRules.IsLondon && len(ret) >= 1 && ret[0] == 0xEF { + if err == nil && hasEOFByte(ret) { + if evm.chainRules.IsShanghai { + // Allow only valid EOF1 if EIP-3540 is enabled. + if hasEOFMagic(ret) { + if !validateEOF(ret) { + err = ErrInvalidCodeFormat + } + } else { + // Reject non-EOF code starting with 0xEF. + err = ErrInvalidCode + } + } else if evm.chainRules.IsLondon { + // Reject code starting with 0xEF in London. err = ErrInvalidCode } } + // if the contract creation ran successfully and no errors were returned // calculate the gas required to store the code. If the code could not // be stored due to not enough gas set an error and let it be handled diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f6e3e45898e..8022a1adda2 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -901,12 +901,13 @@ func makeLog(size int) executionFunc { // opPush1 is a specialized version of pushN func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - codeLen = uint64(len(scope.Contract.Code)) + codeEnd = scope.Contract.CodeEndOffset() integer = new(uint256.Int) ) - *pc++ - if *pc < codeLen { - scope.Stack.Push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + *pc += 1 + dataPos := scope.Contract.CodeBeginOffset() + *pc + if dataPos < codeEnd { + scope.Stack.Push(integer.SetUint64(uint64(scope.Contract.Code[dataPos]))) } else { scope.Stack.Push(integer.Clear()) } @@ -916,15 +917,16 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // make push instruction function func makePush(size uint64, pushByteSize int) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - codeLen := len(scope.Contract.Code) + codeEnd := int(scope.Contract.CodeEndOffset()) - startMin := int(*pc + 1) - if startMin >= codeLen { - startMin = codeLen + pcAbsolute := scope.Contract.CodeBeginOffset() + *pc + startMin := int(pcAbsolute + 1) + if startMin >= codeEnd { + startMin = codeEnd } endMin := startMin + pushByteSize - if startMin+pushByteSize >= codeLen { - endMin = codeLen + if endMin >= codeEnd { + endMin = codeEnd } integer := new(uint256.Int) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 732c65afac7..f874502ba44 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -282,7 +282,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) + op = contract.GetOp(contract.CodeBeginOffset() + pc) operation := in.jt[op] if operation == nil { From ea504330a4703a5bd92d1aeeb7932ec6b6e614e9 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 10 Dec 2021 17:32:07 +0100 Subject: [PATCH 06/49] core/vm: implement EOF EIP-3670: Code Validation --- core/vm/eips.go | 5 ++ core/vm/eof.go | 35 +++++++- core/vm/eof_test.go | 188 ++++++++++++++++++++++++++++++++++++++-- core/vm/errors.go | 26 +++--- core/vm/evm.go | 11 +-- core/vm/instructions.go | 4 + core/vm/jump_table.go | 10 ++- core/vm/opcodes.go | 9 ++ 8 files changed, 259 insertions(+), 29 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index fdf7dbd81d0..775ecceeab2 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -35,6 +35,7 @@ var activators = map[int]func(*JumpTable){ 1884: enable1884, 1344: enable1344, 3540: enable3540, + 3670: enable3670, } // EnableEIP enables the given EIP on the config. @@ -202,3 +203,7 @@ func enable3860(jt *JumpTable) { func enable3540(jt *JumpTable) { // Do nothing. } + +func enable3670(jt *JumpTable) { + // Do nothing. +} diff --git a/core/vm/eof.go b/core/vm/eof.go index 95df5c502cf..70ea35af709 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -135,10 +135,37 @@ outer: return header, nil } -// validateEOF returns true if code has valid format -func validateEOF(code []byte) bool { - _, err := readEOF1Header(code) - return err == nil +// validateInstructions checks that there're no undefined instructions and code ends with a terminating instruction +func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) error { + i := header.CodeBeginOffset() + var opcode OpCode + for i < header.CodeEndOffset() { + opcode = OpCode(code[i]) + if jumpTable[opcode].undefined { + return ErrEOF1UndefinedInstruction + } + if opcode >= PUSH1 && opcode <= PUSH32 { + i += uint64(opcode) - uint64(PUSH1) + 1 + } + i += 1 + } + if !opcode.isTerminating() { + return ErrEOF1TerminatingInstructionMissing + } + return nil +} + +// validateEOF returns true if code has valid format and code section +func validateEOF(code []byte, jumpTable *JumpTable) (EOF1Header, error) { + header, err := readEOF1Header(code) + if err != nil { + return EOF1Header{}, err + } + err = validateInstructions(code, &header, jumpTable) + if err != nil { + return EOF1Header{}, err + } + return header, nil } // readValidEOF1Header parses EOF1-formatted code header, assuming that it is already validated diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index c13970f6ac1..681e999da69 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -30,11 +30,18 @@ type eof1Test struct { var eof1ValidTests = []eof1Test{ {"EF00010100010000", 1, 0}, - {"EF0001010002006000", 2, 0}, - {"EF0001010002020001006000AA", 2, 1}, - {"EF0001010002020004006000AABBCCDD", 2, 4}, - {"EF00010100040200020060006001AABB", 4, 2}, - {"EF000101000602000400600060016002AABBCCDD", 6, 4}, + {"EF0001010002000000", 2, 0}, + {"EF0001010002020001000000AA", 2, 1}, + {"EF0001010002020004000000AABBCCDD", 2, 4}, + {"EF0001010005020002006000600100AABB", 5, 2}, + {"EF00010100070200040060006001600200AABBCCDD", 7, 4}, + {"EF000101000100FE", 1, 0}, // INVALID is defined and can be terminating + {"EF00010100050060006000F3", 5, 0}, // terminating with RETURN + {"EF00010100050060006000FD", 5, 0}, // terminating with REVERT + {"EF0001010003006000FF", 3, 0}, // terminating with SELFDESTRUCT + {"EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", 34, 0}, // PUSH32 + {"EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", 34, 0}, // undefined instructions inside push data + {"EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", 1, 32}, // undefined instructions inside data section } type eof1InvalidTest struct { @@ -84,6 +91,21 @@ var eof1InvalidTests = []eof1InvalidTest{ {"EF0001010002030004006000AABBCCDD", ErrEOF1UnknownSection.Error()}, // section id = 3 } +var eof1InvalidInstructionsTests = []eof1InvalidTest{ + // 0C is undefined instruction + {"EF0001010001000C", ErrEOF1UndefinedInstruction.Error()}, + // EF is undefined instruction + {"EF000101000100EF", ErrEOF1UndefinedInstruction.Error()}, + // ADDRESS is not a terminating instruction + {"EF00010100010030", ErrEOF1TerminatingInstructionMissing.Error()}, + // PUSH1 without data + {"EF00010100010060", ErrEOF1TerminatingInstructionMissing.Error()}, + // PUSH32 with 31 bytes of data + {"EF0001010020007F00000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing.Error()}, + // PUSH32 with 32 bytes of data and no terminating instruction + {"EF0001010021007F0000000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing.Error()}, +} + func TestHasEOFMagic(t *testing.T) { for _, test := range notEOFTests { if hasEOFMagic(common.Hex2Bytes(test)) { @@ -130,14 +152,17 @@ func TestReadEOF1Header(t *testing.T) { } func TestValidateEOF(t *testing.T) { + jt := &mergeInstructionSet for _, test := range eof1ValidTests { - if !validateEOF(common.Hex2Bytes(test.code)) { + _, err := validateEOF(common.Hex2Bytes(test.code), jt) + if err != nil { t.Errorf("code %v expected to be valid", test.code) } } for _, test := range eof1InvalidTests { - if validateEOF(common.Hex2Bytes(test.code)) { + _, err := validateEOF(common.Hex2Bytes(test.code), jt) + if err == nil { t.Errorf("code %v expected to be invalid", test.code) } } @@ -154,3 +179,152 @@ func TestReadValidEOF1Header(t *testing.T) { } } } + +func TestValidateInstructions(t *testing.T) { + jt := &londonInstructionSet + for _, test := range eof1ValidTests { + code := common.Hex2Bytes(test.code) + header, err := readEOF1Header(code) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", test.code, err) + } + + err = validateInstructions(code, &header, jt) + if err != nil { + t.Errorf("code %v instruction validation failure, error: %v", test.code, err) + } + } + + for _, test := range eof1InvalidInstructionsTests { + code := common.Hex2Bytes(test.code) + header, err := readEOF1Header(code) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", test.code, err) + } + + err = validateInstructions(code, &header, jt) + if err == nil { + t.Errorf("code %v expected to be invalid", test.code) + } else if err.Error() != test.error { + t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.error, err.Error()) + } + } +} + +func TestValidateUndefinedInstructions(t *testing.T) { + jt := &londonInstructionSet + code := common.Hex2Bytes("EF0001010002000C00") + instrByte := &code[7] + for opcode := uint16(0); opcode <= 0xff; opcode++ { + if OpCode(opcode) >= PUSH1 && OpCode(opcode) <= PUSH32 { + continue + } + + *instrByte = byte(opcode) + header, err := readEOF1Header(code) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) + } + + err = validateInstructions(code, &header, jt) + if jt[opcode].undefined { + if err == nil { + t.Errorf("opcode %v expected to be invalid", opcode) + } else if err != ErrEOF1UndefinedInstruction { + t.Errorf("opcode %v unxpected error: \"%v\"", opcode, err.Error()) + } + } else { + if err != nil { + t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) + } + } + } +} + +func TestValidateTerminatingInstructions(t *testing.T) { + jt := &londonInstructionSet + code := common.Hex2Bytes("EF0001010001000C") + instrByte := &code[7] + for opcodeValue := uint16(0); opcodeValue <= 0xff; opcodeValue++ { + opcode := OpCode(opcodeValue) + if opcode >= PUSH1 && opcode <= PUSH32 { + continue + } + if jt[opcode].undefined { + continue + } + *instrByte = byte(opcode) + header, err := readEOF1Header(code) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) + } + err = validateInstructions(code, &header, jt) + + if opcode == STOP || opcode == RETURN || opcode == REVERT || opcode == INVALID || opcode == SELFDESTRUCT { + if err != nil { + t.Errorf("opcode %v expected to be valid terminating instruction", opcode) + } + } else { + if err == nil { + t.Errorf("opcode %v expected to be invalid terminating instruction", opcode) + } else if err != ErrEOF1TerminatingInstructionMissing { + t.Errorf("opcode %v unexpected error: \"%v\"", opcode, err.Error()) + } + } + } +} + +func TestValidateTruncatedPush(t *testing.T) { + jt := &londonInstructionSet + zeroes := [33]byte{} + code := common.Hex2Bytes("EF0001010001000C") + for opcode := PUSH1; opcode <= PUSH32; opcode++ { + requiredBytes := opcode - PUSH1 + 1 + + // make code with truncated PUSH data + codeTruncatedPush := append(code, zeroes[:requiredBytes-1]...) + codeTruncatedPush[5] = byte(len(codeTruncatedPush) - 7) + codeTruncatedPush[7] = byte(opcode) + + header, err := readEOF1Header(codeTruncatedPush) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) + } + err = validateInstructions(codeTruncatedPush, &header, jt) + if err == nil { + t.Errorf("code %v has truncated PUSH, expected to be invalid", common.Bytes2Hex(codeTruncatedPush)) + } else if err != ErrEOF1TerminatingInstructionMissing { + t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeTruncatedPush), err) + } + + // make code with full PUSH data but no terminating instruction in the end + codeNotTerminated := append(code, zeroes[:requiredBytes]...) + codeNotTerminated[5] = byte(len(codeNotTerminated) - 7) + codeNotTerminated[7] = byte(opcode) + + header, err = readEOF1Header(codeNotTerminated) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(codeNotTerminated), err) + } + err = validateInstructions(codeTruncatedPush, &header, jt) + if err == nil { + t.Errorf("code %v does not have terminating instruction, expected to be invalid", common.Bytes2Hex(codeNotTerminated)) + } else if err != ErrEOF1TerminatingInstructionMissing { + t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeNotTerminated), err) + } + + // make valid code + codeValid := append(code, zeroes[:requiredBytes+1]...) // + 1 for terminating STOP + codeValid[5] = byte(len(codeValid) - 7) + codeValid[7] = byte(opcode) + + header, err = readEOF1Header(codeValid) + if err != nil { + t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) + } + err = validateInstructions(codeValid, &header, jt) + if err != nil { + t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) + } + } +} diff --git a/core/vm/errors.go b/core/vm/errors.go index 52c8789e807..caad04559d6 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -42,7 +42,7 @@ var ( ErrReturnStackExceeded = errors.New("return stack limit reached") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") - ErrInvalidCodeFormat = errors.New("invalid code: format validation failed") + ErrInvalidEOFCode = errors.New("invalid code: EOF validation failed") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. @@ -51,17 +51,19 @@ var ( // EOF1 validation errors var ( - ErrEOF1InvalidVersion = errors.New("invalid version byte") - ErrEOF1MultipleCodeSections = errors.New("multiple code sections") - ErrEOF1CodeSectionSizeMissing = errors.New("can't read code section size") - ErrEOF1EmptyCodeSection = errors.New("code section size is 0") - ErrEOF1DataSectionBeforeCodeSection = errors.New("data section before code section") - ErrEOF1MultipleDataSections = errors.New("multiple data sections") - ErrEOF1DataSectionSizeMissing = errors.New("can't read data section size") - ErrEOF1EmptyDataSection = errors.New("data section size is 0") - ErrEOF1UnknownSection = errors.New("unknown section id") - ErrEOF1CodeSectionMissing = errors.New("no code section") - ErrEOF1InvalidTotalSize = errors.New("invalid total size") + ErrEOF1InvalidVersion = errors.New("invalid version byte") + ErrEOF1MultipleCodeSections = errors.New("multiple code sections") + ErrEOF1CodeSectionSizeMissing = errors.New("can't read code section size") + ErrEOF1EmptyCodeSection = errors.New("code section size is 0") + ErrEOF1DataSectionBeforeCodeSection = errors.New("data section before code section") + ErrEOF1MultipleDataSections = errors.New("multiple data sections") + ErrEOF1DataSectionSizeMissing = errors.New("can't read data section size") + ErrEOF1EmptyDataSection = errors.New("data section size is 0") + ErrEOF1UnknownSection = errors.New("unknown section id") + ErrEOF1CodeSectionMissing = errors.New("no code section") + ErrEOF1InvalidTotalSize = errors.New("invalid total size") + ErrEOF1UndefinedInstruction = errors.New("undefined instruction") + ErrEOF1TerminatingInstructionMissing = errors.New("code section doesn't end with terminating instruction") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/evm.go b/core/vm/evm.go index e79f163fad0..0959e574f4a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -378,9 +378,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, var header EOF1Header if evm.chainRules.IsShanghai && hasEOFMagic(codeAndHash.code) { var err error - header, err = readEOF1Header(codeAndHash.code) + header, err = validateEOF(codeAndHash.code, evm.interpreter.cfg.JumpTable) if err != nil { - return nil, common.Address{}, gas, ErrInvalidCodeFormat + return nil, common.Address{}, gas, ErrInvalidEOFCode } } // Create a new account on the state @@ -407,10 +407,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err == nil && hasEOFByte(ret) { if evm.chainRules.IsShanghai { - // Allow only valid EOF1 if EIP-3540 is enabled. + // Allow only valid EOF1 if EIP-3540 and EIP-3670 are enabled. if hasEOFMagic(ret) { - if !validateEOF(ret) { - err = ErrInvalidCodeFormat + _, err = validateEOF(ret, evm.interpreter.cfg.JumpTable) + if err != nil { + err = ErrInvalidEOFCode } } else { // Reject non-EOF code starting with 0xEF. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 8022a1adda2..8e587dbfc4c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -858,6 +858,10 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return nil, errStopToken } +func opInvalid(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, &ErrInvalidOpCode{opcode: INVALID} +} + func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { beneficiary := scope.Stack.Pop() callerAddr := scope.Contract.Address() diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index fe6c83dd6d9..e13301dda70 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -49,6 +49,8 @@ type operation struct { memorySize memorySizeFunc writes bool // determines whether this a state modifying operation + + undefined bool } var ( @@ -1436,6 +1438,12 @@ func newFrontierInstructionSet() JumpTable { numPush: 0, memorySize: memoryReturn, }, + INVALID: { + execute: opInvalid, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + }, SELFDESTRUCT: { execute: opSuicide, dynamicGas: gasSelfdestruct, @@ -1450,7 +1458,7 @@ func newFrontierInstructionSet() JumpTable { // Fill all unassigned slots with opUndefined. for i, entry := range tbl { if entry == nil { - tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} + tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0), undefined: true} } } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 59078085d70..b9bf44459ce 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -552,3 +552,12 @@ var stringToOp = map[string]OpCode{ func StringToOp(str string) OpCode { return stringToOp[str] } + +// IsTerminating specifies if an opcode is a valid terminating instruction in EOF. +func (op OpCode) isTerminating() bool { + switch op { + case STOP, RETURN, REVERT, INVALID, SELFDESTRUCT: + return true + } + return false +} From b5e6fabd7c73125b6997ef8d519b7c0c52585f0a Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Tue, 29 Nov 2022 17:20:31 +0200 Subject: [PATCH 07/49] core/vm: handle multiple interpreters on evm EOF invocations --- core/vm/errors.go | 1 + core/vm/evm.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core/vm/errors.go b/core/vm/errors.go index caad04559d6..031d89164f3 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -43,6 +43,7 @@ var ( ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrInvalidEOFCode = errors.New("invalid code: EOF validation failed") + ErrInvalidInterpreter = errors.New("invalid interpreter") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/evm.go b/core/vm/evm.go index 0959e574f4a..98603ab8e53 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -377,8 +377,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Try to read code header if it claims to be EOF-formatted. var header EOF1Header if evm.chainRules.IsShanghai && hasEOFMagic(codeAndHash.code) { + evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + if !ok { + return nil, common.Address{}, gas, ErrInvalidInterpreter + } var err error - header, err = validateEOF(codeAndHash.code, evm.interpreter.cfg.JumpTable) + header, err = validateEOF(codeAndHash.code, evmInterpreter.jt) if err != nil { return nil, common.Address{}, gas, ErrInvalidEOFCode } @@ -409,7 +413,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsShanghai { // Allow only valid EOF1 if EIP-3540 and EIP-3670 are enabled. if hasEOFMagic(ret) { - _, err = validateEOF(ret, evm.interpreter.cfg.JumpTable) + evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + if !ok { + return nil, common.Address{}, gas, ErrInvalidInterpreter + } + _, err = validateEOF(ret, evmInterpreter.jt) if err != nil { err = ErrInvalidEOFCode } From 8bcab93eea283a07a7fcf31c87dfc3f6ce832d48 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 15:47:20 -0600 Subject: [PATCH 08/49] core/vm: implement EOF EIP-4200: Static relative jumps --- core/vm/eips.go | 66 +++++++++++++++++++++++++++ core/vm/eof.go | 28 ++++++++++-- core/vm/eof_test.go | 19 +++++--- core/vm/errors.go | 1 + core/vm/gas.go | 1 + core/vm/instructions_test.go | 87 ++++++++++++++++++++++++++++++++++++ core/vm/interpreter.go | 6 ++- core/vm/jump_table.go | 4 ++ core/vm/opcodes.go | 2 + 9 files changed, 205 insertions(+), 9 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 775ecceeab2..bbf1818baf5 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -17,6 +17,8 @@ package vm import ( + "bytes" + "encoding/binary" "fmt" "sort" @@ -36,6 +38,7 @@ var activators = map[int]func(*JumpTable){ 1344: enable1344, 3540: enable3540, 3670: enable3670, + 4200: enable4200, } // EnableEIP enables the given EIP on the config. @@ -207,3 +210,66 @@ func enable3540(jt *JumpTable) { func enable3670(jt *JumpTable) { // Do nothing. } + +// enable4200 applies EIP-4200 (RJUMP and RJUMPI opcodes) +func enable4200(jt *JumpTable) { + jt[RJUMP] = &operation{ + execute: opRjump, + constantGas: GasFastStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + eof1: true, + } + jt[RJUMPI] = &operation{ + execute: opRjumpi, + constantGas: GasFastishStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + eof1: true, + } +} + +// opRjump implements the RJUMP opcode +func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + idx = scope.Contract.CodeBeginOffset() + *pc + 1 + arg = scope.Contract.Code[idx : idx+2] + relativeOffset int16 + ) + binary.Read(bytes.NewReader(arg), binary.BigEndian, &relativeOffset) + + // Move PC past the RJUMP instruction and its immediate argument. + *pc += 2 + 1 + + // Calculate the new PC given the relative offset. Already validated, + // so no need to verify casts. + *pc = uint64(int64(*pc)+int64(relativeOffset)) - 1 // pc will also be increased by interpreter loop + + 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 + } + + var ( + idx = scope.Contract.CodeBeginOffset() + *pc + 1 + arg = scope.Contract.Code[idx : idx+2] + relativeOffset int16 + ) + binary.Read(bytes.NewReader(arg), binary.BigEndian, &relativeOffset) + + // Move PC past the RJUMP instruction and its immediate argument. + *pc += 2 + 1 + + // Calculate the new PC given the relative offset. Already validated, + // so no need to verify casts. + *pc = uint64(int64(*pc)+int64(relativeOffset)) - 1 // pc will also be increased by interpreter loop + + return nil, nil +} diff --git a/core/vm/eof.go b/core/vm/eof.go index 70ea35af709..40c3acb6a41 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -137,9 +137,13 @@ outer: // validateInstructions checks that there're no undefined instructions and code ends with a terminating instruction func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) error { - i := header.CodeBeginOffset() - var opcode OpCode - for i < header.CodeEndOffset() { + var ( + i = header.CodeBeginOffset() + end = header.CodeEndOffset() + analysis = codeBitmap(code[i : end-1]) + opcode OpCode + ) + for i < end { opcode = OpCode(code[i]) if jumpTable[opcode].undefined { return ErrEOF1UndefinedInstruction @@ -147,6 +151,24 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) if opcode >= PUSH1 && opcode <= PUSH32 { i += uint64(opcode) - uint64(PUSH1) + 1 } + if opcode == RJUMP || opcode == RJUMPI { + var arg int16 + // Read immediate argument. + if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { + return ErrEOF1InvalidRelativeOffset + } + // Check if offfset points to out-of-bounds code + // location. + if (arg < 0 && i+3 < uint64(arg)) || (arg > 0 && end < i+3+uint64(arg)) { + return ErrEOF1InvalidRelativeOffset + } + // Check if offset points to non-code segment. + pos := uint64(int64(i+3) + int64(arg)) + if !analysis.codeSegment(pos - header.CodeBeginOffset()) { + return ErrEOF1InvalidRelativeOffset + } + i += 2 + } i += 1 } if !opcode.isTerminating() { diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 681e999da69..38f4c6af50a 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -104,6 +104,12 @@ var eof1InvalidInstructionsTests = []eof1InvalidTest{ {"EF0001010020007F00000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing.Error()}, // PUSH32 with 32 bytes of data and no terminating instruction {"EF0001010021007F0000000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing.Error()}, + // RJUMP to out-of-bounds (negative) offset. + {"EF0001010004005CFFFA00", ErrEOF1InvalidRelativeOffset.Error()}, + // RJUMP to out-of-bounds (positive) offset. + {"EF0001010004005C000600", ErrEOF1InvalidRelativeOffset.Error()}, + // RJUMP to push data. + {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset.Error()}, } func TestHasEOFMagic(t *testing.T) { @@ -181,7 +187,7 @@ func TestReadValidEOF1Header(t *testing.T) { } func TestValidateInstructions(t *testing.T) { - jt := &londonInstructionSet + jt := &shanghaiInstructionSet for _, test := range eof1ValidTests { code := common.Hex2Bytes(test.code) header, err := readEOF1Header(code) @@ -212,13 +218,16 @@ func TestValidateInstructions(t *testing.T) { } func TestValidateUndefinedInstructions(t *testing.T) { - jt := &londonInstructionSet + jt := &shanghaiInstructionSet code := common.Hex2Bytes("EF0001010002000C00") instrByte := &code[7] for opcode := uint16(0); opcode <= 0xff; opcode++ { if OpCode(opcode) >= PUSH1 && OpCode(opcode) <= PUSH32 { continue } + if OpCode(opcode) == RJUMP || OpCode(opcode) == RJUMPI { + continue + } *instrByte = byte(opcode) header, err := readEOF1Header(code) @@ -242,12 +251,12 @@ func TestValidateUndefinedInstructions(t *testing.T) { } func TestValidateTerminatingInstructions(t *testing.T) { - jt := &londonInstructionSet + jt := &shanghaiInstructionSet code := common.Hex2Bytes("EF0001010001000C") instrByte := &code[7] for opcodeValue := uint16(0); opcodeValue <= 0xff; opcodeValue++ { opcode := OpCode(opcodeValue) - if opcode >= PUSH1 && opcode <= PUSH32 { + if opcode >= PUSH1 && opcode <= PUSH32 || opcode == RJUMP || opcode == RJUMPI { continue } if jt[opcode].undefined { @@ -275,7 +284,7 @@ func TestValidateTerminatingInstructions(t *testing.T) { } func TestValidateTruncatedPush(t *testing.T) { - jt := &londonInstructionSet + jt := &shanghaiInstructionSet zeroes := [33]byte{} code := common.Hex2Bytes("EF0001010001000C") for opcode := PUSH1; opcode <= PUSH32; opcode++ { diff --git a/core/vm/errors.go b/core/vm/errors.go index 031d89164f3..5b3a9ba181c 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -65,6 +65,7 @@ var ( ErrEOF1InvalidTotalSize = errors.New("invalid total size") ErrEOF1UndefinedInstruction = errors.New("undefined instruction") ErrEOF1TerminatingInstructionMissing = errors.New("code section doesn't end with terminating instruction") + ErrEOF1InvalidRelativeOffset = errors.New("relative offset points to immediate argument") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/gas.go b/core/vm/gas.go index 5cf1d852d24..199aeb5a005 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -25,6 +25,7 @@ const ( GasQuickStep uint64 = 2 GasFastestStep uint64 = 3 GasFastStep uint64 = 5 + GasFastishStep uint64 = 7 GasMidStep uint64 = 8 GasSlowStep uint64 = 10 GasExtStep uint64 = 20 diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index bac8604a42d..ebd90e0f127 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -19,8 +19,10 @@ package vm import ( "bytes" + "encoding/binary" "encoding/json" "fmt" + "math/big" "os" "testing" @@ -650,3 +652,88 @@ func TestCreate2Addreses(t *testing.T) { } } } + +func TestRandom(t *testing.T) { + type testcase struct { + name string + random common.Hash + } + + for _, tt := range []testcase{ + {name: "empty hash", random: common.Hash{}}, + {name: "1", random: common.Hash{0}}, + {name: "emptyCodeHash", random: emptyCodeHash}, + {name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})}, + } { + var ( + env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) + } + actual := stack.pop() + expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes())) + if overflow { + t.Errorf("Testcase %v: invalid overflow", tt.name) + } + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual) + } + } +} + +func TestStaticRelativeJumps(t *testing.T) { + type testcase struct { + op string + name string + code string + pc uint64 + condition bool + } + + for _, tt := range []testcase{ + {op: "RJUMP", name: "positive offset", code: "5c000100600100", pc: 4}, + {op: "RJUMP", name: "negative offset", code: "5cfffd00600100", pc: 0}, + {op: "RJUMPI", name: "positive offset", code: "5d000100600100", pc: 4, condition: true}, + {op: "RJUMPI", name: "negative offset", code: "5dfffd00600100", pc: 0, condition: true}, + {op: "RJUMPI", name: "failed condition", code: "5dfffd00600100", pc: 3, condition: false}, + } { + var ( + addr = common.Address{0x42} + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + code = makeEOF1(common.Hex2Bytes(tt.code)) + contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) + header, _ = readEOF1Header(code) + ) + contract.SetCallCode(&addr, common.Hash{}, code, &header) + + if tt.op == "RJUMP" { + opRjump(&pc, evmInterpreter, &ScopeContext{nil, stack, contract}) + } else if tt.op == "RJUMPI" { + if tt.condition { + stack.push(uint256.NewInt(1)) + } else { + stack.push(uint256.NewInt(0)) + } + opRjumpi(&pc, evmInterpreter, &ScopeContext{nil, stack, contract}) + } + + // The pc should be one less than expected because the interpreter will increment it. + if pc != tt.pc-1 { + t.Errorf("%s %s: expected pc to be set to %d, got %d: ", tt.op, tt.name, tt.pc, pc+1) + } + } +} + +func makeEOF1(code []byte) []byte { + header := []byte{0xef, 0x00, 0x01, 0x01, 0xff, 0xff, 0x00} + binary.BigEndian.PutUint16(header[4:6], uint16(len(code))) + return append(header, code...) +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index f874502ba44..3858889f230 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -346,7 +346,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) //nolint:errcheck logged = true } - + // fail if op is not available in legacy context + if operation.eof1 && callContext.Contract.IsLegacy() { + _, err = opUndefined(&pc, in, callContext) + break + } // execute the operation res, err = operation.execute(&pc, in, callContext) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index e13301dda70..03d28e7904e 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -51,6 +51,9 @@ type operation struct { writes bool // determines whether this a state modifying operation undefined bool + + // eof1 specifies if an instrustion should only be available in eof1 + eof1 bool } var ( @@ -84,6 +87,7 @@ func newShanghaiInstructionSet() JumpTable { instructionSet := newLondonInstructionSet() enable3855(&instructionSet) // PUSH0 instruction https://eips.ethereum.org/EIPS/eip-3855 enable3860(&instructionSet) // Limit and meter initcode https://eips.ethereum.org/EIPS/eip-3860 + enable4200(&instructionSet) // Static relative jumps https://eips.ethereum.org/EIPS/eip-4200 return instructionSet } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index b9bf44459ce..51a882106bb 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -120,6 +120,8 @@ const ( MSIZE OpCode = 0x59 GAS OpCode = 0x5a JUMPDEST OpCode = 0x5b + RJUMP OpCode = 0x5c + RJUMPI OpCode = 0x5d PUSH0 OpCode = 0x5f ) From 70dc126bb22bafb003bb97b32390c56355ac63b7 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 8 Nov 2022 10:51:50 -0700 Subject: [PATCH 09/49] core/vm: implement EOF EIP-4750: Functions # Conflicts: # core/vm/analysis.go # core/vm/contract.go # core/vm/evm.go # core/vm/instructions.go # core/vm/instructions_test.go # core/vm/interpreter.go # core/vm/jump_table.go # core/vm/stack.go --- core/vm/analysis.go | 8 ++ core/vm/contract.go | 78 +++++-------- core/vm/eips.go | 74 +++++++++++- core/vm/eof.go | 213 +++++++++++++++++++++++++++-------- core/vm/eof_test.go | 71 +++--------- core/vm/errors.go | 9 +- core/vm/evm.go | 24 ++-- core/vm/instructions.go | 20 ++-- core/vm/instructions_test.go | 107 +++++++++++++++--- core/vm/interpreter.go | 42 ++++++- core/vm/jump_table.go | 1 + core/vm/opcodes.go | 4 +- core/vm/stack.go | 83 ++++++++++++++ 13 files changed, 541 insertions(+), 193 deletions(-) create mode 100644 core/vm/stack.go diff --git a/core/vm/analysis.go b/core/vm/analysis.go index a8520d6f6d0..04e608fcea5 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -26,6 +26,14 @@ func codeBitmap(code []byte) []uint64 { for pc := 0; pc < len(code); { op := OpCode(code[pc]) pc++ + // Short circruit for now on EOF ops with immediates. + // TODO(matt): make EOF-specific code bitmap + if op == RJUMP || op == RJUMPI || op == CALLF { + // TODO CZ: verify that this is correct + bits[pc/64] |= 1 << uint(pc%64) + pc += 4 + continue + } if op >= PUSH1 && op <= PUSH32 { numbits := int(op - PUSH1 + 1) x := uint64(1) << (op - PUSH1) diff --git a/core/vm/contract.go b/core/vm/contract.go index a0c3d4cbb61..3c78cb2106b 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -49,16 +49,15 @@ type Contract struct { caller ContractRef self ContractRef - header EOF1Header - jumpdests map[common.Hash][]uint64 // Aggregated result of JUMPDEST analysis. analysis []uint64 // Locally cached result of JUMPDEST analysis skipAnalysis bool - Code []byte - CodeHash common.Hash - CodeAddr *common.Address - Input []byte + Code []byte + Container *EOF1Container + CodeHash common.Hash + CodeAddr *common.Address + Input []byte Gas uint64 value *uint256.Int @@ -92,11 +91,11 @@ func (c *Contract) validJumpdest(dest *uint256.Int) (bool, bool) { udest, overflow := dest.Uint64WithOverflow() // PC cannot go beyond len(code) and certainly can't be bigger than 64bits. // Don't bother checking for JUMPDEST in that case. - if overflow || udest >= c.CodeSize() { + if overflow || udest >= uint64(len(c.Code)) { return false, false } // Only JUMPDESTs allowed for destinations - if OpCode(c.Code[c.CodeBeginOffset()+udest]) != JUMPDEST { + if OpCode(c.Code[udest]) != JUMPDEST { return false, false } if c.skipAnalysis { @@ -112,6 +111,10 @@ func isCodeFromAnalysis(analysis []uint64, udest uint64) bool { // isCode returns true if the provided PC location is an actual opcode, as // opposed to a data-segment following a PUSHN operation. func (c *Contract) isCode(udest uint64) bool { + if !c.IsLegacy() { + // TODO remove this + panic("shouldn't run jumpdest analysis on EOF") + } // Do we have a contract hash already? // If we do have a hash, that means it's a 'regular' contract. For regular // contracts ( not temporary initcode), we store the analysis in a map @@ -121,8 +124,7 @@ func (c *Contract) isCode(udest uint64) bool { if !exist { // Do the analysis and save in parent context // We do not need to store it in c.analysis - code := c.Code[c.CodeBeginOffset():c.CodeEndOffset()] - analysis = codeBitmap(code) + analysis = codeBitmap(c.Code) c.jumpdests[c.CodeHash] = analysis } // Also stash it in current contract for faster access @@ -135,8 +137,7 @@ func (c *Contract) isCode(udest uint64) bool { // we don't have to recalculate it for every JUMP instruction in the execution // However, we don't save it within the parent context if c.analysis == nil { - code := c.Code[c.CodeBeginOffset():c.CodeEndOffset()] - c.analysis = codeBitmap(code) + c.analysis = codeBitmap(c.Code) } return isCodeFromAnalysis(c.analysis, udest) @@ -154,20 +155,30 @@ func (c *Contract) AsDelegate() *Contract { return c } -// GetOp returns the n'th element in the contract's byte array +// GetOp returns the n'th element in the contract's byte array. func (c *Contract) GetOp(n uint64) OpCode { return OpCode(c.GetByte(n)) } // GetByte returns the n'th byte in the contract's byte array func (c *Contract) GetByte(n uint64) byte { - if n < c.CodeEndOffset() { + if n < uint64(len(c.Code)) { return c.Code[n] } return 0 } +// GetOpInSection returns the n'th element in the code sections's byte array. +func (c *Contract) GetOpInSection(n uint64, s uint64) OpCode { + if n < uint64(c.Container.header.codeSize[s]) { + start := c.Container.code[s] + return OpCode(c.Code[start+n]) + } + + return STOP +} + // Caller returns the caller of the contract. // // Caller will recursively call caller when the contract is a delegate @@ -197,52 +208,23 @@ func (c *Contract) Value() *uint256.Int { // IsLegacy returns true if contract is not EOF func (c *Contract) IsLegacy() bool { - // EOF1 doesn't allow contracts without code section - return c.header.codeSize == 0 -} - -// CodeBeginOffset returns starting offset of the code section -func (c *Contract) CodeBeginOffset() uint64 { - if c.IsLegacy() { - return 0 - } - return c.header.CodeBeginOffset() -} - -// CodeEndOffset returns offset of the code section end -func (c *Contract) CodeEndOffset() uint64 { - if c.IsLegacy() { - return uint64(len(c.Code)) - } - return c.header.CodeEndOffset() -} - -// CodeSize returns the size of the code (if legacy) or code section (if EOF) -func (c *Contract) CodeSize() uint64 { - if c.IsLegacy() { - return uint64(len(c.Code)) - } - return uint64(c.header.codeSize) + return c.Container == nil } // SetCallCode sets the code of the contract and address of the backing data // object -func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, header *EOF1Header) { +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, container *EOF1Container) { c.Code = code + c.Container = container c.CodeHash = hash c.CodeAddr = addr - - c.header.codeSize = header.codeSize - c.header.dataSize = header.dataSize } // SetCodeOptionalHash can be used to provide code, but it's optional to provide hash. // In case hash is not provided, the jumpdest analysis will not be saved to the parent context -func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash, header *EOF1Header) { +func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash, container *EOF1Container) { c.Code = codeAndHash.code + c.Container = container c.CodeHash = codeAndHash.hash c.CodeAddr = addr - - c.header.codeSize = header.codeSize - c.header.dataSize = header.dataSize } diff --git a/core/vm/eips.go b/core/vm/eips.go index bbf1818baf5..c6c19f32046 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -39,6 +39,7 @@ var activators = map[int]func(*JumpTable){ 3540: enable3540, 3670: enable3670, 4200: enable4200, + 4750: enable4750, } // EnableEIP enables the given EIP on the config. @@ -232,7 +233,7 @@ func enable4200(jt *JumpTable) { // opRjump implements the RJUMP opcode func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - idx = scope.Contract.CodeBeginOffset() + *pc + 1 + idx = scope.Contract.Container.code[scope.ActiveSection] + *pc + 1 arg = scope.Contract.Code[idx : idx+2] relativeOffset int16 ) @@ -258,7 +259,7 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } var ( - idx = scope.Contract.CodeBeginOffset() + *pc + 1 + idx = scope.Contract.Container.code[scope.ActiveSection] + *pc + 1 arg = scope.Contract.Code[idx : idx+2] relativeOffset int16 ) @@ -273,3 +274,72 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b return nil, nil } + +// enable4750 applies EIP-4750 (CALLF and RETF opcodes) +func enable4750(jt *JumpTable) { + jt[CALLF] = &operation{ + execute: opCallf, + constantGas: GasMidStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + eof1: true, + } + jt[RETF] = &operation{ + execute: opRetf, + constantGas: GasMidStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + eof1: true, + } +} + +// opCallf implements the CALLF opcode. +func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + idx = scope.Contract.Container.code[scope.ActiveSection] + *pc + 1 + arg = scope.Contract.Code[idx : idx+2] + section int16 + ) + if err := binary.Read(bytes.NewReader(arg), binary.BigEndian, §ion); err != nil { + return nil, err + } + caller := scope.RetStack[len(scope.RetStack)-1] + sig := scope.Contract.Container.types[int(section)] + if scope.Stack.len() < int(caller.StackHeight)+int(sig.input) { + return nil, fmt.Errorf("too few stack items") + } + if len(scope.RetStack) >= 1024 { + return nil, fmt.Errorf("return stack too deep") + } + context := &SubroutineContext{ + Section: scope.ActiveSection, + StackHeight: caller.StackHeight + uint64(scope.Stack.len()), + Pc: *pc, + } + scope.RetStack = append(scope.RetStack, context) + + *pc = 0 + *pc -= 1 // hacks xD + scope.ActiveSection = uint64(section) + scope.Stack.floor = int(context.StackHeight) + + return nil, nil +} + +// opRetf implements the RETF opcode. +func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + sig := scope.Contract.Container.types[scope.ActiveSection] + if scope.Stack.len() < int(sig.output) { + return nil, fmt.Errorf("too few stack items") + } + + last := len(scope.RetStack) - 1 + context := scope.RetStack[last] + scope.RetStack = scope.RetStack[:last] + + *pc = context.Pc - 1 + scope.ActiveSection = context.Section + scope.Stack.floor = int(context.StackHeight) + + return nil, nil +} diff --git a/core/vm/eof.go b/core/vm/eof.go index 40c3acb6a41..d670412a320 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -31,6 +31,7 @@ const ( kindTerminator headerSection = 0 kindCode headerSection = 1 kindData headerSection = 2 + kindType headerSection = 3 versionOffset int = 2 sectionHeaderStart int = 3 @@ -39,8 +40,9 @@ const ( var eofMagic []byte = []byte{0xEF, 0x00} type EOF1Header struct { - codeSize uint16 // Size of code section. Cannot be 0 for EOF1 code. Equals 0 for legacy code. - dataSize uint16 // Size of data section. Equals 0 if data section is absent in EOF1 code. Equals 0 for legacy code. + typeSize uint16 // Size of type section. Must be 2 * n bytes, where n is number of code sections. + codeSize []uint16 // Size of code sections. Cannot be 0 for EOF1 code. Equals 0 for legacy code. + dataSize uint16 // Size of data section. Equals 0 if data section is absent in EOF1 code. Equals 0 for legacy code. } // hasEOFByte returns true if code starts with 0xEF byte @@ -77,7 +79,9 @@ func readEOF1Header(code []byte) (EOF1Header, error) { err error codeRead int dataRead int + typeRead int header EOF1Header + codeSize int ) outer: for i < len(code) { @@ -85,20 +89,36 @@ outer: case kindTerminator: i += 1 break outer - case kindCode: - // Only 1 code section is allowed. - if codeRead != 0 { - return EOF1Header{}, ErrEOF1MultipleCodeSections + case kindType: + // Type section header must be read first. + if codeRead != 0 || dataRead != 0 { + return EOF1Header{}, ErrEOF1TypeSectionHeaderAfterOthers + } + // Only 1 type section is allowed. + if typeRead != 0 { + return EOF1Header{}, ErrEOF1MultipleTypeSections + } + // Size must be present. + if header.typeSize, err = readSectionSize(code, i+1); err != nil { + return EOF1Header{}, ErrEOF1TypeSectionSizeMissing + } + // Type section size must not be 0. + if header.typeSize == 0 { + return EOF1Header{}, ErrEOF1EmptyDataSection } + typeRead += 1 + case kindCode: // Size must be present. - if header.codeSize, err = readSectionSize(code, i+1); err != nil { + size, err := readSectionSize(code, i+1) + if err != nil { return EOF1Header{}, ErrEOF1CodeSectionSizeMissing } // Size must not be 0. - if header.codeSize == 0 { + if size == 0 { return EOF1Header{}, ErrEOF1EmptyCodeSection } - i += 3 + header.codeSize = append(header.codeSize, size) + codeSize += int(size) codeRead += 1 case kindData: // Data section is allowed only after code section. @@ -117,57 +137,73 @@ outer: if header.dataSize == 0 { return EOF1Header{}, ErrEOF1EmptyDataSection } - i += 3 dataRead += 1 default: return EOF1Header{}, ErrEOF1UnknownSection } + i += 3 } // 1 code section is required. - if codeRead != 1 { + if codeRead < 1 { return EOF1Header{}, ErrEOF1CodeSectionMissing } + // 1024 max code sections. + if len(header.codeSize) > 1024 { + return EOF1Header{}, ErrEOF1CodeSectionOverflow + } + // Must have type section if more than one code section. + if len(header.codeSize) > 1 && header.typeSize == 0 { + return EOF1Header{}, ErrEOF1TypeSectionMissing + } // Declared section sizes must correspond to real size (trailing bytes are not allowed.) - if i+int(header.codeSize)+int(header.dataSize) != len(code) { + if i+int(header.typeSize)+codeSize+int(header.dataSize) != len(code) { return EOF1Header{}, ErrEOF1InvalidTotalSize } - return header, nil } // validateInstructions checks that there're no undefined instructions and code ends with a terminating instruction func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) error { var ( - i = header.CodeBeginOffset() - end = header.CodeEndOffset() - analysis = codeBitmap(code[i : end-1]) + i = 0 + analysis = codeBitmap(code) opcode OpCode ) - for i < end { + for i < len(code) { opcode = OpCode(code[i]) if jumpTable[opcode].undefined { return ErrEOF1UndefinedInstruction - } - if opcode >= PUSH1 && opcode <= PUSH32 { - i += uint64(opcode) - uint64(PUSH1) + 1 - } - if opcode == RJUMP || opcode == RJUMPI { + } else if opcode >= PUSH1 && opcode <= PUSH32 { + i += int(opcode) - int(PUSH1) + 1 + } else if opcode == RJUMP || opcode == RJUMPI { var arg int16 // Read immediate argument. if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { return ErrEOF1InvalidRelativeOffset } - // Check if offfset points to out-of-bounds code - // location. - if (arg < 0 && i+3 < uint64(arg)) || (arg > 0 && end < i+3+uint64(arg)) { + // Calculate relative target. + pos := i + 3 + int(arg) + + // Check if offset points to out-of-bounds location. + if pos < 0 || pos >= len(code) { return ErrEOF1InvalidRelativeOffset } // Check if offset points to non-code segment. - pos := uint64(int64(i+3) + int64(arg)) - if !analysis.codeSegment(pos - header.CodeBeginOffset()) { + // TODO(matt): include CALLF and RJUMPs in analysis. + if !analysis.codeSegment(uint64(pos)) { return ErrEOF1InvalidRelativeOffset } i += 2 + } else if opcode == CALLF { + var arg int16 + // Read immediate argument. + if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { + return ErrEOF1InvalidCallfSection + } + if int(arg) >= len(header.codeSize) { + return ErrEOF1InvalidCallfSection + } + i += 2 } i += 1 } @@ -183,36 +219,121 @@ func validateEOF(code []byte, jumpTable *JumpTable) (EOF1Header, error) { if err != nil { return EOF1Header{}, err } - err = validateInstructions(code, &header, jumpTable) - if err != nil { - return EOF1Header{}, err + i := header.Size() + if header.typeSize != 0 { + // TODO(matt): validate type info + i += int(header.typeSize) + } + // Validate each code section. + for _, size := range header.codeSize { + err = validateInstructions(code[i:i+int(size)], &header, jumpTable) + if err != nil { + return EOF1Header{}, err + } + i += int(size) } return header, nil } // readValidEOF1Header parses EOF1-formatted code header, assuming that it is already validated func readValidEOF1Header(code []byte) EOF1Header { - var header EOF1Header - codeSizeOffset := sectionHeaderStart + 1 - header.codeSize = binary.BigEndian.Uint16(code[codeSizeOffset : codeSizeOffset+2]) - if code[codeSizeOffset+2] == 2 { - dataSizeOffset := codeSizeOffset + 3 - header.dataSize = binary.BigEndian.Uint16(code[dataSizeOffset : dataSizeOffset+2]) + var ( + header EOF1Header + i = sectionHeaderStart + codeSections = uint16(1) + ) + // Try to read type section. + if code[i] == byte(kindType) { + size, _ := readSectionSize(code, i+1) + header.typeSize = size + codeSections = size / 2 + i += 3 + } + i += 1 + // Read code sections. + for j := 0; j < int(codeSections); j++ { + size := binary.BigEndian.Uint16(code[i+3*j : i+3*j+2]) + header.codeSize = append(header.codeSize, size) + } + i += int(2 * codeSections) + // Try to read data section. + if code[i] == byte(kindData) { + header.dataSize = binary.BigEndian.Uint16(code[i+1 : i+3]) } return header } -// CodeBeginOffset returns starting offset of the code section -func (header *EOF1Header) CodeBeginOffset() uint64 { - if header.dataSize == 0 { - // len(magic) + version + code_section_id + code_section_size + terminator - return 7 +// Size returns the total size of the EOF1 header. +func (h *EOF1Header) Size() int { + var ( + typeHeaderSize = 0 + codeHeaderSize = len(h.codeSize) * 3 + dataHeaderSize = 0 + ) + if h.typeSize != 0 { + typeHeaderSize = 3 } - // len(magic) + version + code_section_id + code_section_size + data_section_id + data_section_size + terminator - return 10 + if h.dataSize != 0 { + dataHeaderSize = 3 + } + // len(magic) + version + typeHeader + codeHeader + dataHeader + terminator + return 2 + 1 + typeHeaderSize + codeHeaderSize + dataHeaderSize + 1 +} + +type Annotation struct { + input uint8 + output uint8 } -// CodeEndOffset returns offset of the code section end -func (header *EOF1Header) CodeEndOffset() uint64 { - return header.CodeBeginOffset() + uint64(header.codeSize) +type EOF1Container struct { + header EOF1Header + types []Annotation // type defs + code []uint64 // code start offsets + data uint64 // data start offset +} + +func NewEOF1Container(code []byte, jt *JumpTable, validated bool) (EOF1Container, error) { + var ( + container EOF1Container + err error + ) + // If the code has been validated (e.g. during deployment), skip the + // full validation. + if validated { + container.header = readValidEOF1Header(code) + } else { + container.header, err = validateEOF(code, jt) + if err != nil { + return EOF1Container{}, err + } + } + + // Set index to first byte after EOF header. + idx := container.header.Size() + + // Read type section if it exists. + if typeSize := container.header.typeSize; typeSize != 0 { + container.types = readTypeSection(code[idx : idx+int(typeSize)]) + idx += int(typeSize) + } + // Calculate starting offset for each code section. + for _, size := range container.header.codeSize { + container.code = append(container.code, uint64(idx)) + idx += int(size) + } + // Set data offset. + container.data = uint64(idx) + return container, nil +} + +// readTypeSection parses an EOF type section. +func readTypeSection(code []byte) (out []Annotation) { + for i := 0; i < len(code); i += 2 { + sig := Annotation{ + input: code[i], + output: code[i+1], + } + out = append(out, sig) + } + return } diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 38f4c6af50a..195736db9d4 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -75,7 +75,6 @@ var eof1InvalidTests = []eof1InvalidTest{ {"EF000101000200", ErrEOF1InvalidTotalSize.Error()}, // no code section contents {"EF00010100020060", ErrEOF1InvalidTotalSize.Error()}, // not complete code section contents {"EF0001010002006000DEADBEEF", ErrEOF1InvalidTotalSize.Error()}, // trailing bytes after code - {"EF00010100020100020060006000", ErrEOF1MultipleCodeSections.Error()}, // two code sections {"EF000101000000", ErrEOF1EmptyCodeSection.Error()}, // 0 size code section {"EF000101000002000200AABB", ErrEOF1EmptyCodeSection.Error()}, // 0 size code section, with non-0 data section {"EF000102000401000200AABBCCDD6000", ErrEOF1DataSectionBeforeCodeSection.Error()}, // data section before code section @@ -88,7 +87,7 @@ var eof1InvalidTests = []eof1InvalidTest{ {"EF0001010002020004006000AABBCCDDEE", ErrEOF1InvalidTotalSize.Error()}, // trailing bytes after data {"EF0001010002020000006000", ErrEOF1EmptyDataSection.Error()}, // 0 size data section {"EF0001010002020004020004006000AABBCCDDAABBCCDD", ErrEOF1MultipleDataSections.Error()}, // two data sections - {"EF0001010002030004006000AABBCCDD", ErrEOF1UnknownSection.Error()}, // section id = 3 + {"EF00010100020F0004006000AABBCCDD", ErrEOF1UnknownSection.Error()}, // section id = F } var eof1InvalidInstructionsTests = []eof1InvalidTest{ @@ -139,8 +138,8 @@ func TestReadEOF1Header(t *testing.T) { if err != nil { t.Errorf("code %v validation failure, error: %v", test.code, err) } - if header.codeSize != test.codeSize { - t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize) + if header.codeSize[0] != test.codeSize { + t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize[0]) } if header.dataSize != test.dataSize { t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) @@ -162,7 +161,7 @@ func TestValidateEOF(t *testing.T) { for _, test := range eof1ValidTests { _, err := validateEOF(common.Hex2Bytes(test.code), jt) if err != nil { - t.Errorf("code %v expected to be valid", test.code) + t.Errorf("code %v expected to be valid, got %v", test.code, err) } } @@ -177,8 +176,8 @@ func TestValidateEOF(t *testing.T) { func TestReadValidEOF1Header(t *testing.T) { for _, test := range eof1ValidTests { header := readValidEOF1Header(common.Hex2Bytes(test.code)) - if header.codeSize != test.codeSize { - t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize) + if header.codeSize[0] != test.codeSize { + t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize[0]) } if header.dataSize != test.dataSize { t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) @@ -188,27 +187,9 @@ func TestReadValidEOF1Header(t *testing.T) { func TestValidateInstructions(t *testing.T) { jt := &shanghaiInstructionSet - for _, test := range eof1ValidTests { - code := common.Hex2Bytes(test.code) - header, err := readEOF1Header(code) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", test.code, err) - } - - err = validateInstructions(code, &header, jt) - if err != nil { - t.Errorf("code %v instruction validation failure, error: %v", test.code, err) - } - } - for _, test := range eof1InvalidInstructionsTests { code := common.Hex2Bytes(test.code) - header, err := readEOF1Header(code) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", test.code, err) - } - - err = validateInstructions(code, &header, jt) + _, err := validateEOF(code, jt) if err == nil { t.Errorf("code %v expected to be invalid", test.code) } else if err.Error() != test.error { @@ -225,17 +206,12 @@ func TestValidateUndefinedInstructions(t *testing.T) { if OpCode(opcode) >= PUSH1 && OpCode(opcode) <= PUSH32 { continue } - if OpCode(opcode) == RJUMP || OpCode(opcode) == RJUMPI { + if OpCode(opcode) == RJUMP || OpCode(opcode) == RJUMPI || OpCode(opcode) == CALLF { continue } *instrByte = byte(opcode) - header, err := readEOF1Header(code) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) - } - - err = validateInstructions(code, &header, jt) + _, err := validateEOF(code, jt) if jt[opcode].undefined { if err == nil { t.Errorf("opcode %v expected to be invalid", opcode) @@ -256,20 +232,15 @@ func TestValidateTerminatingInstructions(t *testing.T) { instrByte := &code[7] for opcodeValue := uint16(0); opcodeValue <= 0xff; opcodeValue++ { opcode := OpCode(opcodeValue) - if opcode >= PUSH1 && opcode <= PUSH32 || opcode == RJUMP || opcode == RJUMPI { + if opcode >= PUSH1 && opcode <= PUSH32 || opcode == RJUMP || opcode == RJUMPI || opcode == CALLF || opcode == RETF { continue } if jt[opcode].undefined { continue } *instrByte = byte(opcode) - header, err := readEOF1Header(code) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) - } - err = validateInstructions(code, &header, jt) - - if opcode == STOP || opcode == RETURN || opcode == REVERT || opcode == INVALID || opcode == SELFDESTRUCT { + _, err := validateEOF(code, jt) + if opcode.isTerminating() { if err != nil { t.Errorf("opcode %v expected to be valid terminating instruction", opcode) } @@ -295,11 +266,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeTruncatedPush[5] = byte(len(codeTruncatedPush) - 7) codeTruncatedPush[7] = byte(opcode) - header, err := readEOF1Header(codeTruncatedPush) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) - } - err = validateInstructions(codeTruncatedPush, &header, jt) + _, err := validateEOF(codeTruncatedPush, jt) if err == nil { t.Errorf("code %v has truncated PUSH, expected to be invalid", common.Bytes2Hex(codeTruncatedPush)) } else if err != ErrEOF1TerminatingInstructionMissing { @@ -311,11 +278,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeNotTerminated[5] = byte(len(codeNotTerminated) - 7) codeNotTerminated[7] = byte(opcode) - header, err = readEOF1Header(codeNotTerminated) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(codeNotTerminated), err) - } - err = validateInstructions(codeTruncatedPush, &header, jt) + _, err = validateEOF(codeNotTerminated, jt) if err == nil { t.Errorf("code %v does not have terminating instruction, expected to be invalid", common.Bytes2Hex(codeNotTerminated)) } else if err != ErrEOF1TerminatingInstructionMissing { @@ -327,11 +290,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeValid[5] = byte(len(codeValid) - 7) codeValid[7] = byte(opcode) - header, err = readEOF1Header(codeValid) - if err != nil { - t.Errorf("code %v header validation failure, error: %v", common.Bytes2Hex(code), err) - } - err = validateInstructions(codeValid, &header, jt) + _, err = validateEOF(codeValid, jt) if err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) } diff --git a/core/vm/errors.go b/core/vm/errors.go index 5b3a9ba181c..a8d669d1cf3 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -53,7 +53,6 @@ var ( // EOF1 validation errors var ( ErrEOF1InvalidVersion = errors.New("invalid version byte") - ErrEOF1MultipleCodeSections = errors.New("multiple code sections") ErrEOF1CodeSectionSizeMissing = errors.New("can't read code section size") ErrEOF1EmptyCodeSection = errors.New("code section size is 0") ErrEOF1DataSectionBeforeCodeSection = errors.New("data section before code section") @@ -66,6 +65,14 @@ var ( ErrEOF1UndefinedInstruction = errors.New("undefined instruction") ErrEOF1TerminatingInstructionMissing = errors.New("code section doesn't end with terminating instruction") ErrEOF1InvalidRelativeOffset = errors.New("relative offset points to immediate argument") + ErrEOF1TypeSectionMissing = errors.New("no type section") + ErrEOF1TypeSectionInvalidSize = errors.New("invalid type section size") + ErrEOF1CodeSectionOverflow = errors.New("too many code sections") + ErrEOF1MultipleTypeSections = errors.New("multiple type sections") + ErrEOF1TypeSectionHeaderAfterOthers = errors.New("type section header after other section header") + ErrEOF1TypeSectionSizeMissing = errors.New("can't read type section size") + ErrEOF1EmptyTypeSection = errors.New("type section size is 0") + ErrEOF1InvalidCallfSection = errors.New("invalid section in CALLF immediate") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/evm.go b/core/vm/evm.go index 98603ab8e53..bcf4e6c5508 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -245,11 +245,17 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. - addrCopy := addr - - var header EOF1Header + var ( + addrCopy = addr + container *EOF1Container + ) if evm.chainRules.IsShanghai && hasEOFMagic(code) { - header = readValidEOF1Header(code) + evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + if !ok { + return nil, common.Address{}, gas, ErrInvalidInterpreter + } + c, _ := NewEOF1Container(code, evmInterpreter.jt, true) + container = &c } // Initialise a new contract and set the code that is to be used by the EVM. @@ -263,7 +269,7 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, } else { contract = NewContract(caller, AccountRef(addrCopy), value, gas, evm.config.SkipAnalysis) } - contract.SetCallCode(&addrCopy, codeHash, code, &header) + contract.SetCallCode(&addrCopy, codeHash, code, container) readOnly := false if callType == STATICCALLT { readOnly = true @@ -375,17 +381,17 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, address, gas, ErrMaxInitCodeSizeExceeded } // Try to read code header if it claims to be EOF-formatted. - var header EOF1Header + var container *EOF1Container if evm.chainRules.IsShanghai && hasEOFMagic(codeAndHash.code) { evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) if !ok { return nil, common.Address{}, gas, ErrInvalidInterpreter } - var err error - header, err = validateEOF(codeAndHash.code, evmInterpreter.jt) + c, err := NewEOF1Container(codeAndHash.code, evmInterpreter.jt, false) if err != nil { return nil, common.Address{}, gas, ErrInvalidEOFCode } + container = &c } // Create a new account on the state snapshot := evm.intraBlockState.Snapshot() @@ -398,7 +404,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(address), value, gas, evm.config.SkipAnalysis) - contract.SetCodeOptionalHash(&address, codeAndHash, &header) + contract.SetCodeOptionalHash(&address, codeAndHash, container) if evm.config.NoRecursion && evm.depth > 0 { return nil, address, gas, nil diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 8e587dbfc4c..0abb67ff0ad 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -905,11 +905,12 @@ func makeLog(size int) executionFunc { // opPush1 is a specialized version of pushN func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - codeEnd = scope.Contract.CodeEndOffset() + context = scope.RetStack[len(scope.RetStack)-1] + codeEnd = context.CodeOffset + context.CodeLength integer = new(uint256.Int) ) *pc += 1 - dataPos := scope.Contract.CodeBeginOffset() + *pc + dataPos := context.CodeOffset + *pc if dataPos < codeEnd { scope.Stack.Push(integer.SetUint64(uint64(scope.Contract.Code[dataPos]))) } else { @@ -919,12 +920,14 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } // make push instruction function -func makePush(size uint64, pushByteSize int) executionFunc { +func makePush(size uint64, pushByteSize uint64) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - codeEnd := int(scope.Contract.CodeEndOffset()) - - pcAbsolute := scope.Contract.CodeBeginOffset() + *pc - startMin := int(pcAbsolute + 1) + var ( + context = scope.RetStack[len(scope.RetStack)-1] + codeEnd = context.CodeOffset + context.CodeLength + pcAbsolute = context.CodeOffset + *pc + startMin = pcAbsolute + 1 + ) if startMin >= codeEnd { startMin = codeEnd } @@ -935,8 +938,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { integer := new(uint256.Int) scope.Stack.Push(integer.SetBytes(common.RightPadBytes( - // So it doesn't matter what we push onto the stack. - scope.Contract.Code[startMin:endMin], pushByteSize))) + scope.Contract.Code[startMin:endMin], int(pushByteSize)))) *pc += size return nil, nil diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index ebd90e0f127..e82a1ddce86 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -109,7 +109,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.Push(x) stack.Push(y) - opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) if len(stack.Data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.Data)) } @@ -224,7 +224,7 @@ func TestAddMod(t *testing.T) { stack.Push(z) stack.Push(y) stack.Push(x) - opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) actual := stack.Pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -528,12 +528,12 @@ func TestOpMstore(t *testing.T) { pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.PushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.PushN(*new(uint256.Int).SetOne(), *new(uint256.Int)) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -556,7 +556,7 @@ func BenchmarkOpMstore(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.PushN(*value, *memStart) - opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil}) } } @@ -575,7 +575,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.PushN(*uint256.NewInt(32), *start) - opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil, 0, nil}) } } @@ -671,7 +671,7 @@ func TestRandom(t *testing.T) { pc = uint64(0) evmInterpreter = env.interpreter ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -708,21 +708,21 @@ func TestStaticRelativeJumps(t *testing.T) { stack = newstack() pc = uint64(0) evmInterpreter = env.interpreter - code = makeEOF1(common.Hex2Bytes(tt.code)) + code = makeEOF1([][]byte{common.Hex2Bytes(tt.code)}, nil) contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) - header, _ = readEOF1Header(code) + container, _ = NewEOF1Container(code, env.interpreter.cfg.JumpTable, true) ) - contract.SetCallCode(&addr, common.Hash{}, code, &header) + contract.SetCallCode(&addr, common.Hash{}, code, &container) if tt.op == "RJUMP" { - opRjump(&pc, evmInterpreter, &ScopeContext{nil, stack, contract}) + opRjump(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.code[0], uint64(container.header.codeSize[0])}}}) } else if tt.op == "RJUMPI" { if tt.condition { stack.push(uint256.NewInt(1)) } else { stack.push(uint256.NewInt(0)) } - opRjumpi(&pc, evmInterpreter, &ScopeContext{nil, stack, contract}) + opRjumpi(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.code[0], uint64(container.header.codeSize[0])}}}) } // The pc should be one less than expected because the interpreter will increment it. @@ -732,8 +732,83 @@ func TestStaticRelativeJumps(t *testing.T) { } } -func makeEOF1(code []byte) []byte { - header := []byte{0xef, 0x00, 0x01, 0x01, 0xff, 0xff, 0x00} - binary.BigEndian.PutUint16(header[4:6], uint16(len(code))) - return append(header, code...) +func TestEOFFunctions(t *testing.T) { + type testcase struct { + name string + stack int + err bool + } + + code := [][]byte{common.Hex2Bytes("600160025e000100"), common.Hex2Bytes("0149")} + for _, tt := range []testcase{ + {name: "valid callf", stack: 2}, + {name: "stack underflow", stack: 1, err: true}, + {name: "stack too deep", stack: 1, err: true}, + } { + var ( + addr = common.Address{0x42} + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + eofCode = makeEOF1(code, []Annotation{{input: 0, output: 0}, {input: 2, output: 1}}) + contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) + container, _ = NewEOF1Container(eofCode, env.interpreter.cfg.JumpTable, true) + ) + contract.SetCallCode(&addr, common.Hash{}, eofCode, &container) + scope := &ScopeContext{ + Memory: nil, + Stack: stack, + Contract: contract, + ActiveSection: 0, + RetStack: []*SubroutineContext{{0, 0, 0, container.code[0], uint64(container.header.codeSize[0])}}, + } + // Fill stack. + for i := 0; i < tt.stack; i++ { + scope.Stack.push(uint256.NewInt(uint64(i))) + } + pc = 4 + if _, err := opCallf(&pc, evmInterpreter, scope); err == nil && tt.err { + t.Fatalf("expected error, got none") + } else if err != nil && !tt.err { + t.Fatalf("unexpected error: %v", err) + } else if err != nil && tt.err { + continue + } + if len(scope.RetStack) != 2 { + t.Fatalf("subroutine stack not increased") + } + ctx := scope.RetStack[1] + if scope.ActiveSection != 1 { + t.Fatalf("test %v: expected section 1, got %d", tt.name, ctx.Section) + } + if pc+1 != 0 { + t.Fatalf("test %v: expected pc 0, got %d", tt.name, pc) + } + } +} + +func makeEOF1(sections [][]byte, sigs []Annotation) []byte { + out := []byte{0xef, 0x00, 0x01} + // type header + if 0 < len(sigs) { + out = append(out, byte(kindType)) + out = binary.BigEndian.AppendUint16(out, uint16(len(sigs)*2)) + } + // code header + for _, code := range sections { + out = append(out, byte(kindCode)) + out = binary.BigEndian.AppendUint16(out, uint16(len(code))) + } + // terminator + out = append(out, 0x0) + // type section + for _, sig := range sigs { + out = append(out, []byte{sig.input, sig.output}...) + } + // code section + for _, code := range sections { + out = append(out, code...) + } + return out } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 3858889f230..efd1293d485 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -66,9 +66,20 @@ type Interpreter interface { // ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type ScopeContext struct { - Memory *Memory - Stack *stack.Stack - Contract *Contract + Memory *Memory + Stack *stack.Stack + Contract *Contract + ActiveSection uint64 + RetStack []*SubroutineContext +} + +// SubroutineContext contains control-flow information for function subroutines. +type SubroutineContext struct { + Section uint64 + Pc uint64 + StackHeight uint64 + CodeOffset uint64 + CodeLength uint64 } // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports @@ -234,7 +245,17 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( Memory: mem, Stack: locStack, Contract: contract, + RetStack: []*SubroutineContext{ + { + Section: 0, + Pc: 0, + StackHeight: 0, + CodeOffset: 0, + CodeLength: uint64(len(contract.Code)), + }, + }, } + // 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 // to be uint256. Practically much less so feasible. @@ -246,7 +267,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged bool // deferred Tracer should ignore already logged steps res []byte // result of the opcode execution function ) - // Don't move this deferrred function, it's placed before the capturestate-deferred method, + + // The default case is set above, if the contract is actually EOF the + // offset should be updated to beginning of the first code section. + if !contract.IsLegacy() { + callContext.RetStack[0].CodeOffset = contract.Container.code[0] + } + // Don't move this deferred function, it's placed before the capturestate-deferred method, // so that it get's executed _after_: the capturestate needs the stacks before // they are returned to the pools defer func() { @@ -282,7 +309,12 @@ 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(contract.CodeBeginOffset() + pc) + var op OpCode + if contract.IsLegacy() { + op = contract.GetOp(pc) + } else { + op = contract.GetOpInSection(pc, callContext.ActiveSection) + } operation := in.jt[op] if operation == nil { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 03d28e7904e..7918eb396b1 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -88,6 +88,7 @@ func newShanghaiInstructionSet() JumpTable { enable3855(&instructionSet) // PUSH0 instruction https://eips.ethereum.org/EIPS/eip-3855 enable3860(&instructionSet) // Limit and meter initcode https://eips.ethereum.org/EIPS/eip-3860 enable4200(&instructionSet) // Static relative jumps https://eips.ethereum.org/EIPS/eip-4200 + enable4750(&instructionSet) // Functions https://eips.ethereum.org/EIPS/eip-4750 return instructionSet } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 51a882106bb..3ae73748585 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -122,6 +122,8 @@ const ( JUMPDEST OpCode = 0x5b RJUMP OpCode = 0x5c RJUMPI OpCode = 0x5d + CALLF OpCode = 0x5e + RETF OpCode = 0x49 PUSH0 OpCode = 0x5f ) @@ -558,7 +560,7 @@ func StringToOp(str string) OpCode { // IsTerminating specifies if an opcode is a valid terminating instruction in EOF. func (op OpCode) isTerminating() bool { switch op { - case STOP, RETURN, REVERT, INVALID, SELFDESTRUCT: + case STOP, RETURN, REVERT, INVALID, SELFDESTRUCT, RETF: return true } return false diff --git a/core/vm/stack.go b/core/vm/stack.go new file mode 100644 index 00000000000..696edce4454 --- /dev/null +++ b/core/vm/stack.go @@ -0,0 +1,83 @@ +// Copyright 2014 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 ( + "sync" + + "github.com/holiman/uint256" +) + +var stackPool = sync.Pool{ + New: func() interface{} { + return &Stack{data: make([]uint256.Int, 0, 16)} + }, +} + +// Stack is an object for basic stack operations. Items popped to the stack are +// expected to be changed and modified. stack does not take care of adding newly +// initialised objects. +type Stack struct { + data []uint256.Int + floor int +} + +func newstack() *Stack { + return stackPool.Get().(*Stack) +} + +func returnStack(s *Stack) { + s.data = s.data[:0] + stackPool.Put(s) +} + +// Data returns the underlying uint256.Int array. +func (st *Stack) Data() []uint256.Int { + return st.data +} + +func (st *Stack) push(d *uint256.Int) { + // NOTE push limit (1024) is checked in baseCheck + st.data = append(st.data, *d) +} + +func (st *Stack) pop() (ret uint256.Int) { + ret = st.data[len(st.data)-1] + st.data = st.data[:len(st.data)-1] + return +} + +func (st *Stack) len() int { + return len(st.data) - st.floor +} + +func (st *Stack) swap(n int) { + st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n] +} + +func (st *Stack) dup(n int) { + st.push(&st.data[st.len()-n]) +} + +func (st *Stack) peek() *uint256.Int { + return &st.data[st.len()-1] +} + +// Back returns the n'th item in stack +func (st *Stack) Back(n int) *uint256.Int { + return &st.data[st.len()-n-1] +} From 78121051661b655a9b047bcd2ede292bbfa597fb Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Wed, 30 Nov 2022 18:06:25 +0200 Subject: [PATCH 10/49] core/vm: fix merge issues --- core/vm/eips.go | 12 ++++++------ core/vm/eof.go | 2 +- core/vm/evm.go | 2 +- core/vm/stack/stack.go | 5 +++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index c6c19f32046..c9228d86326 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -251,7 +251,7 @@ func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // opRjumpi implements the RJUMPI opcode func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - condition := scope.Stack.pop() + condition := scope.Stack.Pop() if condition.BitLen() == 0 { // Not branching, just skip over immediate argument. *pc += 2 @@ -305,7 +305,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } caller := scope.RetStack[len(scope.RetStack)-1] sig := scope.Contract.Container.types[int(section)] - if scope.Stack.len() < int(caller.StackHeight)+int(sig.input) { + if scope.Stack.Len() < int(caller.StackHeight)+int(sig.input) { return nil, fmt.Errorf("too few stack items") } if len(scope.RetStack) >= 1024 { @@ -313,7 +313,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } context := &SubroutineContext{ Section: scope.ActiveSection, - StackHeight: caller.StackHeight + uint64(scope.Stack.len()), + StackHeight: caller.StackHeight + uint64(scope.Stack.Len()), Pc: *pc, } scope.RetStack = append(scope.RetStack, context) @@ -321,7 +321,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by *pc = 0 *pc -= 1 // hacks xD scope.ActiveSection = uint64(section) - scope.Stack.floor = int(context.StackHeight) + scope.Stack.Floor = int(context.StackHeight) return nil, nil } @@ -329,7 +329,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // opRetf implements the RETF opcode. func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { sig := scope.Contract.Container.types[scope.ActiveSection] - if scope.Stack.len() < int(sig.output) { + if scope.Stack.Len() < int(sig.output) { return nil, fmt.Errorf("too few stack items") } @@ -339,7 +339,7 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt *pc = context.Pc - 1 scope.ActiveSection = context.Section - scope.Stack.floor = int(context.StackHeight) + scope.Stack.Floor = int(context.StackHeight) return nil, nil } diff --git a/core/vm/eof.go b/core/vm/eof.go index d670412a320..c84e1d0d707 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -190,7 +190,7 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) } // Check if offset points to non-code segment. // TODO(matt): include CALLF and RJUMPs in analysis. - if !analysis.codeSegment(uint64(pos)) { + if analysis[uint64(pos)/64]&(uint64(1)<<(uint64(pos)&63)) != 0 { return ErrEOF1InvalidRelativeOffset } i += 2 diff --git a/core/vm/evm.go b/core/vm/evm.go index bcf4e6c5508..06ab666d89d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -252,7 +252,7 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, if evm.chainRules.IsShanghai && hasEOFMagic(code) { evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) if !ok { - return nil, common.Address{}, gas, ErrInvalidInterpreter + return nil, gas, ErrInvalidInterpreter } c, _ := NewEOF1Container(code, evmInterpreter.jt, true) container = &c diff --git a/core/vm/stack/stack.go b/core/vm/stack/stack.go index 9b6e291201e..44c9c25f301 100644 --- a/core/vm/stack/stack.go +++ b/core/vm/stack/stack.go @@ -34,7 +34,8 @@ var stackPool = sync.Pool{ // expected to be changed and modified. stack does not take care of adding newly // initialised objects. type Stack struct { - Data []uint256.Int + Data []uint256.Int + Floor int } func New() *Stack { @@ -87,7 +88,7 @@ func (st *Stack) Reset() { } func (st *Stack) Len() int { - return len(st.Data) + return len(st.Data) - st.Floor } // Print dumps the content of the stack From c2804276d8999d0fa94760b7845607944d71b0ac Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 8 Nov 2022 18:06:37 -0700 Subject: [PATCH 11/49] core/vm: rework eof tests --- core/vm/eof_test.go | 386 ++++++++++++++++++++++++++------------------ 1 file changed, 231 insertions(+), 155 deletions(-) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 195736db9d4..39c8392e73b 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -22,178 +22,254 @@ import ( "github.com/ethereum/go-ethereum/common" ) -type eof1Test struct { - code string - codeSize uint16 - dataSize uint16 -} - -var eof1ValidTests = []eof1Test{ - {"EF00010100010000", 1, 0}, - {"EF0001010002000000", 2, 0}, - {"EF0001010002020001000000AA", 2, 1}, - {"EF0001010002020004000000AABBCCDD", 2, 4}, - {"EF0001010005020002006000600100AABB", 5, 2}, - {"EF00010100070200040060006001600200AABBCCDD", 7, 4}, - {"EF000101000100FE", 1, 0}, // INVALID is defined and can be terminating - {"EF00010100050060006000F3", 5, 0}, // terminating with RETURN - {"EF00010100050060006000FD", 5, 0}, // terminating with REVERT - {"EF0001010003006000FF", 3, 0}, // terminating with SELFDESTRUCT - {"EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", 34, 0}, // PUSH32 - {"EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", 34, 0}, // undefined instructions inside push data - {"EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", 1, 32}, // undefined instructions inside data section -} - -type eof1InvalidTest struct { - code string - error string -} - -// Codes starting with something else other than magic -var notEOFTests = []string{ - // valid: "EF0001010002020004006000AABBCCDD", - "", - "FE", // invalid first byte - "FE0001010002020004006000AABBCCDD", // valid except first byte of magic - "EF", // incomplete magic - "EF01", // not correct magic - "EF0101010002020004006000AABBCCDD", // valid except second byte of magic -} - -// Codes starting with magic, but the rest is invalid -var eof1InvalidTests = []eof1InvalidTest{ - // valid: {"EF0001010002020004006000AABBCCDD", nil}, - {"EF00", ErrEOF1InvalidVersion.Error()}, // no version - {"EF0000", ErrEOF1InvalidVersion.Error()}, // invalid version - {"EF0002", ErrEOF1InvalidVersion.Error()}, // invalid version - {"EF0000010002020004006000AABBCCDD", ErrEOF1InvalidVersion.Error()}, // valid except version - {"EF0001", ErrEOF1CodeSectionMissing.Error()}, // no header - {"EF000100", ErrEOF1CodeSectionMissing.Error()}, // no code section - {"EF000101", ErrEOF1CodeSectionSizeMissing.Error()}, // no code section size - {"EF00010100", ErrEOF1CodeSectionSizeMissing.Error()}, // code section size incomplete - {"EF0001010002", ErrEOF1InvalidTotalSize.Error()}, // no section terminator - {"EF000101000200", ErrEOF1InvalidTotalSize.Error()}, // no code section contents - {"EF00010100020060", ErrEOF1InvalidTotalSize.Error()}, // not complete code section contents - {"EF0001010002006000DEADBEEF", ErrEOF1InvalidTotalSize.Error()}, // trailing bytes after code - {"EF000101000000", ErrEOF1EmptyCodeSection.Error()}, // 0 size code section - {"EF000101000002000200AABB", ErrEOF1EmptyCodeSection.Error()}, // 0 size code section, with non-0 data section - {"EF000102000401000200AABBCCDD6000", ErrEOF1DataSectionBeforeCodeSection.Error()}, // data section before code section - {"EF0001020004AABBCCDD", ErrEOF1DataSectionBeforeCodeSection.Error()}, // data section without code section - {"EF000101000202", ErrEOF1DataSectionSizeMissing.Error()}, // no data section size - {"EF00010100020200", ErrEOF1DataSectionSizeMissing.Error()}, // data section size incomplete - {"EF0001010002020004", ErrEOF1InvalidTotalSize.Error()}, // no section terminator - {"EF0001010002020004006000", ErrEOF1InvalidTotalSize.Error()}, // no data section contents - {"EF0001010002020004006000AABBCC", ErrEOF1InvalidTotalSize.Error()}, // not complete data section contents - {"EF0001010002020004006000AABBCCDDEE", ErrEOF1InvalidTotalSize.Error()}, // trailing bytes after data - {"EF0001010002020000006000", ErrEOF1EmptyDataSection.Error()}, // 0 size data section - {"EF0001010002020004020004006000AABBCCDDAABBCCDD", ErrEOF1MultipleDataSections.Error()}, // two data sections - {"EF00010100020F0004006000AABBCCDD", ErrEOF1UnknownSection.Error()}, // section id = F -} - -var eof1InvalidInstructionsTests = []eof1InvalidTest{ - // 0C is undefined instruction - {"EF0001010001000C", ErrEOF1UndefinedInstruction.Error()}, - // EF is undefined instruction - {"EF000101000100EF", ErrEOF1UndefinedInstruction.Error()}, - // ADDRESS is not a terminating instruction - {"EF00010100010030", ErrEOF1TerminatingInstructionMissing.Error()}, - // PUSH1 without data - {"EF00010100010060", ErrEOF1TerminatingInstructionMissing.Error()}, - // PUSH32 with 31 bytes of data - {"EF0001010020007F00000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing.Error()}, - // PUSH32 with 32 bytes of data and no terminating instruction - {"EF0001010021007F0000000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing.Error()}, - // RJUMP to out-of-bounds (negative) offset. - {"EF0001010004005CFFFA00", ErrEOF1InvalidRelativeOffset.Error()}, - // RJUMP to out-of-bounds (positive) offset. - {"EF0001010004005C000600", ErrEOF1InvalidRelativeOffset.Error()}, - // RJUMP to push data. - {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset.Error()}, -} - func TestHasEOFMagic(t *testing.T) { - for _, test := range notEOFTests { - if hasEOFMagic(common.Hex2Bytes(test)) { - t.Errorf("code %v expected to be not EOF", test) - } - } - - for _, test := range eof1ValidTests { - if !hasEOFMagic(common.Hex2Bytes(test.code)) { - t.Errorf("code %v expected to be EOF", test.code) - } - } - - // invalid but still EOF - for _, test := range eof1InvalidTests { - if !hasEOFMagic(common.Hex2Bytes(test.code)) { - t.Errorf("code %v expected to be EOF", test.code) + for i, test := range []struct { + code string + valid bool + }{ + {"EF00010100010000", true}, + {"EF0001010002000000", true}, + // invalid EOF but still has magic + {"EF00010100020F0004006000AABBCCDD", true}, + // empty + {"", false}, + // invalid first byte + {"FE", false}, + // valid except first byte of magic + {"FE0001010002020004006000AABBCCDD", false}, + // incomplete magic + {"EF", false}, + // not correct magic + {"EF01", false}, + // valid except second byte of magic + {"EF0101010002020004006000AABBCCDD", false}, + } { + if hasEOFMagic(common.Hex2Bytes(test.code)) != test.valid { + t.Errorf("test %d: code %v expected to be EOF", i, test.code) } } } -func TestReadEOF1Header(t *testing.T) { - for _, test := range eof1ValidTests { +func TestEOFContainer(t *testing.T) { + for i, test := range []struct { + code string + codeSize uint16 + dataSize uint16 + err error + }{ + { + code: "EF00010100010000", + codeSize: 1, + dataSize: 0, + }, + { + code: "EF0001010002000000", + codeSize: 2, + dataSize: 0, + }, + { + code: "EF0001010002020001000000AA", + codeSize: 2, + dataSize: 1, + }, + { + code: "EF0001010002020004000000AABBCCDD", + codeSize: 2, + dataSize: 4, + }, + { + code: "EF0001010005020002006000600100AABB", + codeSize: 5, + dataSize: 2, + }, + { + code: "EF00010100070200040060006001600200AABBCCDD", + codeSize: 7, + dataSize: 4, + }, + { // INVALID is defined and can be terminating + code: "EF000101000100FE", + codeSize: 1, + dataSize: 0, + }, + { // terminating with RETURN + code: "EF00010100050060006000F3", + codeSize: 5, + dataSize: 0, + }, + { // terminating with REVERT + code: "EF00010100050060006000FD", + codeSize: 5, + dataSize: 0, + }, + { // terminating with SELFDESTRUCT + code: "EF0001010003006000FF", + codeSize: 3, + dataSize: 0, + }, + { // PUSH32 + code: "EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", + codeSize: 34, + dataSize: 0, + }, + { // undefined instructions inside push data + code: "EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", + codeSize: 34, + dataSize: 0, + }, + { // undefined instructions inside data section + code: "EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", + codeSize: 1, + dataSize: 32, + }, + { // no version + code: "EF00", + err: ErrEOF1InvalidVersion, + }, + { // invalid version + code: "EF0000", + err: ErrEOF1InvalidVersion, + }, + { // invalid version + code: "EF0002", + err: ErrEOF1InvalidVersion, + }, + { // valid except version + code: "EF0000010002020004006000AABBCCDD", + err: ErrEOF1InvalidVersion, + }, + { // no header + code: "EF0001", + err: ErrEOF1CodeSectionMissing, + }, + { // no code section + code: "EF000100", + err: ErrEOF1CodeSectionMissing, + }, + { // no code section size + code: "EF000101", + err: ErrEOF1CodeSectionSizeMissing, + }, + { // code section size incomplete + code: "EF00010100", + err: ErrEOF1CodeSectionSizeMissing, + }, + { // no section terminator + code: "EF0001010002", + err: ErrEOF1InvalidTotalSize, + }, + { // no code section contents + code: "EF000101000200", + err: ErrEOF1InvalidTotalSize, + }, + { // not complete code section contents + code: "EF00010100020060", + err: ErrEOF1InvalidTotalSize, + }, + { // trailing bytes after code + code: "EF0001010002006000DEADBEEF", + err: ErrEOF1InvalidTotalSize, + }, + { // 0 size code section + code: "EF000101000000", + err: ErrEOF1EmptyCodeSection, + }, + { // 0 size code section, with non-0 data section + code: "EF000101000002000200AABB", + err: ErrEOF1EmptyCodeSection, + }, + { // data section before code section + code: "EF000102000401000200AABBCCDD6000", + err: ErrEOF1DataSectionBeforeCodeSection, + }, + { // data section without code section + code: "EF0001020004AABBCCDD", + err: ErrEOF1DataSectionBeforeCodeSection, + }, + { // no data section size + code: "EF000101000202", + err: ErrEOF1DataSectionSizeMissing, + }, + { // data section size incomplete + code: "EF00010100020200", + err: ErrEOF1DataSectionSizeMissing, + }, + { // no section terminator + code: "EF0001010002020004", + err: ErrEOF1InvalidTotalSize, + }, + { // no data section contents + code: "EF0001010002020004006000", + err: ErrEOF1InvalidTotalSize, + }, + { // not complete data section contents + code: "EF0001010002020004006000AABBCC", + err: ErrEOF1InvalidTotalSize, + }, + { // trailing bytes after data + code: "EF0001010002020004006000AABBCCDDEE", + err: ErrEOF1InvalidTotalSize, + }, + { // 0 size data section + code: "EF0001010002020000006000", + err: ErrEOF1EmptyDataSection, + }, + { // two data sections + code: "EF0001010002020004020004006000AABBCCDDAABBCCDD", + err: ErrEOF1MultipleDataSections, + }, + { // section id = F + code: "EF00010100020F0004006000AABBCCDD", + err: ErrEOF1UnknownSection, + }, + } { header, err := readEOF1Header(common.Hex2Bytes(test.code)) if err != nil { - t.Errorf("code %v validation failure, error: %v", test.code, err) - } - if header.codeSize[0] != test.codeSize { - t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize[0]) - } - if header.dataSize != test.dataSize { - t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) - } - } - - for _, test := range eof1InvalidTests { - _, err := readEOF1Header(common.Hex2Bytes(test.code)) - if err == nil { - t.Errorf("code %v expected to be invalid", test.code) - } else if err.Error() != test.error { - t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.error, err.Error()) - } - } -} - -func TestValidateEOF(t *testing.T) { - jt := &mergeInstructionSet - for _, test := range eof1ValidTests { - _, err := validateEOF(common.Hex2Bytes(test.code), jt) - if err != nil { - t.Errorf("code %v expected to be valid, got %v", test.code, err) - } - } - - for _, test := range eof1InvalidTests { - _, err := validateEOF(common.Hex2Bytes(test.code), jt) - if err == nil { - t.Errorf("code %v expected to be invalid", test.code) + if err == test.err { + // failed as expected + continue + } else { + t.Errorf("test %d: code %v validation failure, error: %v", i, test.code, err) + } } - } -} - -func TestReadValidEOF1Header(t *testing.T) { - for _, test := range eof1ValidTests { - header := readValidEOF1Header(common.Hex2Bytes(test.code)) if header.codeSize[0] != test.codeSize { - t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize[0]) + t.Errorf("test %d: code %v codeSize expected %v, got %v", i, test.code, test.codeSize, header.codeSize[0]) } if header.dataSize != test.dataSize { - t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) + t.Errorf("test %d: code %v dataSize expected %v, got %v", i, test.code, test.dataSize, header.dataSize) } } } -func TestValidateInstructions(t *testing.T) { - jt := &shanghaiInstructionSet - for _, test := range eof1InvalidInstructionsTests { - code := common.Hex2Bytes(test.code) - _, err := validateEOF(code, jt) +func TestInvalidInstructions(t *testing.T) { + for _, test := range []struct { + code string + err error + }{ + // 0C is undefined instruction + {"EF0001010001000C", ErrEOF1UndefinedInstruction}, + // EF is undefined instruction + {"EF000101000100EF", ErrEOF1UndefinedInstruction}, + // ADDRESS is not a terminating instruction + {"EF00010100010030", ErrEOF1TerminatingInstructionMissing}, + // PUSH1 without data + {"EF00010100010060", ErrEOF1TerminatingInstructionMissing}, + // PUSH32 with 31 bytes of data + {"EF0001010020007F00000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing}, + // PUSH32 with 32 bytes of data and no terminating instruction + {"EF0001010021007F0000000000000000000000000000000000000000000000000000000000000000", ErrEOF1TerminatingInstructionMissing}, + // RJUMP to out-of-bounds (negative) offset. + {"EF0001010004005CFFFA00", ErrEOF1InvalidRelativeOffset}, + // RJUMP to out-of-bounds (positive) offset. + {"EF0001010004005C000600", ErrEOF1InvalidRelativeOffset}, + // RJUMP to push data. + {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset}, + } { + _, err := validateEOF(common.Hex2Bytes(test.code), &shanghaiInstructionSet) if err == nil { t.Errorf("code %v expected to be invalid", test.code) - } else if err.Error() != test.error { - t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.error, err.Error()) + } else if err.Error() != test.err.Error() { + t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.err, err.Error()) } } } From 03aa0b1c4f130877a908197607d98c7782023122 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Wed, 9 Nov 2022 09:11:17 -0700 Subject: [PATCH 12/49] core/vm: test eof container object --- core/vm/eof.go | 8 +- core/vm/eof_test.go | 185 +++++++++++++++++++++++++++++++------------- 2 files changed, 135 insertions(+), 58 deletions(-) diff --git a/core/vm/eof.go b/core/vm/eof.go index c84e1d0d707..26ba609a3d6 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -172,7 +172,7 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) for i < len(code) { opcode = OpCode(code[i]) if jumpTable[opcode].undefined { - return ErrEOF1UndefinedInstruction + return fmt.Errorf("%v: %v", ErrEOF1UndefinedInstruction, opcode) } else if opcode >= PUSH1 && opcode <= PUSH32 { i += int(opcode) - int(PUSH1) + 1 } else if opcode == RJUMP || opcode == RJUMPI { @@ -198,17 +198,17 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) var arg int16 // Read immediate argument. if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { - return ErrEOF1InvalidCallfSection + return fmt.Errorf("%v: %v", ErrEOF1InvalidCallfSection, err) } if int(arg) >= len(header.codeSize) { - return ErrEOF1InvalidCallfSection + return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, len(header.codeSize)) } i += 2 } i += 1 } if !opcode.isTerminating() { - return ErrEOF1TerminatingInstructionMissing + return fmt.Errorf("%v: %v", ErrEOF1TerminatingInstructionMissing, opcode) } return nil } diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 39c8392e73b..6c12d1e89d9 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -17,6 +17,8 @@ package vm import ( + "fmt" + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -52,75 +54,105 @@ func TestHasEOFMagic(t *testing.T) { func TestEOFContainer(t *testing.T) { for i, test := range []struct { - code string - codeSize uint16 - dataSize uint16 - err error + code string + typeSize int + codeSize []int + dataSize int + types [][]int + codeOffsets []int + data int + err error }{ { - code: "EF00010100010000", - codeSize: 1, - dataSize: 0, + code: "EF00010100010000", + codeSize: []int{1}, + dataSize: 0, + codeOffsets: []int{7}, }, { - code: "EF0001010002000000", - codeSize: 2, - dataSize: 0, + code: "EF0001010002000000", + codeSize: []int{2}, + dataSize: 0, + codeOffsets: []int{7}, }, { - code: "EF0001010002020001000000AA", - codeSize: 2, - dataSize: 1, + code: "EF0001010002020001000000AA", + codeSize: []int{2}, + dataSize: 1, + codeOffsets: []int{10}, + data: 12, }, { - code: "EF0001010002020004000000AABBCCDD", - codeSize: 2, - dataSize: 4, + code: "EF0001010002020004000000AABBCCDD", + codeSize: []int{2}, + dataSize: 4, + codeOffsets: []int{10}, + data: 12, }, { - code: "EF0001010005020002006000600100AABB", - codeSize: 5, - dataSize: 2, + code: "EF0001010005020002006000600100AABB", + codeSize: []int{5}, + dataSize: 2, + codeOffsets: []int{10}, + data: 15, }, { - code: "EF00010100070200040060006001600200AABBCCDD", - codeSize: 7, - dataSize: 4, + code: "EF00010100070200040060006001600200AABBCCDD", + codeSize: []int{7}, + dataSize: 4, + codeOffsets: []int{10}, + data: 17, + }, + { + code: "EF00010300040100080100020000000201600160025e0001000149", + codeSize: []int{8, 2}, + dataSize: 0, + typeSize: 4, + codeOffsets: []int{17, 25}, + types: [][]int{{0, 0}, {2, 1}}, }, { // INVALID is defined and can be terminating - code: "EF000101000100FE", - codeSize: 1, - dataSize: 0, + code: "EF000101000100FE", + codeSize: []int{1}, + dataSize: 0, + codeOffsets: []int{7}, }, { // terminating with RETURN - code: "EF00010100050060006000F3", - codeSize: 5, - dataSize: 0, + code: "EF00010100050060006000F3", + codeSize: []int{5}, + dataSize: 0, + codeOffsets: []int{7}, }, { // terminating with REVERT - code: "EF00010100050060006000FD", - codeSize: 5, - dataSize: 0, + code: "EF00010100050060006000FD", + codeSize: []int{5}, + dataSize: 0, + codeOffsets: []int{7}, }, { // terminating with SELFDESTRUCT - code: "EF0001010003006000FF", - codeSize: 3, - dataSize: 0, + code: "EF0001010003006000FF", + codeSize: []int{3}, + dataSize: 0, + codeOffsets: []int{7}, }, { // PUSH32 - code: "EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", - codeSize: 34, - dataSize: 0, + code: "EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", + codeSize: []int{34}, + dataSize: 0, + codeOffsets: []int{7}, }, { // undefined instructions inside push data - code: "EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", - codeSize: 34, - dataSize: 0, + code: "EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", + codeSize: []int{34}, + dataSize: 0, + codeOffsets: []int{7}, }, { // undefined instructions inside data section - code: "EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", - codeSize: 1, - dataSize: 32, + code: "EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", + codeSize: []int{1}, + dataSize: 32, + codeOffsets: []int{10}, + data: 11, }, { // no version code: "EF00", @@ -223,7 +255,18 @@ func TestEOFContainer(t *testing.T) { err: ErrEOF1UnknownSection, }, } { - header, err := readEOF1Header(common.Hex2Bytes(test.code)) + var ( + jt = &shanghaiInstructionSet + code = common.Hex2Bytes(test.code) + ) + + // Need to validate both code paths. First is the "trusted" EOF + // code that was validated before being stored. This can't + // throw and error. The other path is "untrusted" and so the + // code is validated as it is parsed. This may throw an error. + // If the error is expected, we compare the errors and continue + // on. + con1, err := NewEOF1Container(code, jt, false) if err != nil { if err == test.err { // failed as expected @@ -232,12 +275,46 @@ func TestEOFContainer(t *testing.T) { t.Errorf("test %d: code %v validation failure, error: %v", i, test.code, err) } } - if header.codeSize[0] != test.codeSize { - t.Errorf("test %d: code %v codeSize expected %v, got %v", i, test.code, test.codeSize, header.codeSize[0]) - } - if header.dataSize != test.dataSize { - t.Errorf("test %d: code %v dataSize expected %v, got %v", i, test.code, test.dataSize, header.dataSize) + con2, _ := NewEOF1Container(code, jt, true) + + validate := func(c EOF1Container, name string) { + if len(c.header.codeSize) != len(test.codeSize) { + t.Errorf("%s unexpected number of code sections (want: %d, got %d)", name, len(test.codeSize), len(c.header.codeSize)) + } + if len(c.code) != len(test.codeOffsets) { + t.Errorf("%s unexpected number of code offsets (want: %d, got %d)", name, len(test.codeOffsets), len(c.code)) + } + if len(c.code) != len(c.header.codeSize) { + t.Errorf("%s code offsets does not match code sizes (want: %d, got %d)", name, len(c.header.codeSize), len(c.code)) + } + for j := 0; j < len(c.header.codeSize); j++ { + if test.codeSize != nil && c.header.codeSize[j] != uint16(test.codeSize[j]) { + t.Errorf("%s codeSize expected %v, got %v", name, test.codeSize[j], c.header.codeSize[j]) + } + if test.codeOffsets != nil && c.code[j] != uint64(test.codeOffsets[j]) { + t.Errorf("%s code offset expected %v, got %v", name, test.codeOffsets[j], c.code[j]) + } + } + if c.header.dataSize != uint16(test.dataSize) { + t.Errorf("%s dataSize expected %v, got %v", name, test.dataSize, c.header.dataSize) + } + if c.header.dataSize != 0 && c.data != uint64(test.data) { + t.Errorf("%s data offset expected %v, got %v", name, test.data, c.data) + } + if c.header.typeSize != uint16(test.typeSize) { + t.Errorf("%s typeSize expected %v, got %v", name, test.typeSize, c.header.typeSize) + } + if c.header.typeSize != 0 && len(c.types) != len(test.types) { + t.Errorf("%s unexpected number of types %v, want %v", name, len(c.types), len(test.types)) + } + for j, ty := range c.types { + if int(ty.input) != test.types[j][0] || int(ty.output) != test.types[j][1] { + t.Errorf("%s types annotation mismatch (want {%d, %d}, got {%d, %d}", name, test.types[j][0], test.types[j][1], ty.input, ty.output) + } + } } + validate(con1, fmt.Sprintf("test %2d (validated): code %v", i, test.code)) + validate(con2, fmt.Sprintf("test %2d (not validated): code %v", i, test.code)) } } @@ -268,7 +345,7 @@ func TestInvalidInstructions(t *testing.T) { _, err := validateEOF(common.Hex2Bytes(test.code), &shanghaiInstructionSet) if err == nil { t.Errorf("code %v expected to be invalid", test.code) - } else if err.Error() != test.err.Error() { + } else if !strings.HasPrefix(err.Error(), test.err.Error()) { t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.err, err.Error()) } } @@ -291,7 +368,7 @@ func TestValidateUndefinedInstructions(t *testing.T) { if jt[opcode].undefined { if err == nil { t.Errorf("opcode %v expected to be invalid", opcode) - } else if err != ErrEOF1UndefinedInstruction { + } else if !strings.HasPrefix(err.Error(), ErrEOF1UndefinedInstruction.Error()) { t.Errorf("opcode %v unxpected error: \"%v\"", opcode, err.Error()) } } else { @@ -323,7 +400,7 @@ func TestValidateTerminatingInstructions(t *testing.T) { } else { if err == nil { t.Errorf("opcode %v expected to be invalid terminating instruction", opcode) - } else if err != ErrEOF1TerminatingInstructionMissing { + } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("opcode %v unexpected error: \"%v\"", opcode, err.Error()) } } @@ -345,7 +422,7 @@ func TestValidateTruncatedPush(t *testing.T) { _, err := validateEOF(codeTruncatedPush, jt) if err == nil { t.Errorf("code %v has truncated PUSH, expected to be invalid", common.Bytes2Hex(codeTruncatedPush)) - } else if err != ErrEOF1TerminatingInstructionMissing { + } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeTruncatedPush), err) } @@ -357,7 +434,7 @@ func TestValidateTruncatedPush(t *testing.T) { _, err = validateEOF(codeNotTerminated, jt) if err == nil { t.Errorf("code %v does not have terminating instruction, expected to be invalid", common.Bytes2Hex(codeNotTerminated)) - } else if err != ErrEOF1TerminatingInstructionMissing { + } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeNotTerminated), err) } From 705ea5d9ebcdd7a02b104a369173986c16ef100b Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Wed, 9 Nov 2022 11:52:39 -0700 Subject: [PATCH 13/49] core/vm: refactor eof structures --- core/vm/eof.go | 167 ++++++++++++++++++++++---------------------- core/vm/eof_test.go | 12 ++-- core/vm/evm.go | 2 +- 3 files changed, 90 insertions(+), 91 deletions(-) diff --git a/core/vm/eof.go b/core/vm/eof.go index 26ba609a3d6..46b4d1d6cc7 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -39,12 +39,6 @@ const ( var eofMagic []byte = []byte{0xEF, 0x00} -type EOF1Header struct { - typeSize uint16 // Size of type section. Must be 2 * n bytes, where n is number of code sections. - codeSize []uint16 // Size of code sections. Cannot be 0 for EOF1 code. Equals 0 for legacy code. - dataSize uint16 // Size of data section. Equals 0 if data section is absent in EOF1 code. Equals 0 for legacy code. -} - // hasEOFByte returns true if code starts with 0xEF byte func hasEOFByte(code []byte) bool { return len(code) != 0 && code[0] == eofFormatByte @@ -61,16 +55,56 @@ func isEOFVersion1(code []byte) bool { return versionOffset < len(code) && code[versionOffset] == byte(eof1Version) } -// readSectionSize returns the size of the section at the offset i. -func readSectionSize(code []byte, i int) (uint16, error) { - if len(code) < i+2 { - return 0, fmt.Errorf("section size missing") +type EOF1Header struct { + typeSize uint16 // Size of type section. Must be 2 * n bytes, where n is number of code sections. + codeSize []uint16 // Size of code sections. Cannot be 0 for EOF1 code. Equals 0 for legacy code. + dataSize uint16 // Size of data section. Equals 0 if data section is absent in EOF1 code. Equals 0 for legacy code. +} + +func NewEOF1Header(code []byte, jt *JumpTable, validated bool) (EOF1Header, error) { + if validated { + return readEOF1Header(code), nil } - return binary.BigEndian.Uint16(code[i : i+2]), nil + + header, err := readUncheckedEOF1Header(code) + if err != nil { + return EOF1Header{}, err + } + i := header.Size() + if header.typeSize != 0 { + // TODO(matt): validate type info + i += int(header.typeSize) + } + // Validate each code section. + for _, size := range header.codeSize { + err = validateInstructions(code[i:i+int(size)], &header, jt) + if err != nil { + return EOF1Header{}, err + } + i += int(size) + } + return header, nil } -// readEOF1Header parses EOF1-formatted code header -func readEOF1Header(code []byte) (EOF1Header, error) { +// Size returns the total size of the EOF1 header. +func (h *EOF1Header) Size() int { + var ( + typeHeaderSize = 0 + codeHeaderSize = len(h.codeSize) * 3 + dataHeaderSize = 0 + ) + if h.typeSize != 0 { + typeHeaderSize = 3 + } + if h.dataSize != 0 { + dataHeaderSize = 3 + } + // len(magic) + version + typeHeader + codeHeader + dataHeader + terminator + return 2 + 1 + typeHeaderSize + codeHeaderSize + dataHeaderSize + 1 +} + +// readUncheckedEOF1Header attempts to parse an EOF1-formatted code header. +func readUncheckedEOF1Header(code []byte) (EOF1Header, error) { if !hasEOFMagic(code) || !isEOFVersion1(code) { return EOF1Header{}, ErrEOF1InvalidVersion } @@ -162,6 +196,34 @@ outer: return header, nil } +// readEOF1Header parses EOF1-formatted code header, assuming that it is already validated. +func readEOF1Header(code []byte) EOF1Header { + var ( + header EOF1Header + i = sectionHeaderStart + codeSections = uint16(1) + ) + // Try to read type section. + if code[i] == byte(kindType) { + size, _ := readSectionSize(code, i+1) + header.typeSize = size + codeSections = size / 2 + i += 3 + } + i += 1 + // Read code sections. + for j := 0; j < int(codeSections); j++ { + size := binary.BigEndian.Uint16(code[i+3*j : i+3*j+2]) + header.codeSize = append(header.codeSize, size) + } + i += int(2 * codeSections) + // Try to read data section. + if code[i] == byte(kindData) { + header.dataSize = binary.BigEndian.Uint16(code[i+1 : i+3]) + } + return header +} + // validateInstructions checks that there're no undefined instructions and code ends with a terminating instruction func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) error { var ( @@ -213,71 +275,12 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) return nil } -// validateEOF returns true if code has valid format and code section -func validateEOF(code []byte, jumpTable *JumpTable) (EOF1Header, error) { - header, err := readEOF1Header(code) - if err != nil { - return EOF1Header{}, err - } - i := header.Size() - if header.typeSize != 0 { - // TODO(matt): validate type info - i += int(header.typeSize) - } - // Validate each code section. - for _, size := range header.codeSize { - err = validateInstructions(code[i:i+int(size)], &header, jumpTable) - if err != nil { - return EOF1Header{}, err - } - i += int(size) - } - return header, nil -} - -// readValidEOF1Header parses EOF1-formatted code header, assuming that it is already validated -func readValidEOF1Header(code []byte) EOF1Header { - var ( - header EOF1Header - i = sectionHeaderStart - codeSections = uint16(1) - ) - // Try to read type section. - if code[i] == byte(kindType) { - size, _ := readSectionSize(code, i+1) - header.typeSize = size - codeSections = size / 2 - i += 3 - } - i += 1 - // Read code sections. - for j := 0; j < int(codeSections); j++ { - size := binary.BigEndian.Uint16(code[i+3*j : i+3*j+2]) - header.codeSize = append(header.codeSize, size) - } - i += int(2 * codeSections) - // Try to read data section. - if code[i] == byte(kindData) { - header.dataSize = binary.BigEndian.Uint16(code[i+1 : i+3]) - } - return header -} - -// Size returns the total size of the EOF1 header. -func (h *EOF1Header) Size() int { - var ( - typeHeaderSize = 0 - codeHeaderSize = len(h.codeSize) * 3 - dataHeaderSize = 0 - ) - if h.typeSize != 0 { - typeHeaderSize = 3 - } - if h.dataSize != 0 { - dataHeaderSize = 3 +// readSectionSize returns the size of the section at the offset i. +func readSectionSize(code []byte, i int) (uint16, error) { + if len(code) < i+2 { + return 0, fmt.Errorf("section size missing") } - // len(magic) + version + typeHeader + codeHeader + dataHeader + terminator - return 2 + 1 + typeHeaderSize + codeHeaderSize + dataHeaderSize + 1 + return binary.BigEndian.Uint16(code[i : i+2]), nil } type Annotation struct { @@ -299,13 +302,9 @@ func NewEOF1Container(code []byte, jt *JumpTable, validated bool) (EOF1Container ) // If the code has been validated (e.g. during deployment), skip the // full validation. - if validated { - container.header = readValidEOF1Header(code) - } else { - container.header, err = validateEOF(code, jt) - if err != nil { - return EOF1Container{}, err - } + container.header, err = NewEOF1Header(code, jt, validated) + if err != nil { + return EOF1Container{}, err } // Set index to first byte after EOF header. diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 6c12d1e89d9..d343c7918cb 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -342,7 +342,7 @@ func TestInvalidInstructions(t *testing.T) { // RJUMP to push data. {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset}, } { - _, err := validateEOF(common.Hex2Bytes(test.code), &shanghaiInstructionSet) + _, err := NewEOF1Header(common.Hex2Bytes(test.code), &shanghaiInstructionSet, false) if err == nil { t.Errorf("code %v expected to be invalid", test.code) } else if !strings.HasPrefix(err.Error(), test.err.Error()) { @@ -364,7 +364,7 @@ func TestValidateUndefinedInstructions(t *testing.T) { } *instrByte = byte(opcode) - _, err := validateEOF(code, jt) + _, err := NewEOF1Header(code, jt, false) if jt[opcode].undefined { if err == nil { t.Errorf("opcode %v expected to be invalid", opcode) @@ -392,7 +392,7 @@ func TestValidateTerminatingInstructions(t *testing.T) { continue } *instrByte = byte(opcode) - _, err := validateEOF(code, jt) + _, err := NewEOF1Header(code, jt, false) if opcode.isTerminating() { if err != nil { t.Errorf("opcode %v expected to be valid terminating instruction", opcode) @@ -419,7 +419,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeTruncatedPush[5] = byte(len(codeTruncatedPush) - 7) codeTruncatedPush[7] = byte(opcode) - _, err := validateEOF(codeTruncatedPush, jt) + _, err := NewEOF1Header(codeTruncatedPush, jt, false) if err == nil { t.Errorf("code %v has truncated PUSH, expected to be invalid", common.Bytes2Hex(codeTruncatedPush)) } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { @@ -431,7 +431,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeNotTerminated[5] = byte(len(codeNotTerminated) - 7) codeNotTerminated[7] = byte(opcode) - _, err = validateEOF(codeNotTerminated, jt) + _, err = NewEOF1Header(codeNotTerminated, jt, false) if err == nil { t.Errorf("code %v does not have terminating instruction, expected to be invalid", common.Bytes2Hex(codeNotTerminated)) } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { @@ -443,7 +443,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeValid[5] = byte(len(codeValid) - 7) codeValid[7] = byte(opcode) - _, err = validateEOF(codeValid, jt) + _, err = NewEOF1Header(codeValid, jt, false) if err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 06ab666d89d..8541c97956b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -423,7 +423,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if !ok { return nil, common.Address{}, gas, ErrInvalidInterpreter } - _, err = validateEOF(ret, evmInterpreter.jt) + _, err = NewEOF1Header(ret, evmInterpreter.jt, false) if err != nil { err = ErrInvalidEOFCode } From 304c7c4ada0844b6bbb252cd08f1c2ff026d9735 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Wed, 9 Nov 2022 12:13:13 -0700 Subject: [PATCH 14/49] core/vm: simplify eof init in evm --- core/vm/evm.go | 77 +++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 8541c97956b..595c71fed2e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -246,17 +246,8 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. var ( - addrCopy = addr - container *EOF1Container + addrCopy = addr ) - if evm.chainRules.IsShanghai && hasEOFMagic(code) { - evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) - if !ok { - return nil, gas, ErrInvalidInterpreter - } - c, _ := NewEOF1Container(code, evmInterpreter.jt, true) - container = &c - } // 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. @@ -269,7 +260,7 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, } else { contract = NewContract(caller, AccountRef(addrCopy), value, gas, evm.config.SkipAnalysis) } - contract.SetCallCode(&addrCopy, codeHash, code, container) + contract.SetCallCode(&addrCopy, codeHash, code, evm.mustReadContainer(code)) readOnly := false if callType == STATICCALLT { readOnly = true @@ -381,17 +372,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, address, gas, ErrMaxInitCodeSizeExceeded } // Try to read code header if it claims to be EOF-formatted. - var container *EOF1Container - if evm.chainRules.IsShanghai && hasEOFMagic(codeAndHash.code) { - evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) - if !ok { - return nil, common.Address{}, gas, ErrInvalidInterpreter - } - c, err := NewEOF1Container(codeAndHash.code, evmInterpreter.jt, false) - if err != nil { - return nil, common.Address{}, gas, ErrInvalidEOFCode - } - container = &c + container, err := evm.readContainer(codeAndHash.code) + if err != nil { + return nil, common.Address{}, gas, ErrInvalidEOFCode } // Create a new account on the state snapshot := evm.intraBlockState.Snapshot() @@ -417,19 +400,13 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err == nil && hasEOFByte(ret) { if evm.chainRules.IsShanghai { - // Allow only valid EOF1 if EIP-3540 and EIP-3670 are enabled. - if hasEOFMagic(ret) { - evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) - if !ok { - return nil, common.Address{}, gas, ErrInvalidInterpreter - } - _, err = NewEOF1Header(ret, evmInterpreter.jt, false) - if err != nil { - err = ErrInvalidEOFCode - } - } else { - // Reject non-EOF code starting with 0xEF. - err = ErrInvalidCode + evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + if !ok { + return nil, common.Address{}, gas, ErrInvalidInterpreter + } + _, err = NewEOF1Header(ret, evmInterpreter.jt, false) + if err != nil { + err = ErrInvalidEOFCode } } else if evm.chainRules.IsLondon { // Reject code starting with 0xEF in London. @@ -469,6 +446,36 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } +// mustReadContainer reads a valid EOF container. +func (evm *EVM) mustReadContainer(code []byte) *EOF1Container { + var container *EOF1Container + if evm.chainRules.IsShanghai && hasEOFMagic(code) { + if evmInterpreter, ok := evm.interpreter.(*EVMInterpreter); ok { + c, _ := NewEOF1Container(code, evmInterpreter.jt, true) + container = &c + } + } + return container +} + +// readContainer attempts to read the EOF container defined by code if the +// chainRules supports it. +func (evm *EVM) readContainer(code []byte) (*EOF1Container, error) { + var container *EOF1Container + if evm.chainRules.IsShanghai && hasEOFMagic(code) { + evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + if !ok { + return nil, ErrInvalidInterpreter + } + c, err := NewEOF1Container(code, evmInterpreter.jt, false) + if err != nil { + return nil, err + } + container = &c + } + return container, nil +} + // Create creates a new contract using code as deployment code. // DESCRIBED: docs/programmers_guide/guide.md#nonce func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { From f1482a11feb5726df4d221f4353a0a1fa17abdbe Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Wed, 9 Nov 2022 15:06:43 -0700 Subject: [PATCH 15/49] core/vm: refactor instruction validator to use switch --- core/vm/eof.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/core/vm/eof.go b/core/vm/eof.go index 46b4d1d6cc7..f9cd88e4675 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -232,12 +232,13 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) opcode OpCode ) for i < len(code) { - opcode = OpCode(code[i]) - if jumpTable[opcode].undefined { + switch opcode = OpCode(code[i]); { + case jumpTable[opcode].undefined: return fmt.Errorf("%v: %v", ErrEOF1UndefinedInstruction, opcode) - } else if opcode >= PUSH1 && opcode <= PUSH32 { - i += int(opcode) - int(PUSH1) + 1 - } else if opcode == RJUMP || opcode == RJUMPI { + case opcode >= PUSH1 && opcode <= PUSH32: + i += int(opcode) - int(PUSH1) + 2 + continue // todo make sure this actually continues + case opcode == RJUMP || opcode == RJUMPI: var arg int16 // Read immediate argument. if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { @@ -255,8 +256,8 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) if analysis[uint64(pos)/64]&(uint64(1)<<(uint64(pos)&63)) != 0 { return ErrEOF1InvalidRelativeOffset } - i += 2 - } else if opcode == CALLF { + i += 3 + case opcode == CALLF: var arg int16 // Read immediate argument. if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { @@ -265,9 +266,10 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) if int(arg) >= len(header.codeSize) { return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, len(header.codeSize)) } - i += 2 + i += 3 + default: + i++ } - i += 1 } if !opcode.isTerminating() { return fmt.Errorf("%v: %v", ErrEOF1TerminatingInstructionMissing, opcode) From 0ca562ffca17b1dcc12845e8af8687d1fbd6ce1d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 11 Nov 2022 08:58:09 +0100 Subject: [PATCH 16/49] core/vm: remove go1.19 dependency --- core/vm/instructions_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index e82a1ddce86..f79cd26e209 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -19,7 +19,6 @@ package vm import ( "bytes" - "encoding/binary" "encoding/json" "fmt" "math/big" @@ -792,13 +791,15 @@ func makeEOF1(sections [][]byte, sigs []Annotation) []byte { out := []byte{0xef, 0x00, 0x01} // type header if 0 < len(sigs) { - out = append(out, byte(kindType)) - out = binary.BigEndian.AppendUint16(out, uint16(len(sigs)*2)) + out = append(out, byte(kindType), + byte(uint16(len(sigs)*2)), + byte(uint16(len(sigs)*2)>>8)) } // code header for _, code := range sections { - out = append(out, byte(kindCode)) - out = binary.BigEndian.AppendUint16(out, uint16(len(code))) + out = append(out, byte(kindCode), + byte(uint16(len(code))), + byte(uint16(len(code))>>8)) } // terminator out = append(out, 0x0) From 62bb42ad10366f983e5b21ad2808d688c3384852 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 11 Nov 2022 09:45:20 +0100 Subject: [PATCH 17/49] core/vm: change the eof tests a bit --- core/vm/eof_test.go | 363 ++++++++++++++++++++++---------------------- 1 file changed, 183 insertions(+), 180 deletions(-) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index d343c7918cb..0c60fe8ecfc 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -17,6 +17,7 @@ package vm import ( + "errors" "fmt" "strings" "testing" @@ -25,6 +26,7 @@ import ( ) func TestHasEOFMagic(t *testing.T) { + t.Parallel() for i, test := range []struct { code string valid bool @@ -46,218 +48,219 @@ func TestHasEOFMagic(t *testing.T) { // valid except second byte of magic {"EF0101010002020004006000AABBCCDD", false}, } { - if hasEOFMagic(common.Hex2Bytes(test.code)) != test.valid { + if hasEOFMagic(common.FromHex(test.code)) != test.valid { t.Errorf("test %d: code %v expected to be EOF", i, test.code) } } } func TestEOFContainer(t *testing.T) { + t.Parallel() for i, test := range []struct { - code string - typeSize int - codeSize []int - dataSize int - types [][]int - codeOffsets []int - data int - err error + code string + wantTypeSize int + wantCodeSize []int + wantDataSize int + wantTypes [][]int + wantCodeOffsets []int + wantDataOffset int + wantErr error }{ { - code: "EF00010100010000", - codeSize: []int{1}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF00010100010000", + wantCodeSize: []int{1}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { - code: "EF0001010002000000", - codeSize: []int{2}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF0001010002000000", + wantCodeSize: []int{2}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { - code: "EF0001010002020001000000AA", - codeSize: []int{2}, - dataSize: 1, - codeOffsets: []int{10}, - data: 12, + code: "EF0001010002020001000000AA", + wantCodeSize: []int{2}, + wantDataSize: 1, + wantCodeOffsets: []int{10}, + wantDataOffset: 12, }, { - code: "EF0001010002020004000000AABBCCDD", - codeSize: []int{2}, - dataSize: 4, - codeOffsets: []int{10}, - data: 12, + code: "EF0001010002020004000000AABBCCDD", + wantCodeSize: []int{2}, + wantDataSize: 4, + wantCodeOffsets: []int{10}, + wantDataOffset: 12, }, { - code: "EF0001010005020002006000600100AABB", - codeSize: []int{5}, - dataSize: 2, - codeOffsets: []int{10}, - data: 15, + code: "EF0001010005020002006000600100AABB", + wantCodeSize: []int{5}, + wantDataSize: 2, + wantCodeOffsets: []int{10}, + wantDataOffset: 15, }, { - code: "EF00010100070200040060006001600200AABBCCDD", - codeSize: []int{7}, - dataSize: 4, - codeOffsets: []int{10}, - data: 17, + code: "EF00010100070200040060006001600200AABBCCDD", + wantCodeSize: []int{7}, + wantDataSize: 4, + wantCodeOffsets: []int{10}, + wantDataOffset: 17, }, { - code: "EF00010300040100080100020000000201600160025e0001000149", - codeSize: []int{8, 2}, - dataSize: 0, - typeSize: 4, - codeOffsets: []int{17, 25}, - types: [][]int{{0, 0}, {2, 1}}, + code: "EF00010300040100080100020000000201600160025e0001000149", + wantCodeSize: []int{8, 2}, + wantDataSize: 0, + wantTypeSize: 4, + wantCodeOffsets: []int{17, 25}, + wantTypes: [][]int{{0, 0}, {2, 1}}, }, { // INVALID is defined and can be terminating - code: "EF000101000100FE", - codeSize: []int{1}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF000101000100FE", + wantCodeSize: []int{1}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { // terminating with RETURN - code: "EF00010100050060006000F3", - codeSize: []int{5}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF00010100050060006000F3", + wantCodeSize: []int{5}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { // terminating with REVERT - code: "EF00010100050060006000FD", - codeSize: []int{5}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF00010100050060006000FD", + wantCodeSize: []int{5}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { // terminating with SELFDESTRUCT - code: "EF0001010003006000FF", - codeSize: []int{3}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF0001010003006000FF", + wantCodeSize: []int{3}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { // PUSH32 - code: "EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", - codeSize: []int{34}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF0001010022007F000000000000000000000000000000000000000000000000000000000000000000", + wantCodeSize: []int{34}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { // undefined instructions inside push data - code: "EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", - codeSize: []int{34}, - dataSize: 0, - codeOffsets: []int{7}, + code: "EF0001010022007F0C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F00", + wantCodeSize: []int{34}, + wantDataSize: 0, + wantCodeOffsets: []int{7}, }, { // undefined instructions inside data section - code: "EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", - codeSize: []int{1}, - dataSize: 32, - codeOffsets: []int{10}, - data: 11, + code: "EF000101000102002000000C0D0E0F1E1F2122232425262728292A2B2C2D2E2F494A4B4C4D4E4F5C5D5E5F", + wantCodeSize: []int{1}, + wantDataSize: 32, + wantCodeOffsets: []int{10}, + wantDataOffset: 11, }, { // no version - code: "EF00", - err: ErrEOF1InvalidVersion, + code: "EF00", + wantErr: ErrEOF1InvalidVersion, }, { // invalid version - code: "EF0000", - err: ErrEOF1InvalidVersion, + code: "EF0000", + wantErr: ErrEOF1InvalidVersion, }, { // invalid version - code: "EF0002", - err: ErrEOF1InvalidVersion, + code: "EF0002", + wantErr: ErrEOF1InvalidVersion, }, { // valid except version - code: "EF0000010002020004006000AABBCCDD", - err: ErrEOF1InvalidVersion, + code: "EF0000010002020004006000AABBCCDD", + wantErr: ErrEOF1InvalidVersion, }, { // no header - code: "EF0001", - err: ErrEOF1CodeSectionMissing, + code: "EF0001", + wantErr: ErrEOF1CodeSectionMissing, }, { // no code section - code: "EF000100", - err: ErrEOF1CodeSectionMissing, + code: "EF000100", + wantErr: ErrEOF1CodeSectionMissing, }, { // no code section size - code: "EF000101", - err: ErrEOF1CodeSectionSizeMissing, + code: "EF000101", + wantErr: ErrEOF1CodeSectionSizeMissing, }, { // code section size incomplete - code: "EF00010100", - err: ErrEOF1CodeSectionSizeMissing, + code: "EF00010100", + wantErr: ErrEOF1CodeSectionSizeMissing, }, { // no section terminator - code: "EF0001010002", - err: ErrEOF1InvalidTotalSize, + code: "EF0001010002", + wantErr: ErrEOF1InvalidTotalSize, }, { // no code section contents - code: "EF000101000200", - err: ErrEOF1InvalidTotalSize, + code: "EF000101000200", + wantErr: ErrEOF1InvalidTotalSize, }, { // not complete code section contents - code: "EF00010100020060", - err: ErrEOF1InvalidTotalSize, + code: "EF00010100020060", + wantErr: ErrEOF1InvalidTotalSize, }, { // trailing bytes after code - code: "EF0001010002006000DEADBEEF", - err: ErrEOF1InvalidTotalSize, + code: "EF0001010002006000DEADBEEF", + wantErr: ErrEOF1InvalidTotalSize, }, { // 0 size code section - code: "EF000101000000", - err: ErrEOF1EmptyCodeSection, + code: "EF000101000000", + wantErr: ErrEOF1EmptyCodeSection, }, { // 0 size code section, with non-0 data section - code: "EF000101000002000200AABB", - err: ErrEOF1EmptyCodeSection, + code: "EF000101000002000200AABB", + wantErr: ErrEOF1EmptyCodeSection, }, { // data section before code section - code: "EF000102000401000200AABBCCDD6000", - err: ErrEOF1DataSectionBeforeCodeSection, + code: "EF000102000401000200AABBCCDD6000", + wantErr: ErrEOF1DataSectionBeforeCodeSection, }, { // data section without code section - code: "EF0001020004AABBCCDD", - err: ErrEOF1DataSectionBeforeCodeSection, + code: "EF0001020004AABBCCDD", + wantErr: ErrEOF1DataSectionBeforeCodeSection, }, { // no data section size - code: "EF000101000202", - err: ErrEOF1DataSectionSizeMissing, + code: "EF000101000202", + wantErr: ErrEOF1DataSectionSizeMissing, }, { // data section size incomplete - code: "EF00010100020200", - err: ErrEOF1DataSectionSizeMissing, + code: "EF00010100020200", + wantErr: ErrEOF1DataSectionSizeMissing, }, { // no section terminator - code: "EF0001010002020004", - err: ErrEOF1InvalidTotalSize, + code: "EF0001010002020004", + wantErr: ErrEOF1InvalidTotalSize, }, { // no data section contents - code: "EF0001010002020004006000", - err: ErrEOF1InvalidTotalSize, + code: "EF0001010002020004006000", + wantErr: ErrEOF1InvalidTotalSize, }, { // not complete data section contents - code: "EF0001010002020004006000AABBCC", - err: ErrEOF1InvalidTotalSize, + code: "EF0001010002020004006000AABBCC", + wantErr: ErrEOF1InvalidTotalSize, }, { // trailing bytes after data - code: "EF0001010002020004006000AABBCCDDEE", - err: ErrEOF1InvalidTotalSize, + code: "EF0001010002020004006000AABBCCDDEE", + wantErr: ErrEOF1InvalidTotalSize, }, { // 0 size data section - code: "EF0001010002020000006000", - err: ErrEOF1EmptyDataSection, + code: "EF0001010002020000006000", + wantErr: ErrEOF1EmptyDataSection, }, { // two data sections - code: "EF0001010002020004020004006000AABBCCDDAABBCCDD", - err: ErrEOF1MultipleDataSections, + code: "EF0001010002020004020004006000AABBCCDDAABBCCDD", + wantErr: ErrEOF1MultipleDataSections, }, { // section id = F - code: "EF00010100020F0004006000AABBCCDD", - err: ErrEOF1UnknownSection, + code: "EF00010100020F0004006000AABBCCDD", + wantErr: ErrEOF1UnknownSection, }, } { var ( jt = &shanghaiInstructionSet - code = common.Hex2Bytes(test.code) + code = common.FromHex(test.code) ) // Need to validate both code paths. First is the "trusted" EOF @@ -267,49 +270,42 @@ func TestEOFContainer(t *testing.T) { // If the error is expected, we compare the errors and continue // on. con1, err := NewEOF1Container(code, jt, false) - if err != nil { - if err == test.err { - // failed as expected + if err != nil || test.wantErr != nil { + if errors.Is(err, test.wantErr) { continue - } else { - t.Errorf("test %d: code %v validation failure, error: %v", i, test.code, err) } + t.Errorf("test %d: code %v validation failure, have: %v want %v", i, test.code, err, test.wantErr) } con2, _ := NewEOF1Container(code, jt, true) validate := func(c EOF1Container, name string) { - if len(c.header.codeSize) != len(test.codeSize) { - t.Errorf("%s unexpected number of code sections (want: %d, got %d)", name, len(test.codeSize), len(c.header.codeSize)) - } - if len(c.code) != len(test.codeOffsets) { - t.Errorf("%s unexpected number of code offsets (want: %d, got %d)", name, len(test.codeOffsets), len(c.code)) - } - if len(c.code) != len(c.header.codeSize) { - t.Errorf("%s code offsets does not match code sizes (want: %d, got %d)", name, len(c.header.codeSize), len(c.code)) + switch { + case len(c.header.codeSize) != len(test.wantCodeSize): + t.Errorf("%s unexpected number of code sections (want: %d, have %d)", name, len(test.wantCodeSize), len(c.header.codeSize)) + case len(c.code) != len(test.wantCodeOffsets): + t.Errorf("%s unexpected number of code offsets (want: %d, have %d)", name, len(test.wantCodeOffsets), len(c.code)) + case len(c.code) != len(c.header.codeSize): + t.Errorf("%s code offsets does not match code sizes (want: %d, have %d)", name, len(c.header.codeSize), len(c.code)) + case c.header.dataSize != uint16(test.wantDataSize): + t.Errorf("%s dataSize want %v, have %v", name, test.wantDataSize, c.header.dataSize) + case c.header.dataSize != 0 && c.data != uint64(test.wantDataOffset): + t.Errorf("%s data offset want %v, have %v", name, test.wantDataOffset, c.data) + case c.header.typeSize != uint16(test.wantTypeSize): + t.Errorf("%s typeSize want %v, have %v", name, test.wantTypeSize, c.header.typeSize) + case c.header.typeSize != 0 && len(c.types) != len(test.wantTypes): + t.Errorf("%s unexpected number of types %v, want %v", name, len(c.types), len(test.wantTypes)) } for j := 0; j < len(c.header.codeSize); j++ { - if test.codeSize != nil && c.header.codeSize[j] != uint16(test.codeSize[j]) { - t.Errorf("%s codeSize expected %v, got %v", name, test.codeSize[j], c.header.codeSize[j]) + if test.wantCodeSize != nil && c.header.codeSize[j] != uint16(test.wantCodeSize[j]) { + t.Errorf("%s codeSize want %v, have %v", name, test.wantCodeSize[j], c.header.codeSize[j]) } - if test.codeOffsets != nil && c.code[j] != uint64(test.codeOffsets[j]) { - t.Errorf("%s code offset expected %v, got %v", name, test.codeOffsets[j], c.code[j]) + if test.wantCodeOffsets != nil && c.code[j] != uint64(test.wantCodeOffsets[j]) { + t.Errorf("%s code offset want %v, have %v", name, test.wantCodeOffsets[j], c.code[j]) } } - if c.header.dataSize != uint16(test.dataSize) { - t.Errorf("%s dataSize expected %v, got %v", name, test.dataSize, c.header.dataSize) - } - if c.header.dataSize != 0 && c.data != uint64(test.data) { - t.Errorf("%s data offset expected %v, got %v", name, test.data, c.data) - } - if c.header.typeSize != uint16(test.typeSize) { - t.Errorf("%s typeSize expected %v, got %v", name, test.typeSize, c.header.typeSize) - } - if c.header.typeSize != 0 && len(c.types) != len(test.types) { - t.Errorf("%s unexpected number of types %v, want %v", name, len(c.types), len(test.types)) - } for j, ty := range c.types { - if int(ty.input) != test.types[j][0] || int(ty.output) != test.types[j][1] { - t.Errorf("%s types annotation mismatch (want {%d, %d}, got {%d, %d}", name, test.types[j][0], test.types[j][1], ty.input, ty.output) + if int(ty.input) != test.wantTypes[j][0] || int(ty.output) != test.wantTypes[j][1] { + t.Errorf("%s types annotation mismatch (want {%d, %d}, have {%d, %d}", name, test.wantTypes[j][0], test.wantTypes[j][1], ty.input, ty.output) } } } @@ -319,7 +315,8 @@ func TestEOFContainer(t *testing.T) { } func TestInvalidInstructions(t *testing.T) { - for _, test := range []struct { + t.Parallel() + for i, test := range []struct { code string err error }{ @@ -342,19 +339,24 @@ func TestInvalidInstructions(t *testing.T) { // RJUMP to push data. {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset}, } { - _, err := NewEOF1Header(common.Hex2Bytes(test.code), &shanghaiInstructionSet, false) - if err == nil { - t.Errorf("code %v expected to be invalid", test.code) + if _, err := NewEOF1Header(common.FromHex(test.code), &shanghaiInstructionSet, false); err == nil { + t.Errorf("test %d: expected invalid code: %v", i, test.code) } else if !strings.HasPrefix(err.Error(), test.err.Error()) { - t.Errorf("code %v expected error: \"%v\" got error: \"%v\"", test.code, test.err, err.Error()) + t.Errorf("test %d: want error: \"%v\" have error: \"%v\"", i, test.err, err.Error()) } } } func TestValidateUndefinedInstructions(t *testing.T) { + t.Parallel() jt := &shanghaiInstructionSet - code := common.Hex2Bytes("EF0001010002000C00") - instrByte := &code[7] + code := common.FromHex("EF000101000200" + "0C00") + // Iterate over all possible opcodes, except the ones with immediates: + // PUSH1-PUSH32, RJUMP, RJUMPI, CALLF + // The actual code being validated is + // 0000 + // 0100 + /// ... for opcode := uint16(0); opcode <= 0xff; opcode++ { if OpCode(opcode) >= PUSH1 && OpCode(opcode) <= PUSH32 { continue @@ -362,47 +364,48 @@ func TestValidateUndefinedInstructions(t *testing.T) { if OpCode(opcode) == RJUMP || OpCode(opcode) == RJUMPI || OpCode(opcode) == CALLF { continue } - - *instrByte = byte(opcode) + code[7] = byte(opcode) _, err := NewEOF1Header(code, jt, false) - if jt[opcode].undefined { - if err == nil { - t.Errorf("opcode %v expected to be invalid", opcode) - } else if !strings.HasPrefix(err.Error(), ErrEOF1UndefinedInstruction.Error()) { - t.Errorf("opcode %v unxpected error: \"%v\"", opcode, err.Error()) - } - } else { + if !jt[opcode].undefined { if err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) } + continue + } + if err == nil { + t.Errorf("opcode %v expected to be invalid", opcode) + } else if !strings.HasPrefix(err.Error(), ErrEOF1UndefinedInstruction.Error()) { + t.Errorf("opcode %v unexpected error: \"%v\"", opcode, err.Error()) } } } func TestValidateTerminatingInstructions(t *testing.T) { + t.Parallel() jt := &shanghaiInstructionSet - code := common.Hex2Bytes("EF0001010001000C") - instrByte := &code[7] + code := common.FromHex("EF000101000100" + "0C") for opcodeValue := uint16(0); opcodeValue <= 0xff; opcodeValue++ { opcode := OpCode(opcodeValue) + // Iterate over all possible opcodes, except the ones with immediates: + // PUSH1-PUSH32, RJUMP, RJUMPI, CALLF if opcode >= PUSH1 && opcode <= PUSH32 || opcode == RJUMP || opcode == RJUMPI || opcode == CALLF || opcode == RETF { continue } if jt[opcode].undefined { continue } - *instrByte = byte(opcode) + code[7] = byte(opcode) _, err := NewEOF1Header(code, jt, false) if opcode.isTerminating() { if err != nil { t.Errorf("opcode %v expected to be valid terminating instruction", opcode) } - } else { - if err == nil { - t.Errorf("opcode %v expected to be invalid terminating instruction", opcode) - } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { - t.Errorf("opcode %v unexpected error: \"%v\"", opcode, err.Error()) - } + continue + } + if err == nil { + t.Errorf("opcode %v expected to be invalid terminating instruction", opcode) + } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { + t.Errorf("opcode %v unexpected error: \"%v\"", opcode, err.Error()) } } } @@ -410,7 +413,7 @@ func TestValidateTerminatingInstructions(t *testing.T) { func TestValidateTruncatedPush(t *testing.T) { jt := &shanghaiInstructionSet zeroes := [33]byte{} - code := common.Hex2Bytes("EF0001010001000C") + code := common.FromHex("EF0001010001000C") for opcode := PUSH1; opcode <= PUSH32; opcode++ { requiredBytes := opcode - PUSH1 + 1 From 28a7a275f8305306441b07efa73b3eff0bb4aede Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Mon, 14 Nov 2022 16:51:20 -0700 Subject: [PATCH 18/49] core/vm: refactor eof objects --- core/vm/contract.go | 4 +- core/vm/eips.go | 10 +- core/vm/eof.go | 301 +++++++++++++++++++---------------- core/vm/eof_test.go | 80 ++++------ core/vm/evm.go | 23 ++- core/vm/instructions_test.go | 31 ++-- core/vm/interpreter.go | 2 +- 7 files changed, 235 insertions(+), 216 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index 3c78cb2106b..0dd652c281f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -171,8 +171,8 @@ func (c *Contract) GetByte(n uint64) byte { // GetOpInSection returns the n'th element in the code sections's byte array. func (c *Contract) GetOpInSection(n uint64, s uint64) OpCode { - if n < uint64(c.Container.header.codeSize[s]) { - start := c.Container.code[s] + if n < uint64(c.Container.codeSize[s]) { + start := c.Container.codeOffsets[s] return OpCode(c.Code[start+n]) } diff --git a/core/vm/eips.go b/core/vm/eips.go index c9228d86326..1696ec8deda 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -233,7 +233,7 @@ func enable4200(jt *JumpTable) { // opRjump implements the RJUMP opcode func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - idx = scope.Contract.Container.code[scope.ActiveSection] + *pc + 1 + idx = scope.Contract.Container.codeOffsets[scope.ActiveSection] + *pc + 1 arg = scope.Contract.Code[idx : idx+2] relativeOffset int16 ) @@ -259,7 +259,7 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } var ( - idx = scope.Contract.Container.code[scope.ActiveSection] + *pc + 1 + idx = scope.Contract.Container.codeOffsets[scope.ActiveSection] + *pc + 1 arg = scope.Contract.Code[idx : idx+2] relativeOffset int16 ) @@ -296,7 +296,7 @@ func enable4750(jt *JumpTable) { // opCallf implements the CALLF opcode. func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - idx = scope.Contract.Container.code[scope.ActiveSection] + *pc + 1 + idx = scope.Contract.Container.codeOffsets[scope.ActiveSection] + *pc + 1 arg = scope.Contract.Code[idx : idx+2] section int16 ) @@ -305,7 +305,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } caller := scope.RetStack[len(scope.RetStack)-1] sig := scope.Contract.Container.types[int(section)] - if scope.Stack.Len() < int(caller.StackHeight)+int(sig.input) { + if scope.Stack.Len() < int(caller.StackHeight)+int(sig.Input) { return nil, fmt.Errorf("too few stack items") } if len(scope.RetStack) >= 1024 { @@ -329,7 +329,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // opRetf implements the RETF opcode. func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { sig := scope.Contract.Container.types[scope.ActiveSection] - if scope.Stack.Len() < int(sig.output) { + if scope.Stack.Len() < int(sig.Output) { return nil, fmt.Errorf("too few stack items") } diff --git a/core/vm/eof.go b/core/vm/eof.go index f9cd88e4675..adffbc74337 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -55,177 +55,207 @@ func isEOFVersion1(code []byte) bool { return versionOffset < len(code) && code[versionOffset] == byte(eof1Version) } -type EOF1Header struct { - typeSize uint16 // Size of type section. Must be 2 * n bytes, where n is number of code sections. - codeSize []uint16 // Size of code sections. Cannot be 0 for EOF1 code. Equals 0 for legacy code. - dataSize uint16 // Size of data section. Equals 0 if data section is absent in EOF1 code. Equals 0 for legacy code. +// Annotation represents an EOF v1 function signature. +type Annotation struct { + Input uint8 // number of stack inputs + Output uint8 // number of stack outputs } -func NewEOF1Header(code []byte, jt *JumpTable, validated bool) (EOF1Header, error) { - if validated { - return readEOF1Header(code), nil - } +// EOF1Container is an EOF v1 container object. +type EOF1Container struct { + // header items + typeSize uint16 + codeSize []uint16 + dataSize uint16 + + // sections + types []Annotation + codeOffsets []uint64 + dataOffset uint64 + + // backing data + data []byte +} - header, err := readUncheckedEOF1Header(code) +// ParseEOF1Container parses an EOF v1 container from the provided byte slice. +func ParseEOF1Container(b []byte) (*EOF1Container, error) { + var c EOF1Container + err := c.UnmarshalBinary(b) + return &c, err +} + +// ParseEOF1Container parses and validates an EOF v1 container from the +// provided byte slice. +func ParseAndValidateEOF1Container(b []byte, jt *JumpTable) (*EOF1Container, error) { + c, err := ParseEOF1Container(b) if err != nil { - return EOF1Header{}, err + return nil, err } - i := header.Size() - if header.typeSize != 0 { - // TODO(matt): validate type info - i += int(header.typeSize) + if err := c.ValidateCode(jt); err != nil { + return nil, err } - // Validate each code section. - for _, size := range header.codeSize { - err = validateInstructions(code[i:i+int(size)], &header, jt) - if err != nil { - return EOF1Header{}, err - } - i += int(size) + return c, nil +} + +// UnmarshalBinary decodes an EOF v1 container. +// +// This function ensures that the container is well-formed (e.g. size values +// are correct, sections are ordered correctly, etc), but it does not perform +// code validation on the container. +func (c *EOF1Container) UnmarshalBinary(b []byte) error { + if err := c.parseHeader(b); err != nil { + return err + } + idx := c.HeaderSize() + // Read type section if it exists. + if typeSize := c.typeSize; typeSize != 0 { + c.types = parseTypeSection(b[idx : idx+int(typeSize)]) + idx += int(typeSize) } - return header, nil + // Calculate starting offset for each code section. + for _, size := range c.codeSize { + c.codeOffsets = append(c.codeOffsets, uint64(idx)) + idx += int(size) + } + // Set data offset. + c.dataOffset = uint64(idx) + c.data = b + return nil } -// Size returns the total size of the EOF1 header. -func (h *EOF1Header) Size() int { +// HeaderSize returns the total size of the EOF1 header. +func (c *EOF1Container) HeaderSize() int { var ( typeHeaderSize = 0 - codeHeaderSize = len(h.codeSize) * 3 + codeHeaderSize = len(c.codeSize) * 3 dataHeaderSize = 0 ) - if h.typeSize != 0 { + if c.typeSize != 0 { typeHeaderSize = 3 } - if h.dataSize != 0 { + if c.dataSize != 0 { dataHeaderSize = 3 } // len(magic) + version + typeHeader + codeHeader + dataHeader + terminator return 2 + 1 + typeHeaderSize + codeHeaderSize + dataHeaderSize + 1 } -// readUncheckedEOF1Header attempts to parse an EOF1-formatted code header. -func readUncheckedEOF1Header(code []byte) (EOF1Header, error) { - if !hasEOFMagic(code) || !isEOFVersion1(code) { - return EOF1Header{}, ErrEOF1InvalidVersion +// ValidateCode performs the EOF v1 code validation on the container. +func (c *EOF1Container) ValidateCode(jt *JumpTable) error { + idx := c.HeaderSize() + for _, size := range c.codeSize { + if err := validateInstructions(c.data[idx:idx+int(size)], len(c.codeSize), jt); err != nil { + return err + } + idx += int(size) + } + return nil +} + +// parseEOF1Header attempts to parse an EOF1-formatted code header. +func (c *EOF1Container) parseHeader(b []byte) error { + if !hasEOFMagic(b) || !isEOFVersion1(b) { + return ErrEOF1InvalidVersion } var ( - i = sectionHeaderStart - err error - codeRead int - dataRead int - typeRead int - header EOF1Header - codeSize int + i = sectionHeaderStart + err error + typeRead int + codeRead int + dataRead int + totalCodeSize int ) outer: - for i < len(code) { - switch headerSection(code[i]) { + for i < len(b) { + switch headerSection(b[i]) { case kindTerminator: i += 1 break outer case kindType: // Type section header must be read first. if codeRead != 0 || dataRead != 0 { - return EOF1Header{}, ErrEOF1TypeSectionHeaderAfterOthers + return ErrEOF1TypeSectionHeaderAfterOthers } // Only 1 type section is allowed. if typeRead != 0 { - return EOF1Header{}, ErrEOF1MultipleTypeSections + return ErrEOF1MultipleTypeSections } // Size must be present. - if header.typeSize, err = readSectionSize(code, i+1); err != nil { - return EOF1Header{}, ErrEOF1TypeSectionSizeMissing + if c.typeSize, err = parseSectionSize(b, i+1); err != nil { + return ErrEOF1TypeSectionSizeMissing } // Type section size must not be 0. - if header.typeSize == 0 { - return EOF1Header{}, ErrEOF1EmptyDataSection + if c.typeSize == 0 { + return ErrEOF1EmptyDataSection } typeRead += 1 case kindCode: // Size must be present. - size, err := readSectionSize(code, i+1) + var size uint16 + size, err = parseSectionSize(b, i+1) if err != nil { - return EOF1Header{}, ErrEOF1CodeSectionSizeMissing + return ErrEOF1CodeSectionSizeMissing } // Size must not be 0. if size == 0 { - return EOF1Header{}, ErrEOF1EmptyCodeSection + return ErrEOF1EmptyCodeSection } - header.codeSize = append(header.codeSize, size) - codeSize += int(size) + c.codeSize = append(c.codeSize, size) + totalCodeSize += int(size) codeRead += 1 case kindData: // Data section is allowed only after code section. if codeRead == 0 { - return EOF1Header{}, ErrEOF1DataSectionBeforeCodeSection + return ErrEOF1DataSectionBeforeCodeSection } // Only 1 data section is allowed. if dataRead != 0 { - return EOF1Header{}, ErrEOF1MultipleDataSections + return ErrEOF1MultipleDataSections } // Size must be present. - if header.dataSize, err = readSectionSize(code, i+1); err != nil { - return EOF1Header{}, ErrEOF1DataSectionSizeMissing + if c.dataSize, err = parseSectionSize(b, i+1); err != nil { + return ErrEOF1DataSectionSizeMissing + } // Data section size must not be 0. - if header.dataSize == 0 { - return EOF1Header{}, ErrEOF1EmptyDataSection + if c.dataSize == 0 { + return ErrEOF1EmptyDataSection } dataRead += 1 default: - return EOF1Header{}, ErrEOF1UnknownSection + return ErrEOF1UnknownSection } i += 3 } // 1 code section is required. if codeRead < 1 { - return EOF1Header{}, ErrEOF1CodeSectionMissing + return ErrEOF1CodeSectionMissing } // 1024 max code sections. - if len(header.codeSize) > 1024 { - return EOF1Header{}, ErrEOF1CodeSectionOverflow + if len(c.codeSize) > 1024 { + return ErrEOF1CodeSectionOverflow } // Must have type section if more than one code section. - if len(header.codeSize) > 1 && header.typeSize == 0 { - return EOF1Header{}, ErrEOF1TypeSectionMissing + if len(c.codeSize) > 1 && c.typeSize == 0 { + return ErrEOF1TypeSectionMissing } // Declared section sizes must correspond to real size (trailing bytes are not allowed.) - if i+int(header.typeSize)+codeSize+int(header.dataSize) != len(code) { - return EOF1Header{}, ErrEOF1InvalidTotalSize + if c.HeaderSize()+int(c.typeSize)+totalCodeSize+int(c.dataSize) != len(b) { + return ErrEOF1InvalidTotalSize } - return header, nil + return nil } -// readEOF1Header parses EOF1-formatted code header, assuming that it is already validated. -func readEOF1Header(code []byte) EOF1Header { - var ( - header EOF1Header - i = sectionHeaderStart - codeSections = uint16(1) - ) - // Try to read type section. - if code[i] == byte(kindType) { - size, _ := readSectionSize(code, i+1) - header.typeSize = size - codeSections = size / 2 - i += 3 - } - i += 1 - // Read code sections. - for j := 0; j < int(codeSections); j++ { - size := binary.BigEndian.Uint16(code[i+3*j : i+3*j+2]) - header.codeSize = append(header.codeSize, size) - } - i += int(2 * codeSections) - // Try to read data section. - if code[i] == byte(kindData) { - header.dataSize = binary.BigEndian.Uint16(code[i+1 : i+3]) +// parseSectionSize returns the size of the section at the offset i. +func parseSectionSize(code []byte, i int) (uint16, error) { + if len(code) < i+2 { + return 0, fmt.Errorf("section size missing") } - return header + return binary.BigEndian.Uint16(code[i : i+2]), nil } // validateInstructions checks that there're no undefined instructions and code ends with a terminating instruction -func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) error { +func validateInstructions(code []byte, codeSections int, jumpTable *JumpTable) error { var ( i = 0 analysis = codeBitmap(code) @@ -263,8 +293,8 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { return fmt.Errorf("%v: %v", ErrEOF1InvalidCallfSection, err) } - if int(arg) >= len(header.codeSize) { - return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, len(header.codeSize)) + if int(arg) >= codeSections { + return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, codeSections) } i += 3 default: @@ -277,62 +307,65 @@ func validateInstructions(code []byte, header *EOF1Header, jumpTable *JumpTable) return nil } -// readSectionSize returns the size of the section at the offset i. -func readSectionSize(code []byte, i int) (uint16, error) { - if len(code) < i+2 { - return 0, fmt.Errorf("section size missing") - } - return binary.BigEndian.Uint16(code[i : i+2]), nil -} - -type Annotation struct { - input uint8 - output uint8 -} - -type EOF1Container struct { - header EOF1Header - types []Annotation // type defs - code []uint64 // code start offsets - data uint64 // data start offset -} - -func NewEOF1Container(code []byte, jt *JumpTable, validated bool) (EOF1Container, error) { - var ( - container EOF1Container - err error - ) +// uncheckedParseContainer parses an EOF v1 container without performing +// validation. +func uncheckedParseContainer(code []byte) *EOF1Container { + var c EOF1Container // If the code has been validated (e.g. during deployment), skip the // full validation. - container.header, err = NewEOF1Header(code, jt, validated) - if err != nil { - return EOF1Container{}, err - } + c.typeSize, c.codeSize, c.dataSize = parseHeaderUnchecked(code) // Set index to first byte after EOF header. - idx := container.header.Size() + idx := c.HeaderSize() // Read type section if it exists. - if typeSize := container.header.typeSize; typeSize != 0 { - container.types = readTypeSection(code[idx : idx+int(typeSize)]) + if typeSize := c.typeSize; typeSize != 0 { + c.types = parseTypeSection(code[idx : idx+int(typeSize)]) idx += int(typeSize) } // Calculate starting offset for each code section. - for _, size := range container.header.codeSize { - container.code = append(container.code, uint64(idx)) + for _, size := range c.codeSize { + c.codeOffsets = append(c.codeOffsets, uint64(idx)) idx += int(size) } // Set data offset. - container.data = uint64(idx) - return container, nil + c.dataOffset = uint64(idx) + return &c +} + +// parseHeaderUnchecked parses an EOF v1 header without performing validation. +func parseHeaderUnchecked(code []byte) (typeSize uint16, codeSize []uint16, dataSize uint16) { + var ( + i = sectionHeaderStart + codeSections = uint16(1) + ) + // Try to read type section. + if code[i] == byte(kindType) { + size, _ := parseSectionSize(code, i+1) + typeSize = size + codeSections = size / 2 + i += 3 + } + i += 1 + // Read code sections. + for j := 0; j < int(codeSections); j++ { + size := binary.BigEndian.Uint16(code[i+3*j : i+3*j+2]) + codeSize = append(codeSize, size) + } + i += int(2 * codeSections) + // Try to read data section. + if code[i] == byte(kindData) { + dataSize = binary.BigEndian.Uint16(code[i+1 : i+3]) + } + return } -// readTypeSection parses an EOF type section. -func readTypeSection(code []byte) (out []Annotation) { +// parseTypeSection parses an EOF type section. +func parseTypeSection(code []byte) (out []Annotation) { for i := 0; i < len(code); i += 2 { sig := Annotation{ - input: code[i], - output: code[i+1], + Input: code[i], + Output: code[i+1], } out = append(out, sig) } diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 0c60fe8ecfc..965f64c20de 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -259,58 +259,47 @@ func TestEOFContainer(t *testing.T) { }, } { var ( - jt = &shanghaiInstructionSet code = common.FromHex(test.code) + name = fmt.Sprintf("test %2d: code %v", i, test.code) ) - // Need to validate both code paths. First is the "trusted" EOF - // code that was validated before being stored. This can't - // throw and error. The other path is "untrusted" and so the - // code is validated as it is parsed. This may throw an error. - // If the error is expected, we compare the errors and continue - // on. - con1, err := NewEOF1Container(code, jt, false) + c, err := ParseEOF1Container(code) if err != nil || test.wantErr != nil { if errors.Is(err, test.wantErr) { continue } t.Errorf("test %d: code %v validation failure, have: %v want %v", i, test.code, err, test.wantErr) } - con2, _ := NewEOF1Container(code, jt, true) - validate := func(c EOF1Container, name string) { - switch { - case len(c.header.codeSize) != len(test.wantCodeSize): - t.Errorf("%s unexpected number of code sections (want: %d, have %d)", name, len(test.wantCodeSize), len(c.header.codeSize)) - case len(c.code) != len(test.wantCodeOffsets): - t.Errorf("%s unexpected number of code offsets (want: %d, have %d)", name, len(test.wantCodeOffsets), len(c.code)) - case len(c.code) != len(c.header.codeSize): - t.Errorf("%s code offsets does not match code sizes (want: %d, have %d)", name, len(c.header.codeSize), len(c.code)) - case c.header.dataSize != uint16(test.wantDataSize): - t.Errorf("%s dataSize want %v, have %v", name, test.wantDataSize, c.header.dataSize) - case c.header.dataSize != 0 && c.data != uint64(test.wantDataOffset): - t.Errorf("%s data offset want %v, have %v", name, test.wantDataOffset, c.data) - case c.header.typeSize != uint16(test.wantTypeSize): - t.Errorf("%s typeSize want %v, have %v", name, test.wantTypeSize, c.header.typeSize) - case c.header.typeSize != 0 && len(c.types) != len(test.wantTypes): - t.Errorf("%s unexpected number of types %v, want %v", name, len(c.types), len(test.wantTypes)) + switch { + case len(c.codeSize) != len(test.wantCodeSize): + t.Errorf("%s unexpected number of code sections (want: %d, have %d)", name, len(test.wantCodeSize), len(c.codeSize)) + case len(c.codeOffsets) != len(test.wantCodeOffsets): + t.Errorf("%s unexpected number of code offsets (want: %d, have %d)", name, len(test.wantCodeOffsets), len(c.codeOffsets)) + case len(c.codeOffsets) != len(c.codeSize): + t.Errorf("%s code offsets does not match code sizes (want: %d, have %d)", name, len(c.codeSize), len(c.codeOffsets)) + case c.dataSize != uint16(test.wantDataSize): + t.Errorf("%s dataSize want %v, have %v", name, test.wantDataSize, c.dataSize) + case c.dataSize != 0 && c.dataOffset != uint64(test.wantDataOffset): + t.Errorf("%s data offset want %v, have %v", name, test.wantDataOffset, c.data) + case c.typeSize != uint16(test.wantTypeSize): + t.Errorf("%s typeSize want %v, have %v", name, test.wantTypeSize, c.typeSize) + case c.typeSize != 0 && len(c.types) != len(test.wantTypes): + t.Errorf("%s unexpected number of types %v, want %v", name, len(c.types), len(test.wantTypes)) + } + for j := 0; j < len(c.codeSize); j++ { + if test.wantCodeSize != nil && c.codeSize[j] != uint16(test.wantCodeSize[j]) { + t.Errorf("%s codeSize want %v, have %v", name, test.wantCodeSize[j], c.codeSize[j]) } - for j := 0; j < len(c.header.codeSize); j++ { - if test.wantCodeSize != nil && c.header.codeSize[j] != uint16(test.wantCodeSize[j]) { - t.Errorf("%s codeSize want %v, have %v", name, test.wantCodeSize[j], c.header.codeSize[j]) - } - if test.wantCodeOffsets != nil && c.code[j] != uint64(test.wantCodeOffsets[j]) { - t.Errorf("%s code offset want %v, have %v", name, test.wantCodeOffsets[j], c.code[j]) - } + if test.wantCodeOffsets != nil && c.codeOffsets[j] != uint64(test.wantCodeOffsets[j]) { + t.Errorf("%s code offset want %v, have %v", name, test.wantCodeOffsets[j], c.codeOffsets[j]) } - for j, ty := range c.types { - if int(ty.input) != test.wantTypes[j][0] || int(ty.output) != test.wantTypes[j][1] { - t.Errorf("%s types annotation mismatch (want {%d, %d}, have {%d, %d}", name, test.wantTypes[j][0], test.wantTypes[j][1], ty.input, ty.output) - } + } + for j, ty := range c.types { + if int(ty.Input) != test.wantTypes[j][0] || int(ty.Output) != test.wantTypes[j][1] { + t.Errorf("%s types annotation mismatch (want {%d, %d}, have {%d, %d}", name, test.wantTypes[j][0], test.wantTypes[j][1], ty.Input, ty.Output) } } - validate(con1, fmt.Sprintf("test %2d (validated): code %v", i, test.code)) - validate(con2, fmt.Sprintf("test %2d (not validated): code %v", i, test.code)) } } @@ -339,7 +328,7 @@ func TestInvalidInstructions(t *testing.T) { // RJUMP to push data. {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset}, } { - if _, err := NewEOF1Header(common.FromHex(test.code), &shanghaiInstructionSet, false); err == nil { + if _, err := ParseAndValidateEOF1Container(common.FromHex(test.code), &shanghaiInstructionSet); err == nil { t.Errorf("test %d: expected invalid code: %v", i, test.code) } else if !strings.HasPrefix(err.Error(), test.err.Error()) { t.Errorf("test %d: want error: \"%v\" have error: \"%v\"", i, test.err, err.Error()) @@ -365,7 +354,7 @@ func TestValidateUndefinedInstructions(t *testing.T) { continue } code[7] = byte(opcode) - _, err := NewEOF1Header(code, jt, false) + _, err := ParseAndValidateEOF1Container(code, jt) if !jt[opcode].undefined { if err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) @@ -395,7 +384,7 @@ func TestValidateTerminatingInstructions(t *testing.T) { continue } code[7] = byte(opcode) - _, err := NewEOF1Header(code, jt, false) + _, err := ParseAndValidateEOF1Container(code, jt) if opcode.isTerminating() { if err != nil { t.Errorf("opcode %v expected to be valid terminating instruction", opcode) @@ -422,8 +411,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeTruncatedPush[5] = byte(len(codeTruncatedPush) - 7) codeTruncatedPush[7] = byte(opcode) - _, err := NewEOF1Header(codeTruncatedPush, jt, false) - if err == nil { + if _, err := ParseAndValidateEOF1Container(codeTruncatedPush, jt); err == nil { t.Errorf("code %v has truncated PUSH, expected to be invalid", common.Bytes2Hex(codeTruncatedPush)) } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeTruncatedPush), err) @@ -434,8 +422,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeNotTerminated[5] = byte(len(codeNotTerminated) - 7) codeNotTerminated[7] = byte(opcode) - _, err = NewEOF1Header(codeNotTerminated, jt, false) - if err == nil { + if _, err := ParseAndValidateEOF1Container(codeNotTerminated, jt); err == nil { t.Errorf("code %v does not have terminating instruction, expected to be invalid", common.Bytes2Hex(codeNotTerminated)) } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeNotTerminated), err) @@ -446,8 +433,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeValid[5] = byte(len(codeValid) - 7) codeValid[7] = byte(opcode) - _, err = NewEOF1Header(codeValid, jt, false) - if err != nil { + if _, err := ParseAndValidateEOF1Container(codeValid, jt); err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) } } diff --git a/core/vm/evm.go b/core/vm/evm.go index 595c71fed2e..62f8eaf065a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -260,7 +260,7 @@ func (evm *EVM) call(callType CallType, caller ContractRef, addr common.Address, } else { contract = NewContract(caller, AccountRef(addrCopy), value, gas, evm.config.SkipAnalysis) } - contract.SetCallCode(&addrCopy, codeHash, code, evm.mustReadContainer(code)) + contract.SetCallCode(&addrCopy, codeHash, code, evm.mustParseContainer(code)) readOnly := false if callType == STATICCALLT { readOnly = true @@ -372,7 +372,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, address, gas, ErrMaxInitCodeSizeExceeded } // Try to read code header if it claims to be EOF-formatted. - container, err := evm.readContainer(codeAndHash.code) + container, err := evm.parseContainer(codeAndHash.code) if err != nil { return nil, common.Address{}, gas, ErrInvalidEOFCode } @@ -404,7 +404,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if !ok { return nil, common.Address{}, gas, ErrInvalidInterpreter } - _, err = NewEOF1Header(ret, evmInterpreter.jt, false) + _, err := ParseAndValidateEOF1Container(ret, evmInterpreter.jt) if err != nil { err = ErrInvalidEOFCode } @@ -446,32 +446,29 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } -// mustReadContainer reads a valid EOF container. -func (evm *EVM) mustReadContainer(code []byte) *EOF1Container { +// mustParseContainer reads a valid EOF container. +func (evm *EVM) mustParseContainer(code []byte) *EOF1Container { var container *EOF1Container if evm.chainRules.IsShanghai && hasEOFMagic(code) { - if evmInterpreter, ok := evm.interpreter.(*EVMInterpreter); ok { - c, _ := NewEOF1Container(code, evmInterpreter.jt, true) - container = &c - } + container, _ = ParseEOF1Container(code) } return container } -// readContainer attempts to read the EOF container defined by code if the +// parseContainer attempts to read the EOF container defined by code if the // chainRules supports it. -func (evm *EVM) readContainer(code []byte) (*EOF1Container, error) { +func (evm *EVM) parseContainer(code []byte) (*EOF1Container, error) { var container *EOF1Container if evm.chainRules.IsShanghai && hasEOFMagic(code) { evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) if !ok { return nil, ErrInvalidInterpreter } - c, err := NewEOF1Container(code, evmInterpreter.jt, false) + c, err := ParseAndValidateEOF1Container(code, evmInterpreter.jt) if err != nil { return nil, err } - container = &c + container = c } return container, nil } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index f79cd26e209..7d911c9a667 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -709,19 +709,22 @@ func TestStaticRelativeJumps(t *testing.T) { evmInterpreter = env.interpreter code = makeEOF1([][]byte{common.Hex2Bytes(tt.code)}, nil) contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) - container, _ = NewEOF1Container(code, env.interpreter.cfg.JumpTable, true) + container, err = ParseEOF1Container(code) ) - contract.SetCallCode(&addr, common.Hash{}, code, &container) + if err != nil { + t.Fatal(err) + } + contract.SetCallCode(&addr, common.Hash{}, code, container) if tt.op == "RJUMP" { - opRjump(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.code[0], uint64(container.header.codeSize[0])}}}) + opRjump(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.codeOffsets[0], uint64(container.codeSize[0])}}}) } else if tt.op == "RJUMPI" { if tt.condition { stack.push(uint256.NewInt(1)) } else { stack.push(uint256.NewInt(0)) } - opRjumpi(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.code[0], uint64(container.header.codeSize[0])}}}) + opRjumpi(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.codeOffsets[0], uint64(container.codeSize[0])}}}) } // The pc should be one less than expected because the interpreter will increment it. @@ -750,17 +753,17 @@ func TestEOFFunctions(t *testing.T) { stack = newstack() pc = uint64(0) evmInterpreter = env.interpreter - eofCode = makeEOF1(code, []Annotation{{input: 0, output: 0}, {input: 2, output: 1}}) + eofCode = makeEOF1(code, []Annotation{{Input: 0, Output: 0}, {Input: 2, Output: 1}}) contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) - container, _ = NewEOF1Container(eofCode, env.interpreter.cfg.JumpTable, true) + container, _ = ParseEOF1Container(eofCode) ) - contract.SetCallCode(&addr, common.Hash{}, eofCode, &container) + contract.SetCallCode(&addr, common.Hash{}, eofCode, container) scope := &ScopeContext{ Memory: nil, Stack: stack, Contract: contract, ActiveSection: 0, - RetStack: []*SubroutineContext{{0, 0, 0, container.code[0], uint64(container.header.codeSize[0])}}, + RetStack: []*SubroutineContext{{0, 0, 0, container.codeOffsets[0], uint64(container.codeSize[0])}}, } // Fill stack. for i := 0; i < tt.stack; i++ { @@ -790,22 +793,22 @@ func TestEOFFunctions(t *testing.T) { func makeEOF1(sections [][]byte, sigs []Annotation) []byte { out := []byte{0xef, 0x00, 0x01} // type header - if 0 < len(sigs) { + if len(sigs) != 0 { out = append(out, byte(kindType), - byte(uint16(len(sigs)*2)), - byte(uint16(len(sigs)*2)>>8)) + byte(uint16(len(sigs)*2)>>8), + byte(uint16(len(sigs)*2))) } // code header for _, code := range sections { out = append(out, byte(kindCode), - byte(uint16(len(code))), - byte(uint16(len(code))>>8)) + byte(uint16(len(code))>>8), + byte(uint16(len(code)))) } // terminator out = append(out, 0x0) // type section for _, sig := range sigs { - out = append(out, []byte{sig.input, sig.output}...) + out = append(out, []byte{sig.Input, sig.Output}...) } // code section for _, code := range sections { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index efd1293d485..cf20b8b0d0c 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -271,7 +271,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // The default case is set above, if the contract is actually EOF the // offset should be updated to beginning of the first code section. if !contract.IsLegacy() { - callContext.RetStack[0].CodeOffset = contract.Container.code[0] + callContext.RetStack[0].CodeOffset = contract.Container.codeOffsets[0] } // Don't move this deferred function, it's placed before the capturestate-deferred method, // so that it get's executed _after_: the capturestate needs the stacks before From 2734fb86cf182cf32adc24171b0df72d8f75c94e Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 15 Nov 2022 06:03:42 -0700 Subject: [PATCH 19/49] core/vm: fix off-by-one in type section parser --- core/vm/eof.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/eof.go b/core/vm/eof.go index adffbc74337..12f4e34934c 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -362,7 +362,7 @@ func parseHeaderUnchecked(code []byte) (typeSize uint16, codeSize []uint16, data // parseTypeSection parses an EOF type section. func parseTypeSection(code []byte) (out []Annotation) { - for i := 0; i < len(code); i += 2 { + for i := 0; i < len(code)-1; i += 2 { sig := Annotation{ Input: code[i], Output: code[i+1], From 68d1834621c0098dcb9ff25b108748fa95fcb389 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 15 Nov 2022 13:33:10 -0700 Subject: [PATCH 20/49] core/vm: simplify eof parsing interface --- core/vm/eof.go | 27 ++++----------------------- core/vm/eof_test.go | 23 +++++++++++++++++------ core/vm/evm.go | 18 ++++++++++++------ 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/core/vm/eof.go b/core/vm/eof.go index 12f4e34934c..e27546c8356 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -78,33 +78,14 @@ type EOF1Container struct { } // ParseEOF1Container parses an EOF v1 container from the provided byte slice. -func ParseEOF1Container(b []byte) (*EOF1Container, error) { - var c EOF1Container - err := c.UnmarshalBinary(b) - return &c, err -} - -// ParseEOF1Container parses and validates an EOF v1 container from the -// provided byte slice. -func ParseAndValidateEOF1Container(b []byte, jt *JumpTable) (*EOF1Container, error) { - c, err := ParseEOF1Container(b) - if err != nil { - return nil, err - } - if err := c.ValidateCode(jt); err != nil { - return nil, err - } - return c, nil -} - -// UnmarshalBinary decodes an EOF v1 container. // // This function ensures that the container is well-formed (e.g. size values // are correct, sections are ordered correctly, etc), but it does not perform // code validation on the container. -func (c *EOF1Container) UnmarshalBinary(b []byte) error { +func ParseEOF1Container(b []byte) (*EOF1Container, error) { + var c EOF1Container if err := c.parseHeader(b); err != nil { - return err + return nil, err } idx := c.HeaderSize() // Read type section if it exists. @@ -120,7 +101,7 @@ func (c *EOF1Container) UnmarshalBinary(b []byte) error { // Set data offset. c.dataOffset = uint64(idx) c.data = b - return nil + return &c, nil } // HeaderSize returns the total size of the EOF1 header. diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 965f64c20de..f58b37b3212 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -328,7 +328,7 @@ func TestInvalidInstructions(t *testing.T) { // RJUMP to push data. {"EF0001010006005C0001600100", ErrEOF1InvalidRelativeOffset}, } { - if _, err := ParseAndValidateEOF1Container(common.FromHex(test.code), &shanghaiInstructionSet); err == nil { + if _, err := parseEOF(common.FromHex(test.code), &shanghaiInstructionSet); err == nil { t.Errorf("test %d: expected invalid code: %v", i, test.code) } else if !strings.HasPrefix(err.Error(), test.err.Error()) { t.Errorf("test %d: want error: \"%v\" have error: \"%v\"", i, test.err, err.Error()) @@ -354,7 +354,7 @@ func TestValidateUndefinedInstructions(t *testing.T) { continue } code[7] = byte(opcode) - _, err := ParseAndValidateEOF1Container(code, jt) + _, err := parseEOF(code, jt) if !jt[opcode].undefined { if err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) @@ -384,7 +384,7 @@ func TestValidateTerminatingInstructions(t *testing.T) { continue } code[7] = byte(opcode) - _, err := ParseAndValidateEOF1Container(code, jt) + _, err := parseEOF(code, jt) if opcode.isTerminating() { if err != nil { t.Errorf("opcode %v expected to be valid terminating instruction", opcode) @@ -411,7 +411,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeTruncatedPush[5] = byte(len(codeTruncatedPush) - 7) codeTruncatedPush[7] = byte(opcode) - if _, err := ParseAndValidateEOF1Container(codeTruncatedPush, jt); err == nil { + if _, err := parseEOF(codeTruncatedPush, jt); err == nil { t.Errorf("code %v has truncated PUSH, expected to be invalid", common.Bytes2Hex(codeTruncatedPush)) } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeTruncatedPush), err) @@ -422,7 +422,7 @@ func TestValidateTruncatedPush(t *testing.T) { codeNotTerminated[5] = byte(len(codeNotTerminated) - 7) codeNotTerminated[7] = byte(opcode) - if _, err := ParseAndValidateEOF1Container(codeNotTerminated, jt); err == nil { + if _, err := parseEOF(codeNotTerminated, jt); err == nil { t.Errorf("code %v does not have terminating instruction, expected to be invalid", common.Bytes2Hex(codeNotTerminated)) } else if !strings.HasPrefix(err.Error(), ErrEOF1TerminatingInstructionMissing.Error()) { t.Errorf("code %v unexpected validation error: %v", common.Bytes2Hex(codeNotTerminated), err) @@ -433,8 +433,19 @@ func TestValidateTruncatedPush(t *testing.T) { codeValid[5] = byte(len(codeValid) - 7) codeValid[7] = byte(opcode) - if _, err := ParseAndValidateEOF1Container(codeValid, jt); err != nil { + if _, err := parseEOF(codeValid, jt); err != nil { t.Errorf("code %v instruction validation failure, error: %v", common.Bytes2Hex(code), err) } } } + +func parseEOF(b []byte, jt *JumpTable) (*EOF1Container, error) { + c, err := ParseEOF1Container(b) + if err != nil { + return nil, err + } + if err := c.ValidateCode(jt); err != nil { + return nil, err + } + return c, nil +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 62f8eaf065a..57052651684 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -400,12 +400,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err == nil && hasEOFByte(ret) { if evm.chainRules.IsShanghai { - evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + c, err := ParseEOF1Container(ret) + if err != nil { + err = ErrInvalidEOFCode + } + evmInterpreter, ok := evm.Interpreter().(*EVMInterpreter) if !ok { return nil, common.Address{}, gas, ErrInvalidInterpreter } - _, err := ParseAndValidateEOF1Container(ret, evmInterpreter.jt) - if err != nil { + if err := c.ValidateCode(evmInterpreter.jt); err != nil { err = ErrInvalidEOFCode } } else if evm.chainRules.IsLondon { @@ -460,12 +463,15 @@ func (evm *EVM) mustParseContainer(code []byte) *EOF1Container { func (evm *EVM) parseContainer(code []byte) (*EOF1Container, error) { var container *EOF1Container if evm.chainRules.IsShanghai && hasEOFMagic(code) { - evmInterpreter, ok := evm.interpreter.(*EVMInterpreter) + c, err := ParseEOF1Container(code) + if err != nil { + return nil, err + } + evmInterpreter, ok := evm.Interpreter().(*EVMInterpreter) if !ok { return nil, ErrInvalidInterpreter } - c, err := ParseAndValidateEOF1Container(code, evmInterpreter.jt) - if err != nil { + if err := c.ValidateCode(evmInterpreter.jt); err != nil { return nil, err } container = c From 011293d41a5d16f98e9e37557d4407a0754bf321 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 15 Nov 2022 14:03:42 -0700 Subject: [PATCH 21/49] core/vm: refactor eof more, move validation to separate file --- core/vm/eof.go | 242 ++++++++++-------------------------------- core/vm/validation.go | 76 +++++++++++++ 2 files changed, 134 insertions(+), 184 deletions(-) create mode 100644 core/vm/validation.go diff --git a/core/vm/eof.go b/core/vm/eof.go index e27546c8356..3d19ab96c6e 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -22,44 +22,20 @@ import ( "fmt" ) -type eofVersion int -type headerSection byte +// eofMagic is the prefix denoting a contract is an EOF contract. +var eofMagic []byte = []byte{0xEF, 0x00} const ( - eofFormatByte byte = 0xEF - eof1Version eofVersion = 1 - kindTerminator headerSection = 0 - kindCode headerSection = 1 - kindData headerSection = 2 - kindType headerSection = 3 - - versionOffset int = 2 - sectionHeaderStart int = 3 + eofFormatByte byte = 0xEF + eof1Version int = 1 ) -var eofMagic []byte = []byte{0xEF, 0x00} - -// 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 { - return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)]) -} - -// isEOFVersion1 returns true if the code's version byte equals eof1Version. It -// does not verify the EOF magic is valid. -func isEOFVersion1(code []byte) bool { - return versionOffset < len(code) && code[versionOffset] == byte(eof1Version) -} - -// Annotation represents an EOF v1 function signature. -type Annotation struct { - Input uint8 // number of stack inputs - Output uint8 // number of stack outputs -} +const ( + kindTerminator int = iota + kindCode + kindData + kindType +) // EOF1Container is an EOF v1 container object. type EOF1Container struct { @@ -77,6 +53,12 @@ type EOF1Container struct { data []byte } +// Annotation represents an EOF v1 function signature. +type Annotation struct { + Input uint8 // number of stack inputs + Output uint8 // number of stack outputs +} + // ParseEOF1Container parses an EOF v1 container from the provided byte slice. // // This function ensures that the container is well-formed (e.g. size values @@ -84,10 +66,10 @@ type EOF1Container struct { // code validation on the container. func ParseEOF1Container(b []byte) (*EOF1Container, error) { var c EOF1Container - if err := c.parseHeader(b); err != nil { + idx, err := c.parseHeader(b) + if err != nil { return nil, err } - idx := c.HeaderSize() // Read type section if it exists. if typeSize := c.typeSize; typeSize != 0 { c.types = parseTypeSection(b[idx : idx+int(typeSize)]) @@ -104,42 +86,24 @@ func ParseEOF1Container(b []byte) (*EOF1Container, error) { return &c, nil } -// HeaderSize returns the total size of the EOF1 header. -func (c *EOF1Container) HeaderSize() int { - var ( - typeHeaderSize = 0 - codeHeaderSize = len(c.codeSize) * 3 - dataHeaderSize = 0 - ) - if c.typeSize != 0 { - typeHeaderSize = 3 - } - if c.dataSize != 0 { - dataHeaderSize = 3 - } - // len(magic) + version + typeHeader + codeHeader + dataHeader + terminator - return 2 + 1 + typeHeaderSize + codeHeaderSize + dataHeaderSize + 1 -} - // ValidateCode performs the EOF v1 code validation on the container. func (c *EOF1Container) ValidateCode(jt *JumpTable) error { - idx := c.HeaderSize() - for _, size := range c.codeSize { - if err := validateInstructions(c.data[idx:idx+int(size)], len(c.codeSize), jt); err != nil { + for i, start := range c.codeOffsets { + end := start + uint64(c.codeSize[i]) + if err := validateCode(c.data[start:end], len(c.codeSize), jt); err != nil { return err } - idx += int(size) } return nil } // parseEOF1Header attempts to parse an EOF1-formatted code header. -func (c *EOF1Container) parseHeader(b []byte) error { +func (c *EOF1Container) parseHeader(b []byte) (int, error) { if !hasEOFMagic(b) || !isEOFVersion1(b) { - return ErrEOF1InvalidVersion + return 0, ErrEOF1InvalidVersion } var ( - i = sectionHeaderStart + i = 3 err error typeRead int codeRead int @@ -148,26 +112,26 @@ func (c *EOF1Container) parseHeader(b []byte) error { ) outer: for i < len(b) { - switch headerSection(b[i]) { + switch int(b[i]) { case kindTerminator: i += 1 break outer case kindType: // Type section header must be read first. if codeRead != 0 || dataRead != 0 { - return ErrEOF1TypeSectionHeaderAfterOthers + return i, ErrEOF1TypeSectionHeaderAfterOthers } // Only 1 type section is allowed. if typeRead != 0 { - return ErrEOF1MultipleTypeSections + return i, ErrEOF1MultipleTypeSections } // Size must be present. if c.typeSize, err = parseSectionSize(b, i+1); err != nil { - return ErrEOF1TypeSectionSizeMissing + return i, ErrEOF1TypeSectionSizeMissing } // Type section size must not be 0. if c.typeSize == 0 { - return ErrEOF1EmptyDataSection + return i, ErrEOF1EmptyDataSection } typeRead += 1 case kindCode: @@ -175,11 +139,11 @@ outer: var size uint16 size, err = parseSectionSize(b, i+1) if err != nil { - return ErrEOF1CodeSectionSizeMissing + return i, ErrEOF1CodeSectionSizeMissing } // Size must not be 0. if size == 0 { - return ErrEOF1EmptyCodeSection + return i, ErrEOF1EmptyCodeSection } c.codeSize = append(c.codeSize, size) totalCodeSize += int(size) @@ -187,44 +151,44 @@ outer: case kindData: // Data section is allowed only after code section. if codeRead == 0 { - return ErrEOF1DataSectionBeforeCodeSection + return i, ErrEOF1DataSectionBeforeCodeSection } // Only 1 data section is allowed. if dataRead != 0 { - return ErrEOF1MultipleDataSections + return i, ErrEOF1MultipleDataSections } // Size must be present. if c.dataSize, err = parseSectionSize(b, i+1); err != nil { - return ErrEOF1DataSectionSizeMissing + return i, ErrEOF1DataSectionSizeMissing } // Data section size must not be 0. if c.dataSize == 0 { - return ErrEOF1EmptyDataSection + return i, ErrEOF1EmptyDataSection } dataRead += 1 default: - return ErrEOF1UnknownSection + return i, ErrEOF1UnknownSection } i += 3 } // 1 code section is required. if codeRead < 1 { - return ErrEOF1CodeSectionMissing + return i, ErrEOF1CodeSectionMissing } // 1024 max code sections. if len(c.codeSize) > 1024 { - return ErrEOF1CodeSectionOverflow + return i, ErrEOF1CodeSectionOverflow } // Must have type section if more than one code section. if len(c.codeSize) > 1 && c.typeSize == 0 { - return ErrEOF1TypeSectionMissing + return i, ErrEOF1TypeSectionMissing } // Declared section sizes must correspond to real size (trailing bytes are not allowed.) - if c.HeaderSize()+int(c.typeSize)+totalCodeSize+int(c.dataSize) != len(b) { - return ErrEOF1InvalidTotalSize + if i+int(c.typeSize)+totalCodeSize+int(c.dataSize) != len(b) { + return i, ErrEOF1InvalidTotalSize } - return nil + return i, nil } // parseSectionSize returns the size of the section at the offset i. @@ -235,112 +199,6 @@ func parseSectionSize(code []byte, i int) (uint16, error) { return binary.BigEndian.Uint16(code[i : i+2]), nil } -// validateInstructions checks that there're no undefined instructions and code ends with a terminating instruction -func validateInstructions(code []byte, codeSections int, jumpTable *JumpTable) error { - var ( - i = 0 - analysis = codeBitmap(code) - opcode OpCode - ) - for i < len(code) { - switch opcode = OpCode(code[i]); { - case jumpTable[opcode].undefined: - return fmt.Errorf("%v: %v", ErrEOF1UndefinedInstruction, opcode) - case opcode >= PUSH1 && opcode <= PUSH32: - i += int(opcode) - int(PUSH1) + 2 - continue // todo make sure this actually continues - case opcode == RJUMP || opcode == RJUMPI: - var arg int16 - // Read immediate argument. - if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { - return ErrEOF1InvalidRelativeOffset - } - // Calculate relative target. - pos := i + 3 + int(arg) - - // Check if offset points to out-of-bounds location. - if pos < 0 || pos >= len(code) { - return ErrEOF1InvalidRelativeOffset - } - // Check if offset points to non-code segment. - // TODO(matt): include CALLF and RJUMPs in analysis. - if analysis[uint64(pos)/64]&(uint64(1)<<(uint64(pos)&63)) != 0 { - return ErrEOF1InvalidRelativeOffset - } - i += 3 - case opcode == CALLF: - var arg int16 - // Read immediate argument. - if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { - return fmt.Errorf("%v: %v", ErrEOF1InvalidCallfSection, err) - } - if int(arg) >= codeSections { - return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, codeSections) - } - i += 3 - default: - i++ - } - } - if !opcode.isTerminating() { - return fmt.Errorf("%v: %v", ErrEOF1TerminatingInstructionMissing, opcode) - } - return nil -} - -// uncheckedParseContainer parses an EOF v1 container without performing -// validation. -func uncheckedParseContainer(code []byte) *EOF1Container { - var c EOF1Container - // If the code has been validated (e.g. during deployment), skip the - // full validation. - c.typeSize, c.codeSize, c.dataSize = parseHeaderUnchecked(code) - - // Set index to first byte after EOF header. - idx := c.HeaderSize() - - // Read type section if it exists. - if typeSize := c.typeSize; typeSize != 0 { - c.types = parseTypeSection(code[idx : idx+int(typeSize)]) - idx += int(typeSize) - } - // Calculate starting offset for each code section. - for _, size := range c.codeSize { - c.codeOffsets = append(c.codeOffsets, uint64(idx)) - idx += int(size) - } - // Set data offset. - c.dataOffset = uint64(idx) - return &c -} - -// parseHeaderUnchecked parses an EOF v1 header without performing validation. -func parseHeaderUnchecked(code []byte) (typeSize uint16, codeSize []uint16, dataSize uint16) { - var ( - i = sectionHeaderStart - codeSections = uint16(1) - ) - // Try to read type section. - if code[i] == byte(kindType) { - size, _ := parseSectionSize(code, i+1) - typeSize = size - codeSections = size / 2 - i += 3 - } - i += 1 - // Read code sections. - for j := 0; j < int(codeSections); j++ { - size := binary.BigEndian.Uint16(code[i+3*j : i+3*j+2]) - codeSize = append(codeSize, size) - } - i += int(2 * codeSections) - // Try to read data section. - if code[i] == byte(kindData) { - dataSize = binary.BigEndian.Uint16(code[i+1 : i+3]) - } - return -} - // parseTypeSection parses an EOF type section. func parseTypeSection(code []byte) (out []Annotation) { for i := 0; i < len(code)-1; i += 2 { @@ -352,3 +210,19 @@ func parseTypeSection(code []byte) (out []Annotation) { } return } + +// 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 { + return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)]) +} + +// isEOFVersion1 returns true if the code's version byte equals eof1Version. It +// does not verify the EOF magic is valid. +func isEOFVersion1(code []byte) bool { + return 2 < len(code) && code[2] == byte(eof1Version) +} diff --git a/core/vm/validation.go b/core/vm/validation.go new file mode 100644 index 00000000000..1c3caee6e9b --- /dev/null +++ b/core/vm/validation.go @@ -0,0 +1,76 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package vm + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// validateCode checks that there're no undefined instructions and code ends with a terminating instruction +func validateCode(code []byte, codeSections int, jumpTable *JumpTable) error { + var ( + i = 0 + analysis = codeBitmap(code) + opcode OpCode + ) + for i < len(code) { + switch opcode = OpCode(code[i]); { + case jumpTable[opcode].undefined: + return fmt.Errorf("%v: %v", ErrEOF1UndefinedInstruction, opcode) + case opcode >= PUSH1 && opcode <= PUSH32: + i += int(opcode) - int(PUSH1) + 2 + continue // todo make sure this actually continues + case opcode == RJUMP || opcode == RJUMPI: + var arg int16 + // Read immediate argument. + if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { + return ErrEOF1InvalidRelativeOffset + } + // Calculate relative target. + pos := i + 3 + int(arg) + + // Check if offset points to out-of-bounds location. + if pos < 0 || pos >= len(code) { + return ErrEOF1InvalidRelativeOffset + } + // Check if offset points to non-code segment. + // TODO(matt): include CALLF and RJUMPs in analysis. + if analysis[uint64(pos)/64]&(uint64(1)<<(uint64(pos)&63)) != 0 { + return ErrEOF1InvalidRelativeOffset + } + i += 3 + case opcode == CALLF: + var arg int16 + // Read immediate argument. + if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { + return fmt.Errorf("%v: %v", ErrEOF1InvalidCallfSection, err) + } + if int(arg) >= codeSections { + return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, codeSections) + } + i += 3 + default: + i++ + } + } + if !opcode.isTerminating() { + return fmt.Errorf("%v: %v", ErrEOF1TerminatingInstructionMissing, opcode) + } + return nil +} From d54f5c3f08feb46afea2d9ca14e7f02ffd90f0f5 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 15 Nov 2022 14:38:22 -0700 Subject: [PATCH 22/49] core/vm: misc clean up --- core/vm/contract.go | 4 ++++ core/vm/eips.go | 2 ++ core/vm/eof.go | 9 ++++++++ core/vm/instructions_test.go | 43 ++++++++++++++++++++++++++++++++++++ core/vm/interpreter.go | 5 +++++ core/vm/jump_table.go | 5 ++++- core/vm/validation.go | 15 ++++++++----- 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index 0dd652c281f..aa874269499 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -115,6 +115,10 @@ func (c *Contract) isCode(udest uint64) bool { // TODO remove this panic("shouldn't run jumpdest analysis on EOF") } + // Do we already have an analysis laying around? + if c.analysis != nil { + return c.analysis[udest/64]&(uint64(1)<<(udest&63)) == 0 + } // Do we have a contract hash already? // If we do have a hash, that means it's a 'regular' contract. For regular // contracts ( not temporary initcode), we store the analysis in a map diff --git a/core/vm/eips.go b/core/vm/eips.go index 1696ec8deda..bdac1e2b496 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -277,6 +277,8 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // enable4750 applies EIP-4750 (CALLF and RETF opcodes) func enable4750(jt *JumpTable) { + jt[JUMP].legacyOnly = true + jt[JUMPI].legacyOnly = true jt[CALLF] = &operation{ execute: opCallf, constantGas: GasMidStep, diff --git a/core/vm/eof.go b/core/vm/eof.go index 3d19ab96c6e..6c8bc9a0aa1 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -211,6 +211,15 @@ func parseTypeSection(code []byte) (out []Annotation) { return } +func parseArg(b []byte) (int16, error) { + if len(b) < 2 { + return 0, fmt.Errorf("argument missing") + } + arg := int16(b[0]) << 8 + arg += int16(b[1]) + return arg, nil +} + // hasEOFByte returns true if code starts with 0xEF byte func hasEOFByte(code []byte) bool { return len(code) != 0 && code[0] == eofFormatByte diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 7d911c9a667..e60ab412c49 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -559,6 +559,49 @@ func BenchmarkOpMstore(bench *testing.B) { } } +func TestOpTstore(t *testing.T) { + var ( + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env, env.Config) + caller = common.Address{} + to = common.Address{1} + contractRef = contractRef{caller} + contract = NewContract(contractRef, AccountRef(to), new(big.Int), 0) + scopeContext = ScopeContext{mem, stack, contract, 0, nil} + value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") + ) + + // Add a stateObject for the caller and the contract being called + statedb.CreateAccount(caller) + statedb.CreateAccount(to) + + env.interpreter = evmInterpreter + pc := uint64(0) + // push the value to the stack + stack.push(new(uint256.Int).SetBytes(value)) + // push the location to the stack + stack.push(new(uint256.Int)) + opTstore(&pc, evmInterpreter, &scopeContext) + // there should be no elements on the stack after TSTORE + if stack.len() != 0 { + t.Fatal("stack wrong size") + } + // push the location to the stack + stack.push(new(uint256.Int)) + opTload(&pc, evmInterpreter, &scopeContext) + // there should be one element on the stack after TLOAD + if stack.len() != 1 { + t.Fatal("stack wrong size") + } + val := stack.peek() + if !bytes.Equal(val.Bytes(), value) { + t.Fatal("incorrect element read from transient storage") + } +} + func BenchmarkOpKeccak256(bench *testing.B) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index cf20b8b0d0c..3e105e719cd 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -383,6 +383,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( _, err = opUndefined(&pc, in, callContext) break } + // fail if op is not available in eof context + if operation.legacyOnly && !callContext.Contract.IsLegacy() { + _, err = opUndefined(&pc, in, callContext) + break + } // execute the operation res, err = operation.execute(&pc, in, callContext) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 7918eb396b1..249bd5516a9 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -52,8 +52,11 @@ type operation struct { undefined bool - // eof1 specifies if an instrustion should only be available in eof1 + // eof1 specifies if an instruction should only be available in eof1 eof1 bool + + // legacyOnly specifies if an instruction should only be available in legacy evm + legacyOnly bool } var ( diff --git a/core/vm/validation.go b/core/vm/validation.go index 1c3caee6e9b..894fd2aebd2 100644 --- a/core/vm/validation.go +++ b/core/vm/validation.go @@ -26,7 +26,7 @@ import ( func validateCode(code []byte, codeSections int, jumpTable *JumpTable) error { var ( i = 0 - analysis = codeBitmap(code) + analysis []byte opcode OpCode ) for i < len(code) { @@ -51,15 +51,18 @@ func validateCode(code []byte, codeSections int, jumpTable *JumpTable) error { } // Check if offset points to non-code segment. // TODO(matt): include CALLF and RJUMPs in analysis. - if analysis[uint64(pos)/64]&(uint64(1)<<(uint64(pos)&63)) != 0 { + if analysis == nil { + // TODO CZ: implement the logic https://github.com/ethereum/go-ethereum/pull/26133/commits/370c24c344f7fc26cc29fba9b33825baeded8876#diff-882af11429522494f308c57017fe1c44e86b338e3bf5c308584d9d187606d252L64 + // analysis = codeBitmap(code) + } + if analysis[pos/64]&(1<<(uint64(pos)&63)) != 0 { return ErrEOF1InvalidRelativeOffset } i += 3 case opcode == CALLF: - var arg int16 - // Read immediate argument. - if err := binary.Read(bytes.NewReader(code[i+1:]), binary.BigEndian, &arg); err != nil { - return fmt.Errorf("%v: %v", ErrEOF1InvalidCallfSection, err) + arg, err := parseArg(code[i+1:]) + if err != nil { + return ErrEOF1InvalidCallfSection } if int(arg) >= codeSections { return fmt.Errorf("%v: want section %v, but only have %d sections", ErrEOF1InvalidCallfSection, arg, codeSections) From 2572feb1cefd66519316c98444005f196a1fa220 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 15 Nov 2022 17:38:00 -0700 Subject: [PATCH 23/49] core/vm: use parse arg helper --- core/vm/eips.go | 38 ++++++++++++-------------------------- core/vm/eof.go | 11 +++++++++++ 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index bdac1e2b496..bfeaa50aeb7 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -17,8 +17,6 @@ package vm import ( - "bytes" - "encoding/binary" "fmt" "sort" @@ -233,11 +231,14 @@ func enable4200(jt *JumpTable) { // opRjump implements the RJUMP opcode func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - idx = scope.Contract.Container.codeOffsets[scope.ActiveSection] + *pc + 1 - arg = scope.Contract.Code[idx : idx+2] - relativeOffset int16 + code = scope.Contract.Container.CodeAt(int(scope.ActiveSection)) + idx = *pc + 1 ) - binary.Read(bytes.NewReader(arg), binary.BigEndian, &relativeOffset) + + relativeOffset, err := parseArg(code[idx : idx+2]) + if err != nil { + return nil, err + } // Move PC past the RJUMP instruction and its immediate argument. *pc += 2 + 1 @@ -257,22 +258,7 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b *pc += 2 return nil, nil } - - var ( - idx = scope.Contract.Container.codeOffsets[scope.ActiveSection] + *pc + 1 - arg = scope.Contract.Code[idx : idx+2] - relativeOffset int16 - ) - binary.Read(bytes.NewReader(arg), binary.BigEndian, &relativeOffset) - - // Move PC past the RJUMP instruction and its immediate argument. - *pc += 2 + 1 - - // Calculate the new PC given the relative offset. Already validated, - // so no need to verify casts. - *pc = uint64(int64(*pc)+int64(relativeOffset)) - 1 // pc will also be increased by interpreter loop - - return nil, nil + return opRjump(pc, interpreter, scope) } // enable4750 applies EIP-4750 (CALLF and RETF opcodes) @@ -298,11 +284,11 @@ func enable4750(jt *JumpTable) { // opCallf implements the CALLF opcode. func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - idx = scope.Contract.Container.codeOffsets[scope.ActiveSection] + *pc + 1 - arg = scope.Contract.Code[idx : idx+2] - section int16 + code = scope.Contract.Container.CodeAt(int(scope.ActiveSection)) + idx = *pc + 1 ) - if err := binary.Read(bytes.NewReader(arg), binary.BigEndian, §ion); err != nil { + section, err := parseArg(code[idx : idx+2]) + if err != nil { return nil, err } caller := scope.RetStack[len(scope.RetStack)-1] diff --git a/core/vm/eof.go b/core/vm/eof.go index 6c8bc9a0aa1..8c658a7e4ac 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -97,6 +97,16 @@ func (c *EOF1Container) ValidateCode(jt *JumpTable) error { return nil } +// CodeAt returns the code section at the specified index. +func (c *EOF1Container) CodeAt(section int) []byte { + if len(c.codeOffsets) <= section { + return nil + } + idx := c.codeOffsets[section] + size := uint64(c.codeSize[section]) + return c.data[idx : idx+size] +} + // parseEOF1Header attempts to parse an EOF1-formatted code header. func (c *EOF1Container) parseHeader(b []byte) (int, error) { if !hasEOFMagic(b) || !isEOFVersion1(b) { @@ -211,6 +221,7 @@ func parseTypeSection(code []byte) (out []Annotation) { return } +// parseArg returns the int16 located at b[0:2]. func parseArg(b []byte) (int16, error) { if len(b) < 2 { return 0, fmt.Errorf("argument missing") From 8ff24936416520dd7526ba85afe5c7567eabc43b Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Wed, 16 Nov 2022 15:16:03 -0700 Subject: [PATCH 24/49] tests: add shanghai to tests list --- tests/init.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/init.go b/tests/init.go index e760f037ecd..7b3caf5ab76 100644 --- a/tests/init.go +++ b/tests/init.go @@ -227,7 +227,7 @@ var Forks = map[string]*params.ChainConfig{ MergeNetsplitBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), }, - "ArrowGlacierToMergeAtDiffC0000": { + "Shanghai": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), TangerineWhistleBlock: big.NewInt(0), @@ -240,6 +240,7 @@ var Forks = map[string]*params.ChainConfig{ BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0), + ShanghaiBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0xC0000), }, } From 528b340d5b5011edc806ffe172d35d2eafe64c8e Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Wed, 16 Nov 2022 15:45:20 -0700 Subject: [PATCH 25/49] core/vm: don't validate code if EOF parse fails --- core/vm/evm.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 57052651684..7b589bdd9a6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -400,15 +400,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err == nil && hasEOFByte(ret) { if evm.chainRules.IsShanghai { - c, err := ParseEOF1Container(ret) - if err != nil { - err = ErrInvalidEOFCode - } evmInterpreter, ok := evm.Interpreter().(*EVMInterpreter) if !ok { return nil, common.Address{}, gas, ErrInvalidInterpreter } - if err := c.ValidateCode(evmInterpreter.jt); err != nil { + + c, err := ParseEOF1Container(ret) + if err != nil { + err = ErrInvalidEOFCode + } else if err := c.ValidateCode(evmInterpreter.jt); err != nil { err = ErrInvalidEOFCode } } else if evm.chainRules.IsLondon { From d338bd0bd7099e2fb1d07e13f5591b7b48737c42 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Thu, 17 Nov 2022 06:19:48 -0700 Subject: [PATCH 26/49] core/vm: update callf and retf ops to b0 and b1 --- core/vm/eof_test.go | 2 +- core/vm/opcodes.go | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index f58b37b3212..bb808a90749 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -107,7 +107,7 @@ func TestEOFContainer(t *testing.T) { wantDataOffset: 17, }, { - code: "EF00010300040100080100020000000201600160025e0001000149", + code: "EF0001030004010008010002000000020160016002b000010001b1", wantCodeSize: []int{8, 2}, wantDataSize: 0, wantTypeSize: 4, diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 3ae73748585..d82d3ed8072 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -122,8 +122,6 @@ const ( JUMPDEST OpCode = 0x5b RJUMP OpCode = 0x5c RJUMPI OpCode = 0x5d - CALLF OpCode = 0x5e - RETF OpCode = 0x49 PUSH0 OpCode = 0x5f ) @@ -204,11 +202,10 @@ const ( LOG4 ) -// unofficial opcodes used for parsing. +// 0xb0 range - control flow ops. const ( - PUSH OpCode = 0xb0 + iota - DUP - SWAP + CALLF OpCode = 0xb0 + iota + RETF ) // 0xf0 range - closures. @@ -390,10 +387,6 @@ var opCodeToString = map[OpCode]string{ REVERT: "REVERT", INVALID: "INVALID", SELFDESTRUCT: "SELFDESTRUCT", - - PUSH: "PUSH", - DUP: "DUP", - SWAP: "SWAP", } func (op OpCode) String() string { From ef63306604c3cfd174ac5ca2a11c76bba94625b1 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Thu, 17 Nov 2022 06:30:55 -0700 Subject: [PATCH 27/49] core/vm: ensure type section size is 2n code sections --- core/vm/contract.go | 4 ++-- core/vm/eips.go | 4 ++-- core/vm/eof.go | 19 +++++++++++++------ core/vm/eof_test.go | 4 ++++ core/vm/errors.go | 17 ++++++++++------- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index aa874269499..a8477372d05 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -176,8 +176,8 @@ func (c *Contract) GetByte(n uint64) byte { // GetOpInSection returns the n'th element in the code sections's byte array. func (c *Contract) GetOpInSection(n uint64, s uint64) OpCode { if n < uint64(c.Container.codeSize[s]) { - start := c.Container.codeOffsets[s] - return OpCode(c.Code[start+n]) + code := c.Container.CodeAt(s) + return OpCode(code[n]) } return STOP diff --git a/core/vm/eips.go b/core/vm/eips.go index bfeaa50aeb7..b167ac2bbde 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -231,7 +231,7 @@ func enable4200(jt *JumpTable) { // opRjump implements the RJUMP opcode func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.Container.CodeAt(int(scope.ActiveSection)) + code = scope.Contract.Container.CodeAt(scope.ActiveSection) idx = *pc + 1 ) @@ -284,7 +284,7 @@ func enable4750(jt *JumpTable) { // opCallf implements the CALLF opcode. func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - code = scope.Contract.Container.CodeAt(int(scope.ActiveSection)) + code = scope.Contract.Container.CodeAt(scope.ActiveSection) idx = *pc + 1 ) section, err := parseArg(code[idx : idx+2]) diff --git a/core/vm/eof.go b/core/vm/eof.go index 8c658a7e4ac..d3e76e9cc46 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -71,9 +71,12 @@ func ParseEOF1Container(b []byte) (*EOF1Container, error) { return nil, err } // Read type section if it exists. - if typeSize := c.typeSize; typeSize != 0 { - c.types = parseTypeSection(b[idx : idx+int(typeSize)]) - idx += int(typeSize) + if ts := int(c.typeSize); ts != 0 { + c.types = parseTypeSection(b[idx : idx+int(ts)]) + if c.types[0].Input != 0 || c.types[0].Output != 0 { + return nil, ErrEOF1TypeSectionFirstEntryNonZero + } + idx += ts } // Calculate starting offset for each code section. for _, size := range c.codeSize { @@ -98,8 +101,8 @@ func (c *EOF1Container) ValidateCode(jt *JumpTable) error { } // CodeAt returns the code section at the specified index. -func (c *EOF1Container) CodeAt(section int) []byte { - if len(c.codeOffsets) <= section { +func (c *EOF1Container) CodeAt(section uint64) []byte { + if len(c.codeOffsets) <= int(section) { return nil } idx := c.codeOffsets[section] @@ -133,7 +136,7 @@ outer: } // Only 1 type section is allowed. if typeRead != 0 { - return i, ErrEOF1MultipleTypeSections + return i, ErrEOF1TypeSectionDuplicate } // Size must be present. if c.typeSize, err = parseSectionSize(b, i+1); err != nil { @@ -194,6 +197,10 @@ outer: if len(c.codeSize) > 1 && c.typeSize == 0 { return i, ErrEOF1TypeSectionMissing } + // If type section, ensure type section size is 2n the number of code sections. + if c.typeSize != 0 && len(c.codeSize) != int(c.typeSize/2) { + return i, ErrEOF1TypeSectionInvalidSize + } // Declared section sizes must correspond to real size (trailing bytes are not allowed.) if i+int(c.typeSize)+totalCodeSize+int(c.dataSize) != len(b) { return i, ErrEOF1InvalidTotalSize diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index bb808a90749..9952ed60c0e 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -257,6 +257,10 @@ func TestEOFContainer(t *testing.T) { code: "EF00010100020F0004006000AABBCCDD", wantErr: ErrEOF1UnknownSection, }, + { + code: "0xEF000103000401000801000201000801000200303030303030303030303000300030303030303030003000", + wantErr: ErrEOF1TypeSectionInvalidSize, + }, } { var ( code = common.FromHex(test.code) diff --git a/core/vm/errors.go b/core/vm/errors.go index a8d669d1cf3..ca81c3be2a9 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -65,14 +65,17 @@ var ( ErrEOF1UndefinedInstruction = errors.New("undefined instruction") ErrEOF1TerminatingInstructionMissing = errors.New("code section doesn't end with terminating instruction") ErrEOF1InvalidRelativeOffset = errors.New("relative offset points to immediate argument") - ErrEOF1TypeSectionMissing = errors.New("no type section") - ErrEOF1TypeSectionInvalidSize = errors.New("invalid type section size") ErrEOF1CodeSectionOverflow = errors.New("too many code sections") - ErrEOF1MultipleTypeSections = errors.New("multiple type sections") - ErrEOF1TypeSectionHeaderAfterOthers = errors.New("type section header after other section header") - ErrEOF1TypeSectionSizeMissing = errors.New("can't read type section size") - ErrEOF1EmptyTypeSection = errors.New("type section size is 0") - ErrEOF1InvalidCallfSection = errors.New("invalid section in CALLF immediate") + + ErrEOF1TypeSectionDuplicate = errors.New("multiple type sections") + ErrEOF1TypeSectionMissing = errors.New("no type section") + ErrEOF1TypeSectionInvalidSize = errors.New("invalid type section size") + ErrEOF1TypeSectionHeaderAfterOthers = errors.New("type section header after other section header") + ErrEOF1TypeSectionSizeMissing = errors.New("can't read type section size") + ErrEOF1TypeSectionEmpty = errors.New("type section size is 0") + ErrEOF1TypeSectionFirstEntryNonZero = errors.New("first entry of type section must have 0 input and 0 output") + + ErrEOF1InvalidCallfSection = errors.New("invalid section in CALLF immediate") ) // ErrStackUnderflow wraps an evm error when the items on the stack less From 63ff89e0776c42526521acdd284254afcda29096 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Thu, 17 Nov 2022 12:57:35 -0700 Subject: [PATCH 28/49] core/vm: properly return error during contract deploy --- core/vm/evm.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 7b589bdd9a6..db31b68e17d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -405,10 +405,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrInvalidInterpreter } - c, err := ParseEOF1Container(ret) + var c *EOF1Container + c, err = ParseEOF1Container(ret) if err != nil { err = ErrInvalidEOFCode - } else if err := c.ValidateCode(evmInterpreter.jt); err != nil { + } else if err = c.ValidateCode(evmInterpreter.jt); err != nil { err = ErrInvalidEOFCode } } else if evm.chainRules.IsLondon { From 7d4305e0c3a5dc26e6cfcac5556aa07ad1debdfc Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Thu, 1 Dec 2022 11:42:39 +0200 Subject: [PATCH 29/49] core/vm: remove OpTstore test as it's not implemented yet --- core/vm/instructions_test.go | 43 ------------------------------------ 1 file changed, 43 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 50e4634b83c..800e46628de 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -560,49 +560,6 @@ func BenchmarkOpMstore(bench *testing.B) { } } -func TestOpTstore(t *testing.T) { - var ( - statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) - stack = newstack() - mem = NewMemory() - evmInterpreter = NewEVMInterpreter(env, env.Config) - caller = common.Address{} - to = common.Address{1} - contractRef = contractRef{caller} - contract = NewContract(contractRef, AccountRef(to), new(big.Int), 0) - scopeContext = ScopeContext{mem, stack, contract, 0, nil} - value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") - ) - - // Add a stateObject for the caller and the contract being called - statedb.CreateAccount(caller) - statedb.CreateAccount(to) - - env.interpreter = evmInterpreter - pc := uint64(0) - // push the value to the stack - stack.push(new(uint256.Int).SetBytes(value)) - // push the location to the stack - stack.push(new(uint256.Int)) - opTstore(&pc, evmInterpreter, &scopeContext) - // there should be no elements on the stack after TSTORE - if stack.len() != 0 { - t.Fatal("stack wrong size") - } - // push the location to the stack - stack.push(new(uint256.Int)) - opTload(&pc, evmInterpreter, &scopeContext) - // there should be one element on the stack after TLOAD - if stack.len() != 1 { - t.Fatal("stack wrong size") - } - val := stack.peek() - if !bytes.Equal(val.Bytes(), value) { - t.Fatal("incorrect element read from transient storage") - } -} - func BenchmarkOpKeccak256(bench *testing.B) { var ( env = NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, params.TestChainConfig, Config{}) From e4f80037e60ac9f12aca5d9da111e9de1d93f23d Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Thu, 1 Dec 2022 11:53:20 +0200 Subject: [PATCH 30/49] core/vm: fix lint issues after merge --- core/vm/eof.go | 2 +- core/vm/instructions_test.go | 40 ++++++++++++++++++------------------ go.mod | 3 ++- go.sum | 8 +++++--- tests/testdata | 2 +- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/core/vm/eof.go b/core/vm/eof.go index d3e76e9cc46..aee820fa499 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -72,7 +72,7 @@ func ParseEOF1Container(b []byte) (*EOF1Container, error) { } // Read type section if it exists. if ts := int(c.typeSize); ts != 0 { - c.types = parseTypeSection(b[idx : idx+int(ts)]) + c.types = parseTypeSection(b[idx : idx+ts]) if c.types[0].Input != 0 || c.types[0].Output != 0 { return nil, ErrEOF1TypeSectionFirstEntryNonZero } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 800e46628de..a4f72b8e0ad 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -235,7 +235,7 @@ func TestAddMod(t *testing.T) { // getResult is a convenience function to generate the expected values // func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { // var ( -// env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) +// env = NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, params.TestChainConfig, Config{}) // stack = stack.New() // pc = uint64(0) // interpreter = env.interpreter.(*EVMInterpreter) @@ -302,7 +302,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { a.SetBytes(arg) stack.Push(a) } - op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) stack.Pop() } } @@ -666,16 +666,16 @@ func TestRandom(t *testing.T) { {name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})}, } { var ( - env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(evmtypes.BlockContext{PrevRanDao: &tt.random}, evmtypes.TxContext{}, nil, params.TestChainConfig, Config{}) + stack = stack.New() pc = uint64(0) - evmInterpreter = env.interpreter + evmInterpreter = env.interpreter.(*EVMInterpreter) ) - opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) - if len(stack.data) != 1 { - t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) + opDifficulty(&pc, evmInterpreter, &ScopeContext{nil, stack, nil, 0, nil}) + if len(stack.Data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.Data)) } - actual := stack.pop() + actual := stack.Pop() expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes())) if overflow { t.Errorf("Testcase %v: invalid overflow", tt.name) @@ -704,12 +704,12 @@ func TestStaticRelativeJumps(t *testing.T) { } { var ( addr = common.Address{0x42} - env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, params.TestChainConfig, Config{}) + stack = stack.New() pc = uint64(0) - evmInterpreter = env.interpreter + evmInterpreter = env.interpreter.(*EVMInterpreter) code = makeEOF1([][]byte{common.Hex2Bytes(tt.code)}, nil) - contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) + contract = NewContract(AccountRef(addr), AccountRef(addr), uint256.NewInt(0), 0, env.config.SkipAnalysis) container, err = ParseEOF1Container(code) ) if err != nil { @@ -721,9 +721,9 @@ func TestStaticRelativeJumps(t *testing.T) { opRjump(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.codeOffsets[0], uint64(container.codeSize[0])}}}) } else if tt.op == "RJUMPI" { if tt.condition { - stack.push(uint256.NewInt(1)) + stack.Push(uint256.NewInt(1)) } else { - stack.push(uint256.NewInt(0)) + stack.Push(uint256.NewInt(0)) } opRjumpi(&pc, evmInterpreter, &ScopeContext{nil, stack, contract, 0, []*SubroutineContext{{0, 0, 0, container.codeOffsets[0], uint64(container.codeSize[0])}}}) } @@ -750,12 +750,12 @@ func TestEOFFunctions(t *testing.T) { } { var ( addr = common.Address{0x42} - env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, params.TestChainConfig, Config{}) + stack = stack.New() pc = uint64(0) - evmInterpreter = env.interpreter + evmInterpreter = env.interpreter.(*EVMInterpreter) eofCode = makeEOF1(code, []Annotation{{Input: 0, Output: 0}, {Input: 2, Output: 1}}) - contract = NewContract(AccountRef(addr), AccountRef(addr), common.Big0, 0) + contract = NewContract(AccountRef(addr), AccountRef(addr), uint256.NewInt(0), 0, env.config.SkipAnalysis) container, _ = ParseEOF1Container(eofCode) ) contract.SetCallCode(&addr, common.Hash{}, eofCode, container) @@ -768,7 +768,7 @@ func TestEOFFunctions(t *testing.T) { } // Fill stack. for i := 0; i < tt.stack; i++ { - scope.Stack.push(uint256.NewInt(uint64(i))) + scope.Stack.Push(uint256.NewInt(uint64(i))) } pc = 4 if _, err := opCallf(&pc, evmInterpreter, scope); err == nil && tt.err { diff --git a/go.mod b/go.mod index 9e208fd75cb..adc0608d3cc 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/edsrzf/mmap-go v1.1.0 github.com/emicklei/dot v1.0.0 github.com/emirpasic/gods v1.18.1 + github.com/ethereum/go-ethereum v1.10.26 github.com/ferranbt/fastssz v0.1.2 github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c github.com/gballet/go-verkle v0.0.0-20221121182333-31427a1f2d35 @@ -134,7 +135,7 @@ require ( github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.3.2 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect diff --git a/go.sum b/go.sum index de6e10cc600..43095ebd393 100644 --- a/go.sum +++ b/go.sum @@ -274,6 +274,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= @@ -558,7 +560,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3PYPwICLl+/9oulQauOuETfgFvhBDffs0= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/ledgerwatch/erigon-lib v0.0.0-20221130102021-fd11ffca3d4e h1:ra69WM5dWNVthBJr8I5Sv3M6vNjmhUxy0kwqSQ/+SuQ= @@ -657,8 +659,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/tests/testdata b/tests/testdata index 9e058e565e6..c76c5d274e2 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 9e058e565e664ae2ba93f9a25c4beb105df07480 +Subproject commit c76c5d274e271e039b0b11e9372f0c1143dd6c30 From 3b35f1d5602b9aaac5b702c486d9e7b64a1a92b6 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Thu, 1 Dec 2022 11:58:20 +0200 Subject: [PATCH 31/49] core/vm: handle analysis init on validateCode this logic had to be checked after merge of EOF from upstream https://github.com/ETCCooperative/erigon/pull/4#discussion_r1036252354 --- core/vm/validation.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/vm/validation.go b/core/vm/validation.go index 894fd2aebd2..e8d654e23f5 100644 --- a/core/vm/validation.go +++ b/core/vm/validation.go @@ -26,7 +26,7 @@ import ( func validateCode(code []byte, codeSections int, jumpTable *JumpTable) error { var ( i = 0 - analysis []byte + analysis []uint64 opcode OpCode ) for i < len(code) { @@ -52,8 +52,7 @@ func validateCode(code []byte, codeSections int, jumpTable *JumpTable) error { // Check if offset points to non-code segment. // TODO(matt): include CALLF and RJUMPs in analysis. if analysis == nil { - // TODO CZ: implement the logic https://github.com/ethereum/go-ethereum/pull/26133/commits/370c24c344f7fc26cc29fba9b33825baeded8876#diff-882af11429522494f308c57017fe1c44e86b338e3bf5c308584d9d187606d252L64 - // analysis = codeBitmap(code) + analysis = codeBitmap(code) } if analysis[pos/64]&(1<<(uint64(pos)&63)) != 0 { return ErrEOF1InvalidRelativeOffset From 4ea3d3c1ba09c041a1199d409039d7877d493499 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Thu, 1 Dec 2022 12:13:41 +0200 Subject: [PATCH 32/49] core/vm: fix codeBitmap logic --- core/vm/analysis.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 04e608fcea5..83a341bab2d 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -29,8 +29,13 @@ func codeBitmap(code []byte) []uint64 { // Short circruit for now on EOF ops with immediates. // TODO(matt): make EOF-specific code bitmap if op == RJUMP || op == RJUMPI || op == CALLF { - // TODO CZ: verify that this is correct - bits[pc/64] |= 1 << uint(pc%64) + // TODO(CZ): optimise this. For now the logic of bitvec.setN has been copied from https://github.dev/lightclient/go-ethereum/blob/b3f36e10766956bf204f2d2b9415dcd8cfd2be6b/core/vm/analysis.go#L37 + a := uint16(0b1111) << (pc % 8) + bits[pc/8] |= uint64(a) + if b := uint64(a >> 8); b != 0 { + bits[pc/8+1] = b + } + pc += 4 continue } From 513d63f1ee334e93d6235c92828f0b4e6c479f87 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Thu, 1 Dec 2022 12:59:33 +0200 Subject: [PATCH 33/49] =?UTF-8?q?core/vm:=20refix=20codeBitmap=20based=20o?= =?UTF-8?q?n=20holiman=E2=80=99s=20suggestion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/vm/analysis.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 83a341bab2d..e4d94f88583 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -29,14 +29,15 @@ func codeBitmap(code []byte) []uint64 { // Short circruit for now on EOF ops with immediates. // TODO(matt): make EOF-specific code bitmap if op == RJUMP || op == RJUMPI || op == CALLF { - // TODO(CZ): optimise this. For now the logic of bitvec.setN has been copied from https://github.dev/lightclient/go-ethereum/blob/b3f36e10766956bf204f2d2b9415dcd8cfd2be6b/core/vm/analysis.go#L37 - a := uint16(0b1111) << (pc % 8) - bits[pc/8] |= uint64(a) - if b := uint64(a >> 8); b != 0 { - bits[pc/8+1] = b - } + // TODO(CZ): For now the logic of bitvec.setN has been copied + // and modified to work with a uint64 slice instead of a bitvec. + // https://github.dev/lightclient/go-ethereum/blob/b3f36e10766956bf204f2d2b9415dcd8cfd2be6b/core/vm/analysis.go#L37 + + // Also applied @holiman suggestion in order to make legacy code tests work. + // https://github.com/ethereum/go-ethereum/pull/26133/files#r1026320917 + bits[pc/64] |= 1 << (uint64(pc) & 63) + pc += 2 - pc += 4 continue } if op >= PUSH1 && op <= PUSH32 { From e0de7760619fa88a673b1fdb37230f2de9223dd2 Mon Sep 17 00:00:00 2001 From: meows Date: Thu, 1 Dec 2022 06:11:09 -0800 Subject: [PATCH 34/49] core/vm: rename eof1 -> eof1Only for consistency with legacyOnly Date: 2022-12-01 06:11:09-08:00 Signed-off-by: meows --- core/vm/eips.go | 8 ++++---- core/vm/interpreter.go | 2 +- core/vm/jump_table.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index b167ac2bbde..5ec2a9f5d5a 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -217,14 +217,14 @@ func enable4200(jt *JumpTable) { constantGas: GasFastStep, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - eof1: true, + eof1Only: true, } jt[RJUMPI] = &operation{ execute: opRjumpi, constantGas: GasFastishStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - eof1: true, + eof1Only: true, } } @@ -270,14 +270,14 @@ func enable4750(jt *JumpTable) { constantGas: GasMidStep, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - eof1: true, + eof1Only: true, } jt[RETF] = &operation{ execute: opRetf, constantGas: GasMidStep, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - eof1: true, + eof1Only: true, } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 3e105e719cd..a0c2246aa6b 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -379,7 +379,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged = true } // fail if op is not available in legacy context - if operation.eof1 && callContext.Contract.IsLegacy() { + if operation.eof1Only && callContext.Contract.IsLegacy() { _, err = opUndefined(&pc, in, callContext) break } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 249bd5516a9..98a255ac876 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -52,8 +52,8 @@ type operation struct { undefined bool - // eof1 specifies if an instruction should only be available in eof1 - eof1 bool + // eof1Only specifies if an instruction should only be available in eof1Only + eof1Only bool // legacyOnly specifies if an instruction should only be available in legacy evm legacyOnly bool From ed99df442fffa33e9224e9bc2454578466986d4e Mon Sep 17 00:00:00 2001 From: meows Date: Thu, 1 Dec 2022 06:50:22 -0800 Subject: [PATCH 35/49] core/vm: use ethereum/go-ethereum conditional logic for push constructor This (re)uses the exact same logic as ethereum/go-ethereum at b3f36e10766956bf204f2d2b9415dcd8cfd2be6b. Rel https://github.com/ETCCooperative/erigon/pull/4#pullrequestreview-1199734442 The latest 'blame' on this stanza shows that the latest change resulted from the merge commit, so I assume that logic in here is not intentionally custom or reactionary to the upstream implementation. Date: 2022-12-01 06:50:22-08:00 Signed-off-by: meows --- core/vm/instructions.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 0abb67ff0ad..34f8b52f07d 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -753,7 +753,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - //TODO: use uint256.Int instead of converting with toBig() + // TODO: use uint256.Int instead of converting with toBig() if !value.IsZero() { gas += params.CallStipend } @@ -925,15 +925,16 @@ func makePush(size uint64, pushByteSize uint64) executionFunc { var ( context = scope.RetStack[len(scope.RetStack)-1] codeEnd = context.CodeOffset + context.CodeLength + startMin = codeEnd pcAbsolute = context.CodeOffset + *pc - startMin = pcAbsolute + 1 ) - if startMin >= codeEnd { - startMin = codeEnd + if pcAbsolute+1 < startMin { + startMin = pcAbsolute + 1 } - endMin := startMin + pushByteSize - if endMin >= codeEnd { - endMin = codeEnd + + endMin := codeEnd + if startMin+pushByteSize < endMin { + endMin = startMin + pushByteSize } integer := new(uint256.Int) From fc1d1391efa2728c007ecb0e13bf534ff252b716 Mon Sep 17 00:00:00 2001 From: meows Date: Thu, 1 Dec 2022 07:36:37 -0800 Subject: [PATCH 36/49] core/vm: add sanity test for opcode::string maps Date: 2022-12-01 07:36:37-08:00 Signed-off-by: meows --- core/vm/opcodes.go | 5 +++-- core/vm/opcodes_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 core/vm/opcodes_test.go diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index d82d3ed8072..9da6d5b3b39 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -288,8 +288,8 @@ var opCodeToString = map[OpCode]string{ // 0x50 range - 'storage' and execution. POP: "POP", - //DUP: "DUP", - //SWAP: "SWAP", + // DUP: "DUP", + // SWAP: "SWAP", MLOAD: "MLOAD", MSTORE: "MSTORE", MSTORE8: "MSTORE8", @@ -546,6 +546,7 @@ var stringToOp = map[string]OpCode{ } // StringToOp finds the opcode whose name is stored in `str`. +// It panics if the string is not associated with any opcode. func StringToOp(str string) OpCode { return stringToOp[str] } diff --git a/core/vm/opcodes_test.go b/core/vm/opcodes_test.go new file mode 100644 index 00000000000..372f0b10414 --- /dev/null +++ b/core/vm/opcodes_test.go @@ -0,0 +1,15 @@ +package vm + +import ( + "testing" +) + +// TestOpcodeStringer is a sanity check to test the internal consistency of our opcode:string mappings. +func TestOpcodeStringer(t *testing.T) { + for op, str := range opCodeToString { + // StringToOp will panic if the string is not valid. + if gotOp := StringToOp(str); gotOp != op { + t.Errorf("StringToOp[%q] = %v, want %v", str, gotOp, op) + } + } +} From d2a1f6889945ab4c40575be98604fb5ef01e4f18 Mon Sep 17 00:00:00 2001 From: meows Date: Thu, 1 Dec 2022 07:49:08 -0800 Subject: [PATCH 37/49] core/vm: remove unused and commented DUP,SWAP string assignments Date: 2022-12-01 07:49:08-08:00 Signed-off-by: meows --- core/vm/opcodes.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 9da6d5b3b39..5fa4f455607 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -287,9 +287,7 @@ var opCodeToString = map[OpCode]string{ BASEFEE: "BASEFEE", // 0x50 range - 'storage' and execution. - POP: "POP", - // DUP: "DUP", - // SWAP: "SWAP", + POP: "POP", MLOAD: "MLOAD", MSTORE: "MSTORE", MSTORE8: "MSTORE8", From e24886d4c7a454ec0eb065709acb52512a713b47 Mon Sep 17 00:00:00 2001 From: meows Date: Thu, 1 Dec 2022 10:03:34 -0800 Subject: [PATCH 38/49] core/vm: remove commented operation fields .reverts and .halts Date: 2022-12-01 10:03:34-08:00 Signed-off-by: meows --- core/vm/absint_cfg_proof_check.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/vm/absint_cfg_proof_check.go b/core/vm/absint_cfg_proof_check.go index 58663a1c34e..e9e9d7f1959 100644 --- a/core/vm/absint_cfg_proof_check.go +++ b/core/vm/absint_cfg_proof_check.go @@ -9,8 +9,6 @@ import ( ) type CfgOpSem struct { - // reverts bool - // halts bool isPush bool isDup bool isSwap bool @@ -32,8 +30,6 @@ func NewCfgAbsSem() *CfgAbsSem { continue } opsem := CfgOpSem{} - // opsem.reverts = op.reverts - // opsem.halts = op.halts opsem.isPush = op.isPush opsem.isDup = op.isDup opsem.isSwap = op.isSwap @@ -98,7 +94,7 @@ func resolveCheck(sem *CfgAbsSem, code []byte, st0 *astate, pc0 int) (map[int]bo if stack.hasIndices(0) { jumpDest := stack.values[0] if jumpDest.kind == InvalidValue { - //program terminates, don't add edges + // program terminates, don't add edges } else if jumpDest.kind == TopValue { empty := make(map[int]bool) return empty, empty, errors.New("unresolvable jumps found") @@ -113,7 +109,7 @@ func resolveCheck(sem *CfgAbsSem, code []byte, st0 *astate, pc0 int) (map[int]bo } } - //fall-thru edge + // fall-thru edge if opcode != JUMP { if pc0 < codeLen-opsem.numBytes { succs[pc0+opsem.numBytes] = true From 2f4a71742724df4853d67a60a5b767fc17d39733 Mon Sep 17 00:00:00 2001 From: meows Date: Sat, 3 Dec 2022 07:48:54 -0800 Subject: [PATCH 39/49] Revert "core/vm: use ethereum/go-ethereum conditional logic for push constructor" This reverts commit ed99df442fffa33e9224e9bc2454578466986d4e. --- core/vm/instructions.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 34f8b52f07d..0abb67ff0ad 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -753,7 +753,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - // TODO: use uint256.Int instead of converting with toBig() + //TODO: use uint256.Int instead of converting with toBig() if !value.IsZero() { gas += params.CallStipend } @@ -925,16 +925,15 @@ func makePush(size uint64, pushByteSize uint64) executionFunc { var ( context = scope.RetStack[len(scope.RetStack)-1] codeEnd = context.CodeOffset + context.CodeLength - startMin = codeEnd pcAbsolute = context.CodeOffset + *pc + startMin = pcAbsolute + 1 ) - if pcAbsolute+1 < startMin { - startMin = pcAbsolute + 1 + if startMin >= codeEnd { + startMin = codeEnd } - - endMin := codeEnd - if startMin+pushByteSize < endMin { - endMin = startMin + pushByteSize + endMin := startMin + pushByteSize + if endMin >= codeEnd { + endMin = codeEnd } integer := new(uint256.Int) From 90f208988187909cbd4aaa98f0b44921da74b11f Mon Sep 17 00:00:00 2001 From: meows Date: Mon, 5 Dec 2022 09:48:23 -0800 Subject: [PATCH 40/49] core/vm: use correct pc var Date: 2022-12-05 09:48:23-08:00 Signed-off-by: meows --- core/vm/interpreter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 84a0b549073..e7671d66e4b 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -381,16 +381,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } // fail if op is not available in legacy context if operation.eof1Only && callContext.Contract.IsLegacy() { - _, err = opUndefined(&pc, in, callContext) + _, err = opUndefined(pc, in, callContext) break } // fail if op is not available in eof context if operation.legacyOnly && !callContext.Contract.IsLegacy() { - _, err = opUndefined(&pc, in, callContext) + _, err = opUndefined(pc, in, callContext) break } // execute the operation - res, err = operation.execute(&pc, in, callContext) + res, err = operation.execute(pc, in, callContext) if err != nil { break From 4fca79cc53c42b4082598ca344380d23fb9c90f9 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Tue, 6 Dec 2022 11:57:41 +0200 Subject: [PATCH 41/49] =?UTF-8?q?core/vm:=20switch=20dep=20of=20=E2=80=9Cg?= =?UTF-8?q?ithub.com/ethereum/go-ethereum/common=E2=80=9D=20to=20=E2=80=9C?= =?UTF-8?q?github.com/ledgerwatch/erigon/common=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/vm/eof_test.go | 2 +- go.mod | 2 +- go.sum | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 9952ed60c0e..23cbe3d2264 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -22,7 +22,7 @@ import ( "strings" "testing" - "github.com/ethereum/go-ethereum/common" + "github.com/ledgerwatch/erigon/common" ) func TestHasEOFMagic(t *testing.T) { diff --git a/go.mod b/go.mod index 61959c27732..893099fb8d7 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/edsrzf/mmap-go v1.1.0 github.com/emicklei/dot v1.0.0 github.com/emirpasic/gods v1.18.1 - github.com/ethereum/go-ethereum v1.10.26 github.com/ferranbt/fastssz v0.1.2 github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c github.com/gballet/go-verkle v0.0.0-20221121182333-31427a1f2d35 @@ -114,6 +113,7 @@ require ( github.com/klauspost/compress v1.15.10 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/ledgerwatch/trackerslist v1.0.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect diff --git a/go.sum b/go.sum index cbe0b24b0cb..5e037ec6b09 100644 --- a/go.sum +++ b/go.sum @@ -274,8 +274,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= @@ -561,6 +559,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/ledgerwatch/erigon-lib v0.0.0-20221203234336-784cd740b9a7 h1:FB/oKLRCp2Azi9IFUhKpngF4awK8e+irVGfkEcj9j9Y= From e30257c8811a5eb5ac8c4a3101e1b2f727e7717c Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Fri, 9 Dec 2022 09:03:30 +0200 Subject: [PATCH 42/49] core/vm: remove superfluous code --- core/vm/contract.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index a8477372d05..f2e0664f7ec 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -115,10 +115,6 @@ func (c *Contract) isCode(udest uint64) bool { // TODO remove this panic("shouldn't run jumpdest analysis on EOF") } - // Do we already have an analysis laying around? - if c.analysis != nil { - return c.analysis[udest/64]&(uint64(1)<<(udest&63)) == 0 - } // Do we have a contract hash already? // If we do have a hash, that means it's a 'regular' contract. For regular // contracts ( not temporary initcode), we store the analysis in a map From a457a348e9d0f452a6026a296dfac6c81809aef3 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 00:01:41 +0200 Subject: [PATCH 43/49] tests: fix tests by /s/ShanghaiBlock/ShanghaiTime --- tests/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/init.go b/tests/init.go index 7b3caf5ab76..55a3fdb9b65 100644 --- a/tests/init.go +++ b/tests/init.go @@ -240,7 +240,7 @@ var Forks = map[string]*params.ChainConfig{ BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0), - ShanghaiBlock: big.NewInt(0), + ShanghaiTime: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0xC0000), }, } From 581f18ffb84918667677e8ae1717aaa874ab1325 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 13:00:58 +0200 Subject: [PATCH 44/49] tests: keep ArrowGlacierToMergeAtDiffC0000 as a fork in tests --- tests/init.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/init.go b/tests/init.go index 55a3fdb9b65..92ff9bb63a2 100644 --- a/tests/init.go +++ b/tests/init.go @@ -227,6 +227,21 @@ var Forks = map[string]*params.ChainConfig{ MergeNetsplitBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), }, + "ArrowGlacierToMergeAtDiffC0000": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + TangerineWhistleBlock: big.NewInt(0), + SpuriousDragonBlock: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0xC0000), + }, "Shanghai": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), @@ -240,8 +255,10 @@ var Forks = map[string]*params.ChainConfig{ BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), ShanghaiTime: big.NewInt(0), - TerminalTotalDifficulty: big.NewInt(0xC0000), + TerminalTotalDifficulty: big.NewInt(0), }, } From 4e57c8c6208f2dd93f0e597e33783b145851994e Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 13:03:43 +0200 Subject: [PATCH 45/49] tests: revert head for testdata to 9e058e5 --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index c76c5d274e2..9e058e565e6 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit c76c5d274e271e039b0b11e9372f0c1143dd6c30 +Subproject commit 9e058e565e664ae2ba93f9a25c4beb105df07480 From c4708a9f228fcde98a9c20299093ebc0043f80b7 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 18:20:14 +0200 Subject: [PATCH 46/49] Update core/vm/eips.go Co-authored-by: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> --- core/vm/eips.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 5ec2a9f5d5a..8012a620c99 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -253,7 +253,7 @@ func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // opRjumpi implements the RJUMPI opcode func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { condition := scope.Stack.Pop() - if condition.BitLen() == 0 { + if condition.IsZero() { // Not branching, just skip over immediate argument. *pc += 2 return nil, nil From 6fbc90f51c33e283ada243f847e7f52870a31db5 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 18:51:29 +0200 Subject: [PATCH 47/49] core/vm: mark `CALLCODE` & `SELFDESTRUCT` as legacyOnly based on EIP3670 --- core/vm/eips.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 8012a620c99..1633135e3d7 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -207,7 +207,8 @@ func enable3540(jt *JumpTable) { } func enable3670(jt *JumpTable) { - // Do nothing. + jt[CALLCODE].legacyOnly = true + jt[SELFDESTRUCT].legacyOnly = true } // enable4200 applies EIP-4200 (RJUMP and RJUMPI opcodes) From f4c02bab7f15a134c679b58c01820150af51972b Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 19:08:48 +0200 Subject: [PATCH 48/49] common/compiler: fix vyper test --- common/compiler/test.v.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/compiler/test.v.py b/common/compiler/test.v.py index 35af56c8f6e..0a9b8db1108 100644 --- a/common/compiler/test.v.py +++ b/common/compiler/test.v.py @@ -1,3 +1,3 @@ -@public +@external def test(): - hello: int128 + hello: int128 = 1 From 6171a3ccd231a74ff50c4d09883e1180691c5bc4 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Mon, 12 Dec 2022 21:01:13 +0200 Subject: [PATCH 49/49] core/vm: remove superfluous check --- core/vm/evm.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index d31403fbbb7..e377d8da909 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -338,10 +338,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrContractAddressCollision return nil, common.Address{}, 0, err } - // Check whether the init code size has been exceeded. - if evm.config.HasEip3860(evm.chainRules) && len(codeAndHash.code) > params.MaxInitCodeSize { - return nil, address, gas, ErrMaxInitCodeSizeExceeded - } // Try to read code header if it claims to be EOF-formatted. container, err := evm.parseContainer(codeAndHash.code) if err != nil {