Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-4200 #2

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions core/vm/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 28 additions & 5 deletions core/vm/analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"math/bits"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)

Expand Down Expand Up @@ -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])
}
Expand All @@ -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()
}
Expand All @@ -83,20 +99,27 @@ 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)
for i := range code {
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++ {
Expand Down
64 changes: 43 additions & 21 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
67 changes: 67 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package vm

import (
"encoding/binary"
"fmt"
"sort"

Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Loading