diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 4aa8cfe70f11..9abd4ccb915a 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -61,20 +61,21 @@ func (bits *bitvec) codeSegment(pos uint64) bool { } // codeBitmap collects data locations in code. -func codeBitmap(code []byte) bitvec { +func codeBitmap(c *Contract) bitvec { // The bitmap is 4 bytes longer than necessary, in case the code // ends with a PUSH32, the algorithm will push zeroes onto the // bitvector outside the bounds of the actual code. - bits := make(bitvec, len(code)/8+1+4) - return codeBitmapInternal(code, bits) + bits := make(bitvec, c.CodeSize/8+1+4) + return codeBitmapInternal(c, bits) } // codeBitmapInternal is the internal implementation of codeBitmap. // It exists for the purpose of being able to run benchmark tests // without dynamic allocations affecting the results. -func codeBitmapInternal(code, bits bitvec) bitvec { - for pc := uint64(0); pc < uint64(len(code)); { - op := OpCode(code[pc]) +func codeBitmapInternal(c *Contract, bits bitvec) bitvec { + codeBegin := c.CodeBeginOffset + for pc := uint64(0); pc < c.CodeSize; { + op := OpCode(c.Container[codeBegin+pc]) pc++ if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false). continue diff --git a/core/vm/analysis_test.go b/core/vm/analysis_test.go index 398861f8ae7d..547f5ca296e4 100644 --- a/core/vm/analysis_test.go +++ b/core/vm/analysis_test.go @@ -20,6 +20,7 @@ import ( "math/bits" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -52,7 +53,15 @@ func TestJumpDestAnalysis(t *testing.T) { {[]byte{byte(PUSH32)}, 0b0000_0001, 4}, } for i, test := range tests { - ret := codeBitmap(test.code) + var ( + contract Contract + addr common.Address + hash common.Hash + header EOF1Header + ) + contract.SetCallCode(&addr, hash, test.code, &header) + + ret := codeBitmap(&contract) if ret[test.which] != test.exp { t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which]) } @@ -64,10 +73,17 @@ const analysisCodeSize = 1200 * 1024 func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) { // 1.4 ms code := make([]byte, analysisCodeSize) - bench.SetBytes(analysisCodeSize) + var ( + contract Contract + addr common.Address + hash common.Hash + header EOF1Header + ) + contract.SetCallCode(&addr, hash, code, &header) bench.ResetTimer() + bench.SetBytes(analysisCodeSize) for i := 0; i < bench.N; i++ { - codeBitmap(code) + codeBitmap(&contract) } bench.StopTimer() } @@ -83,7 +99,13 @@ func BenchmarkJumpdestHashing_1200k(bench *testing.B) { } func BenchmarkJumpdestOpAnalysis(bench *testing.B) { - var op OpCode + var ( + contract Contract + addr common.Address + hash common.Hash + header EOF1Header + op OpCode + ) bencher := func(b *testing.B) { code := make([]byte, analysisCodeSize) b.SetBytes(analysisCodeSize) @@ -91,12 +113,13 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) { code[i] = byte(op) } bits := make(bitvec, len(code)/8+1+4) + contract.SetCallCode(&addr, hash, code, &header) b.ResetTimer() for i := 0; i < b.N; i++ { for j := range bits { bits[j] = 0 } - codeBitmapInternal(code, bits) + codeBitmapInternal(&contract, bits) } } for op = PUSH1; op <= PUSH32; op++ { diff --git a/core/vm/contract.go b/core/vm/contract.go index bb0902969ec7..de5a3d8d357c 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -50,13 +50,15 @@ type Contract struct { caller ContractRef self ContractRef + CodeBeginOffset uint64 // starting offset of the code section if EOF or 0 if legacy + CodeSize uint64 // size of the code section if EOF or bytecode size if legacy + jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. analysis bitvec // Locally cached result of JUMPDEST analysis - Code []byte - CodeHash common.Hash - CodeAddr *common.Address - Input []byte + Container []byte + ContainerHash common.Hash + Input []byte Gas uint64 value *big.Int @@ -82,15 +84,17 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin return c } +// validJumpdest returns true if destination offset is inside code bounds and is JUMPDEST opcode +// dest is offset inside code section in case of EOF contract func (c *Contract) validJumpdest(dest *uint256.Int) bool { udest, overflow := dest.Uint64WithOverflow() // PC cannot go beyond len(code) and certainly can't be bigger than 63bits. // Don't bother checking for JUMPDEST in that case. - if overflow || udest >= uint64(len(c.Code)) { + if overflow || udest >= c.CodeSize { return false } // Only JUMPDESTs allowed for destinations - if OpCode(c.Code[udest]) != JUMPDEST { + if c.GetOp(udest) != JUMPDEST { return false } return c.isCode(udest) @@ -106,14 +110,14 @@ func (c *Contract) isCode(udest uint64) bool { // 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 - if c.CodeHash != (common.Hash{}) { + if c.ContainerHash != (common.Hash{}) { // Does parent context have the analysis? - analysis, exist := c.jumpdests[c.CodeHash] + analysis, exist := c.jumpdests[c.ContainerHash] if !exist { // Do the analysis and save in parent context // We do not need to store it in c.analysis - analysis = codeBitmap(c.Code) - c.jumpdests[c.CodeHash] = analysis + analysis = codeBitmap(c) + c.jumpdests[c.ContainerHash] = analysis } // Also stash it in current contract for faster access c.analysis = analysis @@ -124,7 +128,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 { - c.analysis = codeBitmap(c.Code) + c.analysis = codeBitmap(c) } return c.analysis.codeSegment(udest) } @@ -142,9 +146,10 @@ func (c *Contract) AsDelegate() *Contract { } // GetOp returns the n'th element in the contract's byte array +// n is offset inside code section in case of EOF contract func (c *Contract) GetOp(n uint64) OpCode { - if n < uint64(len(c.Code)) { - return OpCode(c.Code[n]) + if n < c.CodeSize { + return OpCode(c.Container[c.CodeBeginOffset+n]) } return STOP @@ -177,18 +182,35 @@ func (c *Contract) Value() *big.Int { return c.value } +// getCodeBounds returns beginning offset and size of the code section if EOF +// or 0 and bytecode length if legacy +func getCodeBounds(container []byte, header *EOF1Header) (begin uint64, size uint64) { + // EOF1 doesn't allow contracts without code section + isLegacy := (header.codeSize == 0) + if isLegacy { + begin = 0 + size = uint64(len(container)) + } else { + begin = header.CodeBeginOffset() + size = uint64(header.codeSize) + } + return +} + // 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) { - c.Code = code - c.CodeHash = hash - c.CodeAddr = addr +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, container []byte, header *EOF1Header) { + c.Container = container + c.ContainerHash = hash + + c.CodeBeginOffset, c.CodeSize = getCodeBounds(container, header) } // 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) { - c.Code = codeAndHash.code - c.CodeHash = codeAndHash.hash - c.CodeAddr = addr +func (c *Contract) SetCodeOptionalHash(addr *common.Address, containerAndHash *codeAndHash, header *EOF1Header) { + c.Container = containerAndHash.code + c.ContainerHash = containerAndHash.hash + + c.CodeBeginOffset, c.CodeSize = getCodeBounds(containerAndHash.code, header) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 93f5c399a668..80b9eb01d716 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -17,6 +17,7 @@ package vm import ( + "encoding/binary" "fmt" "sort" @@ -32,6 +33,9 @@ var activators = map[int]func(*JumpTable){ 2200: enable2200, 1884: enable1884, 1344: enable1344, + 3540: enable3540, + 3670: enable3670, + 4200: enable4200, } // EnableEIP enables the given EIP on the config. @@ -192,3 +196,66 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by scope.Stack.push(new(uint256.Int)) return nil, nil } + +func enable3540(jt *JumpTable) { + // Do nothing. +} + +func enable3670(jt *JumpTable) { + // Do nothing. +} + +func enable4200(jt *JumpTable) { + // New opcodes + jt[RJUMP] = &operation{ + execute: opRjump, + constantGas: params.RjumpGas, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + + jt[RJUMPI] = &operation{ + execute: opRjumpi, + constantGas: params.RjumpiGas, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } +} + +func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + immediateSize := 2 + codeEnd := int(len(scope.Contract.Container)) + start := codeEnd + pcAbsolute := scope.Contract.CodeBeginOffset + *pc + if int(pcAbsolute+1) < start { + start = int(pcAbsolute + 1) + } + + end := start + immediateSize + + if end >= codeEnd { + return nil, ErrEOF1TerminatingInstructionMissing + } + + *pc = *pc + uint64(immediateSize) + bytes := scope.Contract.Container[start:end] + pos := int16(binary.BigEndian.Uint16(bytes)) + + if pos < 0 { + *pc = *pc - uint64(pos*-1) + } else { + *pc = *pc + uint64(pos) + } + return nil, nil +} + +func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + cond := scope.Stack.pop() + if !cond.IsZero() { + return opRjump(pc, interpreter, scope) + } else { + *pc += 2 + } + + return nil, nil +} diff --git a/core/vm/eof.go b/core/vm/eof.go new file mode 100644 index 000000000000..467cb602fcb6 --- /dev/null +++ b/core/vm/eof.go @@ -0,0 +1,219 @@ +// 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" +) + +const ( + eofFormatByte byte = 0xEF + eofMagicLen int = 2 + eof1Version byte = 1 + sectionKindTerminator byte = 0 + sectionKindCode byte = 1 + sectionKindData byte = 2 +) + +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. +} + +func getEofMagic() []byte { + return []byte{0xEF, 0x00} +} + +// hasFormatByte returns true if code starts with 0xEF byte +func hasFormatByte(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(code) >= eofMagicLen && bytes.Equal(getEofMagic(), code[0:eofMagicLen]) +} + +// readEOF1Header parses EOF1-formatted code header +func readEOF1Header(code []byte) (EOF1Header, error) { + codeLen := len(code) + + i := eofMagicLen + if i >= codeLen || code[i] != eof1Version { + return EOF1Header{}, ErrEOF1InvalidVersion + } + i += 1 + + var header EOF1Header +sectionLoop: + for i < codeLen { + sectionKind := code[i] + i += 1 + switch sectionKind { + case sectionKindTerminator: + break sectionLoop + case sectionKindCode: + // Only 1 code section is allowed. + if header.codeSize != 0 { + return EOF1Header{}, ErrEOF1MultipleCodeSections + } + // Code section size must be present. + if i+2 > codeLen { + return EOF1Header{}, ErrEOF1CodeSectionSizeMissing + } + header.codeSize = binary.BigEndian.Uint16(code[i : i+2]) + // Code section size must not be 0. + if header.codeSize == 0 { + return EOF1Header{}, ErrEOF1EmptyCodeSection + } + i += 2 + case sectionKindData: + // Data section is allowed only after code section. + if header.codeSize == 0 { + return EOF1Header{}, ErrEOF1DataSectionBeforeCodeSection + } + // Only 1 data section is allowed. + if header.dataSize != 0 { + return EOF1Header{}, ErrEOF1MultipleDataSections + } + // Data section size must be present. + if i+2 > codeLen { + return EOF1Header{}, ErrEOF1DataSectionSizeMissing + } + header.dataSize = binary.BigEndian.Uint16(code[i : i+2]) + // Data section size must not be 0. + if header.dataSize == 0 { + return EOF1Header{}, ErrEOF1EmptyDataSection + } + i += 2 + default: + return EOF1Header{}, ErrEOF1UnknownSection + } + } + // 1 code section is required. + if header.codeSize == 0 { + return EOF1Header{}, ErrEOF1CodeSectionMissing + } + // Declared section sizes must correspond to real size (trailing bytes are not allowed.) + if i+int(header.codeSize)+int(header.dataSize) != codeLen { + 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 { + i := header.CodeBeginOffset() + var opcode OpCode + jumpdests := map[uint64]bool{} + immediates := map[uint64]bool{} + + for i < header.CodeEndOffset() { + opcode = OpCode(code[i]) + if jumpTable[opcode].undefined { + return ErrEOF1UndefinedInstruction + } + if opcode >= PUSH1 && opcode <= PUSH32 { + pushSize := uint64(opcode) - uint64(PUSH1) + 1 + for j := i + 1; j <= i+pushSize; j++ { + immediates[j] = true + } + i += pushSize + } + + if opcode == RJUMP || opcode == RJUMPI { + i += 1 + if i+1 >= header.CodeEndOffset() { + break + } + + rjdBytes := code[i : i+2] + offset := int16(binary.BigEndian.Uint16(rjdBytes)) + + var rjumpdest uint64 + if offset < 0 { + rjumpdest = i + 2 - uint64(offset*-1) + } else { + rjumpdest = i + 2 + uint64(offset) + } + + if rjumpdest < header.CodeBeginOffset() || rjumpdest >= header.CodeEndOffset() { + return ErrEOF1RJumpDestinationOutOfBounds + } + + jumpdests[rjumpdest] = true + immediates[i] = true + immediates[i+1] = true + i += 1 + } + i += 1 + } + + for k := range jumpdests { + if immediates[k] { + return ErrEOF1RJumpDestTargetsImmediate + } + } + + 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 +func readValidEOF1Header(code []byte) EOF1Header { + var header EOF1Header + codeSizeOffset := 2 + eofMagicLen + 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 uint64(5 + eofMagicLen) + } + // len(magic) + version + code_section_id + code_section_size + data_section_id + data_section_size + terminator + return uint64(8 + eofMagicLen) +} + +// 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 000000000000..78cc71a37b88 --- /dev/null +++ b/core/vm/eof_test.go @@ -0,0 +1,362 @@ +// 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}, + {"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 + {"EF000101000E005C00056002600255600160015500", 14, 0}, // RJUMP + {"EF000101000F005C000560026002555B600160015500", 15, 0}, // RJUMP (to JUMPDEST) + {"EF00010100100060015D00056002600255600160015500", 16, 0}, // RJUMPI + {"EF00010100110060015D000560026002555B600160015500", 17, 0}, // RJUMPI (to JUMPDEST) +} + +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 +} + +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 truncated offset (final instruction in code section) + {"EF00010100060060016001555C", ErrEOF1TerminatingInstructionMissing.Error()}, + // RJUMP truncated offset + {"EF00010100070060016001555C00", ErrEOF1TerminatingInstructionMissing.Error()}, + // RJUMP to RJUMP's immediate + {"EF0001010011005C000660026002555C0000600160015500", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMP to PUSH's immediate + {"EF000101000A006400000000005CFFFC00", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMP to PUSH's immediate + {"EF000101000A006400000000005CFFFB00", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMP to RJUMPI's immediate + {"EF000101000D005C00086001600155005DFFF700", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMP target outside of code section bounds + {"EF0001010004020004005C000300AABBCCDD", ErrEOF1RJumpDestinationOutOfBounds.Error()}, + // RJUMPI truncated offset (final instruction in code section) + {"EF000101000800600160015560015d", ErrEOF1TerminatingInstructionMissing.Error()}, + // RJUMPI truncated offset + {"EF000101000900600160015560015d00", ErrEOF1TerminatingInstructionMissing.Error()}, + // RJUMPI to RJUMP's immediate + {"EF00010100130060015D000660026002555C0000600160015500", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMPI to PUSH's immediate + {"EF000101000C0064000000000060015DFFFA00", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMPI to PUSHs immediate + {"EF000101000C0064000000000060015CFFF900", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMPI to RJUMPI's immediate + {"EF000101000F0060015D00086001600155005DFFF700", ErrEOF1RJumpDestTargetsImmediate.Error()}, + // RJUMPI target outside of code section bounds + {"EF00010100060200040060015D000300AABBCCDD", ErrEOF1RJumpDestinationOutOfBounds.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) + } + } +} + +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) { + jt := &eip4200InstructionSet + for _, test := range eof1ValidTests { + _, err := validateEOF(common.Hex2Bytes(test.code), jt) + if err != nil { + t.Errorf("code %v expected to be valid", test.code) + } + } + + for _, test := range eof1InvalidTests { + _, err := validateEOF(common.Hex2Bytes(test.code), jt) + if err == nil { + 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) + } + } +} + +func TestValidateInstructions(t *testing.T) { + jt := &eip4200InstructionSet + 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 004f8ef1c83c..94a426adc6cb 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -34,14 +34,34 @@ var ( ErrWriteProtection = errors.New("write protection") ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrGasUintOverflow = errors.New("gas uint64 overflow") - ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrInvalidEOFCode = errors.New("invalid code: EOF 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") + ErrEOF1UndefinedInstruction = errors.New("undefined instruction") + ErrEOF1TerminatingInstructionMissing = errors.New("code section doesn't end with terminating instruction") + ErrEOF1RJumpDestinationOutOfBounds = errors.New("relative jump destination out of bounds") + ErrEOF1RJumpDestTargetsImmediate = errors.New("relative jump destination targets immediate") +) + // 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 888f4812a590..551f1aab2de0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -221,10 +221,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ret, err = nil, nil // gas is unchanged } else { addrCopy := addr + + var header EOF1Header + if hasEIP3540And3670(&evm.Config) && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -278,10 +284,18 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr + + code := evm.StateDB.GetCode(addrCopy) + + var header EOF1Header + if hasEIP3540And3670(&evm.Config) && 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. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -319,9 +333,17 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr + + code := evm.StateDB.GetCode(addrCopy) + + var header EOF1Header + if hasEIP3540And3670(&evm.Config) && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -371,10 +393,18 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. addrCopy := addr + + code := evm.StateDB.GetCode(addrCopy) + + var header EOF1Header + if hasEIP3540And3670(&evm.Config) && 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. contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. @@ -402,6 +432,19 @@ func (c *codeAndHash) Hash() common.Hash { return c.hash } +func hasEIP3540And3670(vmConfig *Config) bool { + eip3540 := false + eip3670 := false + for _, eip := range vmConfig.ExtraEips { + if eip == 3540 { + eip3540 = true + } else if eip == 3670 { + eip3670 = true + } + } + return eip3540 && eip3670 +} + // create creates a new contract using code as deployment code. func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { // Depth check execution. Fail if we're trying to execute above the @@ -427,6 +470,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } + // Try to read code header if it claims to be EOF-formatted. + var header EOF1Header + if hasEIP3540And3670(&evm.Config) && hasEOFMagic(codeAndHash.code) { + var err error + header, err = validateEOF(codeAndHash.code, evm.interpreter.cfg.JumpTable) + if err != nil { + return nil, common.Address{}, gas, ErrInvalidEOFCode + } + } // Create a new account on the state snapshot := evm.StateDB.Snapshot() evm.StateDB.CreateAccount(address) @@ -438,7 +490,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) - contract.SetCodeOptionalHash(&address, codeAndHash) + contract.SetCodeOptionalHash(&address, codeAndHash, &header) if evm.Config.Debug { if evm.depth == 0 { @@ -457,9 +509,22 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrMaxCodeSizeExceeded } - // Reject code starting with 0xEF if EIP-3541 is enabled. - if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { - err = ErrInvalidCode + if err == nil && hasFormatByte(ret) { + if hasEIP3540And3670(&evm.Config) { + // Allow only valid EOF1 if EIP-3540 and EIP-3670 are enabled. + if hasEOFMagic(ret) { + _, err = validateEOF(ret, evm.interpreter.cfg.JumpTable) + if err != nil { + err = ErrInvalidEOFCode + } + } 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 diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 22d459233b3d..3de9d51dbc4f 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -349,22 +349,22 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { l := new(uint256.Int) - l.SetUint64(uint64(len(scope.Contract.Code))) + l.SetUint64(uint64(len(scope.Contract.Container))) scope.Stack.push(l) return nil, nil } func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = scope.Stack.pop() - codeOffset = scope.Stack.pop() - length = scope.Stack.pop() + memOffset = scope.Stack.pop() + containerOffset = scope.Stack.pop() + length = scope.Stack.pop() ) - uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + uint64ContainerOffset, overflow := containerOffset.Uint64WithOverflow() if overflow { - uint64CodeOffset = 0xffffffffffffffff + uint64ContainerOffset = 0xffffffffffffffff } - codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + codeCopy := getData(scope.Contract.Container, uint64ContainerOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil @@ -812,13 +812,17 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b } func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])} + return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Container[*pc])} } func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { return nil, errStopToken } +func opInvalid(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, &ErrInvalidOpCode{opcode: INVALID} +} + func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { return nil, ErrWriteProtection @@ -867,12 +871,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.CodeBeginOffset + scope.Contract.CodeSize integer = new(uint256.Int) ) *pc += 1 - if *pc < codeLen { - scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + dataPos := scope.Contract.CodeBeginOffset + *pc + if dataPos < codeEnd { + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Container[dataPos]))) } else { scope.Stack.push(integer.Clear()) } @@ -882,21 +887,22 @@ 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.CodeBeginOffset + scope.Contract.CodeSize) - startMin := codeLen - if int(*pc+1) < startMin { - startMin = int(*pc + 1) + startMin := codeEnd + pcAbsolute := scope.Contract.CodeBeginOffset + *pc + if int(pcAbsolute+1) < startMin { + startMin = int(pcAbsolute + 1) } - endMin := codeLen + endMin := codeEnd if startMin+pushByteSize < endMin { endMin = startMin + pushByteSize } integer := new(uint256.Int) scope.Stack.push(integer.SetBytes(common.RightPadBytes( - scope.Contract.Code[startMin:endMin], pushByteSize))) + scope.Contract.Container[startMin:endMin], pushByteSize))) *pc += size return nil, nil diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 312977b75588..0faefe16110a 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -124,7 +124,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( in.returnData = nil // Don't bother with the execution if there's no code. - if len(contract.Code) == 0 { + if len(contract.Container) == 0 { return nil, nil } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 94229436d23c..6bf00e9b2ae5 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -42,6 +42,8 @@ type operation struct { // memorySize returns the memory size required for the operation memorySize memorySizeFunc + + undefined bool } var ( @@ -55,6 +57,7 @@ var ( berlinInstructionSet = newBerlinInstructionSet() londonInstructionSet = newLondonInstructionSet() mergeInstructionSet = newMergeInstructionSet() + eip4200InstructionSet = newEIP4200InstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -78,6 +81,12 @@ func validate(jt JumpTable) JumpTable { return jt } +func newEIP4200InstructionSet() JumpTable { + instructionSet := newMergeInstructionSet() + enable4200(&instructionSet) + return validate(instructionSet) +} + func newMergeInstructionSet() JumpTable { instructionSet := newLondonInstructionSet() instructionSet[PREVRANDAO] = &operation{ @@ -1026,6 +1035,12 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(2, 0), memorySize: memoryReturn, }, + INVALID: { + execute: opInvalid, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + }, SELFDESTRUCT: { execute: opSelfdestruct, dynamicGas: gasSelfdestruct, @@ -1037,7 +1052,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 fa7de5049ace..339cf88ae764 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -116,6 +116,8 @@ const ( MSIZE OpCode = 0x59 GAS OpCode = 0x5a JUMPDEST OpCode = 0x5b + RJUMP OpCode = 0x5c + RJUMPI OpCode = 0x5d PUSH0 OpCode = 0x5f ) @@ -298,6 +300,8 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", + RJUMP: "RJUMP", + RJUMPI: "RJUMPI", PUSH0: "PUSH0", // 0x60 range - push. @@ -462,6 +466,8 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, + "RJUMP": RJUMP, + "RJUMPI": RJUMPI, "PUSH0": PUSH0, "PUSH1": PUSH1, "PUSH2": PUSH2, @@ -546,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 +} diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 6a916f55b2a7..9e07f33d9a5d 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -68,10 +68,11 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon value = big.NewInt(0) contract = vm.NewContract(account{}, account{}, value, startGas) ) - contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} + contract.Container = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} if contractCode != nil { - contract.Code = contractCode + contract.Container = contractCode } + contract.CodeSize = uint64(len(contract.Container)) tracer.CaptureTxStart(gasLimit) tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 1bc7456d31c6..e7b9cd26f264 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -58,7 +58,8 @@ func TestStoreCapture(t *testing.T) { env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: logger}) contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000) ) - contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} + contract.Container = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} + contract.CodeSize = uint64(len(contract.Container)) var index common.Hash logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil) _, err := env.Interpreter().Run(contract, []byte{}, false) diff --git a/params/protocol_params.go b/params/protocol_params.go index b0037fd471c4..9f4c2d8b29f2 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -104,6 +104,8 @@ const ( ExtcodeHashGasConstantinople uint64 = 400 // Cost of EXTCODEHASH (introduced in Constantinople) ExtcodeHashGasEIP1884 uint64 = 700 // Cost of EXTCODEHASH after EIP 1884 (part in Istanbul) SelfdestructGasEIP150 uint64 = 5000 // Cost of SELFDESTRUCT post EIP 150 (Tangerine) + RjumpGas uint64 = 5 // Cost of RJUMP defined in EIP-4200 + RjumpiGas uint64 = 7 // Cost of RJUMPI defined in EIP-4200 // EXP has a dynamic portion depending on the size of the exponent ExpByteFrontier uint64 = 10 // was set to 10 in Frontier