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