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

core/vm: implement full EOF suite [WiP] #6215

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
83fddab
core/vm: Make INVALID a defined opcode (#24017)
gumb0 Dec 17, 2021
3a6ea15
core/vm: fill gaps in jump table with opUndefined (#24031)
chfast Dec 3, 2021
4b34b5d
core/vm: simplify error handling in interpreter loop (#23952)
chfast Nov 29, 2021
c5a9122
cmd/hack: temp fix build by breaking interpreter loop [WiP]
ziogaschr Nov 29, 2022
80c6fe2
core/vm: implement EOF EIP-3540: EOF v1
gumb0 May 11, 2021
ea50433
core/vm: implement EOF EIP-3670: Code Validation
gumb0 Dec 10, 2021
b5e6fab
core/vm: handle multiple interpreters on evm EOF invocations
ziogaschr Nov 29, 2022
8bcab93
core/vm: implement EOF EIP-4200: Static relative jumps
lightclient Nov 5, 2022
70dc126
core/vm: implement EOF EIP-4750: Functions
lightclient Nov 8, 2022
7812105
core/vm: fix merge issues
ziogaschr Nov 30, 2022
c280427
core/vm: rework eof tests
lightclient Nov 9, 2022
03aa0b1
core/vm: test eof container object
lightclient Nov 9, 2022
705ea5d
core/vm: refactor eof structures
lightclient Nov 9, 2022
304c7c4
core/vm: simplify eof init in evm
lightclient Nov 9, 2022
f1482a1
core/vm: refactor instruction validator to use switch
lightclient Nov 9, 2022
0ca562f
core/vm: remove go1.19 dependency
holiman Nov 11, 2022
62bb42a
core/vm: change the eof tests a bit
holiman Nov 11, 2022
28a7a27
core/vm: refactor eof objects
lightclient Nov 14, 2022
2734fb8
core/vm: fix off-by-one in type section parser
lightclient Nov 15, 2022
68d1834
core/vm: simplify eof parsing interface
lightclient Nov 15, 2022
011293d
core/vm: refactor eof more, move validation to separate file
lightclient Nov 15, 2022
d54f5c3
core/vm: misc clean up
lightclient Nov 15, 2022
2572feb
core/vm: use parse arg helper
lightclient Nov 16, 2022
8ff2493
tests: add shanghai to tests list
lightclient Nov 16, 2022
528b340
core/vm: don't validate code if EOF parse fails
lightclient Nov 16, 2022
d338bd0
core/vm: update callf and retf ops to b0 and b1
lightclient Nov 17, 2022
ef63306
core/vm: ensure type section size is 2n code sections
lightclient Nov 17, 2022
63ff89e
core/vm: properly return error during contract deploy
lightclient Nov 17, 2022
e38a059
Merge branch 'devel' into eof
ziogaschr Nov 30, 2022
7d4305e
core/vm: remove OpTstore test as it's not implemented yet
ziogaschr Dec 1, 2022
e4f8003
core/vm: fix lint issues after merge
ziogaschr Dec 1, 2022
3b35f1d
core/vm: handle analysis init on validateCode
ziogaschr Dec 1, 2022
4ea3d3c
core/vm: fix codeBitmap logic
ziogaschr Dec 1, 2022
513d63f
core/vm: refix codeBitmap based on holiman’s suggestion
ziogaschr Dec 1, 2022
e0de776
core/vm: rename eof1 -> eof1Only for consistency with legacyOnly
meowsbits Dec 1, 2022
ed99df4
core/vm: use ethereum/go-ethereum conditional logic for push constructor
meowsbits Dec 1, 2022
fc1d139
core/vm: add sanity test for opcode::string maps
meowsbits Dec 1, 2022
d2a1f68
core/vm: remove unused and commented DUP,SWAP string assignments
meowsbits Dec 1, 2022
e24886d
core/vm: remove commented operation fields .reverts and .halts
meowsbits Dec 1, 2022
2f4a717
Revert "core/vm: use ethereum/go-ethereum conditional logic for push …
meowsbits Dec 3, 2022
6e2b198
Merge branch 'devel' into eof
meowsbits Dec 5, 2022
90f2089
core/vm: use correct pc var
meowsbits Dec 5, 2022
4fca79c
core/vm: switch dep of “github.com/ethereum/go-ethereum/common” to “g…
ziogaschr Dec 6, 2022
e30257c
core/vm: remove superfluous code
ziogaschr Dec 9, 2022
b2a7893
Merge branch 'devel' of github.com:ledgerwatch/erigon into eof
ziogaschr Dec 11, 2022
a457a34
tests: fix tests by /s/ShanghaiBlock/ShanghaiTime
ziogaschr Dec 11, 2022
581f18f
tests: keep ArrowGlacierToMergeAtDiffC0000 as a fork in tests
ziogaschr Dec 12, 2022
4e57c8c
tests: revert head for testdata to 9e058e5
ziogaschr Dec 12, 2022
c4708a9
Update core/vm/eips.go
ziogaschr Dec 12, 2022
6fbc90f
core/vm: mark `CALLCODE` & `SELFDESTRUCT` as legacyOnly based on EIP3670
ziogaschr Dec 12, 2022
f4c02ba
common/compiler: fix vyper test
ziogaschr Dec 12, 2022
6171a3c
core/vm: remove superfluous check
ziogaschr Dec 12, 2022
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
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
GasPrice: u256.Num0,
Value: nil,
Data: common.Hex2Bytes("b9b046f9"),
}, 0, errors.New("invalid opcode: opcode 0xfe not defined"), nil},
}, 0, errors.New("invalid opcode: INVALID"), nil},

{"Valid", ethereum.CallMsg{
From: addr,
Expand Down
10 changes: 3 additions & 7 deletions core/vm/absint_cfg_proof_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
)

type CfgOpSem struct {
reverts bool
halts bool
isPush bool
isDup bool
isSwap bool
Expand All @@ -32,8 +30,6 @@ func NewCfgAbsSem() *CfgAbsSem {
continue
}
opsem := CfgOpSem{}
opsem.reverts = op.reverts
opsem.halts = op.halts
opsem.isPush = op.isPush
opsem.isDup = op.isDup
opsem.isSwap = op.isSwap
Expand Down Expand Up @@ -87,7 +83,7 @@ func resolveCheck(sem *CfgAbsSem, code []byte, st0 *astate, pc0 int) (map[int]bo
succs := make(map[int]bool)
jumps := make(map[int]bool)

if opsem == nil || opsem.halts || opsem.reverts {
if opsem == nil {
return succs, jumps, nil
}

Expand All @@ -98,7 +94,7 @@ func resolveCheck(sem *CfgAbsSem, code []byte, st0 *astate, pc0 int) (map[int]bo
if stack.hasIndices(0) {
jumpDest := stack.values[0]
if jumpDest.kind == InvalidValue {
//program terminates, don't add edges
// program terminates, don't add edges
} else if jumpDest.kind == TopValue {
empty := make(map[int]bool)
return empty, empty, errors.New("unresolvable jumps found")
Expand All @@ -113,7 +109,7 @@ func resolveCheck(sem *CfgAbsSem, code []byte, st0 *astate, pc0 int) (map[int]bo
}
}

//fall-thru edge
// fall-thru edge
if opcode != JUMP {
if pc0 < codeLen-opsem.numBytes {
succs[pc0+opsem.numBytes] = true
Expand Down
2 changes: 1 addition & 1 deletion core/vm/absint_cfg_proof_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func toProgram(code []byte) *Program {
op := OpCode(code[pc])
stmt.opcode = op
stmt.operation = jt[op]
stmt.ends = stmt.operation == nil || stmt.operation.halts || stmt.operation.reverts
stmt.ends = stmt.operation == nil
//fmt.Printf("%v %v %v", pc, stmt.opcode, stmt.operation.valid)

if op.IsPush() {
Expand Down
14 changes: 14 additions & 0 deletions core/vm/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ func codeBitmap(code []byte) []uint64 {
for pc := 0; pc < len(code); {
op := OpCode(code[pc])
pc++
// Short circruit for now on EOF ops with immediates.
// TODO(matt): make EOF-specific code bitmap
if op == RJUMP || op == RJUMPI || op == CALLF {
// TODO(CZ): For now the logic of bitvec.setN has been copied
// and modified to work with a uint64 slice instead of a bitvec.
// https://github.dev/lightclient/go-ethereum/blob/b3f36e10766956bf204f2d2b9415dcd8cfd2be6b/core/vm/analysis.go#L37

// Also applied @holiman suggestion in order to make legacy code tests work.
// https://github.com/ethereum/go-ethereum/pull/26133/files#r1026320917
bits[pc/64] |= 1 << (uint64(pc) & 63)
pc += 2

continue
}
if op >= PUSH1 && op <= PUSH32 {
numbits := int(op - PUSH1 + 1)
x := uint64(1) << (op - PUSH1)
Expand Down
43 changes: 33 additions & 10 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ type Contract struct {
CallerAddress common.Address
caller ContractRef
self ContractRef
jumpdests map[common.Hash][]uint64 // Aggregated result of JUMPDEST analysis.
analysis []uint64 // Locally cached result of JUMPDEST analysis
skipAnalysis bool

Code []byte
CodeHash common.Hash
CodeAddr *common.Address
Input []byte
jumpdests map[common.Hash][]uint64 // Aggregated result of JUMPDEST analysis.
analysis []uint64 // Locally cached result of JUMPDEST analysis
skipAnalysis bool

Code []byte
Container *EOF1Container
CodeHash common.Hash
CodeAddr *common.Address
Input []byte

Gas uint64
value *uint256.Int
Expand Down Expand Up @@ -109,6 +111,10 @@ func isCodeFromAnalysis(analysis []uint64, udest uint64) bool {
// isCode returns true if the provided PC location is an actual opcode, as
// opposed to a data-segment following a PUSHN operation.
func (c *Contract) isCode(udest uint64) bool {
if !c.IsLegacy() {
// TODO remove this
panic("shouldn't run jumpdest analysis on EOF")
}
// Do we have a contract hash already?
// If we do have a hash, that means it's a 'regular' contract. For regular
// contracts ( not temporary initcode), we store the analysis in a map
Expand Down Expand Up @@ -149,7 +155,7 @@ func (c *Contract) AsDelegate() *Contract {
return c
}

// GetOp returns the n'th element in the contract's byte array
// GetOp returns the n'th element in the contract's byte array.
func (c *Contract) GetOp(n uint64) OpCode {
return OpCode(c.GetByte(n))
}
Expand All @@ -163,6 +169,16 @@ func (c *Contract) GetByte(n uint64) byte {
return 0
}

// GetOpInSection returns the n'th element in the code sections's byte array.
func (c *Contract) GetOpInSection(n uint64, s uint64) OpCode {
if n < uint64(c.Container.codeSize[s]) {
code := c.Container.CodeAt(s)
return OpCode(code[n])
}

return STOP
}

// Caller returns the caller of the contract.
//
// Caller will recursively call caller when the contract is a delegate
Expand Down Expand Up @@ -190,18 +206,25 @@ func (c *Contract) Value() *uint256.Int {
return c.value
}

// IsLegacy returns true if contract is not EOF
func (c *Contract) IsLegacy() bool {
return c.Container == nil
}

// SetCallCode sets the code of the contract and address of the backing data
// object
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, container *EOF1Container) {
c.Code = code
c.Container = container
c.CodeHash = hash
c.CodeAddr = addr
}

// SetCodeOptionalHash can be used to provide code, but it's optional to provide hash.
// In case hash is not provided, the jumpdest analysis will not be saved to the parent context
func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) {
func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash, container *EOF1Container) {
c.Code = codeAndHash.code
c.Container = container
c.CodeHash = codeAndHash.hash
c.CodeAddr = addr
}
134 changes: 134 additions & 0 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ var activators = map[int]func(*JumpTable){
2200: enable2200,
1884: enable1884,
1344: enable1344,
3540: enable3540,
3670: enable3670,
4200: enable4200,
4750: enable4750,
}

// EnableEIP enables the given EIP on the config.
Expand Down Expand Up @@ -197,3 +201,133 @@ func enable3860(jt *JumpTable) {
jt[CREATE].dynamicGas = gasCreateEip3860
jt[CREATE2].dynamicGas = gasCreate2Eip3860
}

func enable3540(jt *JumpTable) {
// Do nothing.
}

func enable3670(jt *JumpTable) {
// Do nothing.
yperbasis marked this conversation as resolved.
Show resolved Hide resolved
}

// enable4200 applies EIP-4200 (RJUMP and RJUMPI opcodes)
func enable4200(jt *JumpTable) {
jt[RJUMP] = &operation{
execute: opRjump,
constantGas: GasFastStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
eof1Only: true,
}
jt[RJUMPI] = &operation{
execute: opRjumpi,
constantGas: GasFastishStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
eof1Only: true,
}
}

// opRjump implements the RJUMP opcode
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.Container.CodeAt(scope.ActiveSection)
idx = *pc + 1
)

relativeOffset, err := parseArg(code[idx : idx+2])
if err != nil {
return nil, err
}

// Move PC past the RJUMP instruction and its immediate argument.
*pc += 2 + 1

// Calculate the new PC given the relative offset. Already validated,
// so no need to verify casts.
*pc = uint64(int64(*pc)+int64(relativeOffset)) - 1 // pc will also be increased by interpreter loop

return nil, nil
}

// opRjumpi implements the RJUMPI opcode
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
condition := scope.Stack.Pop()
if condition.BitLen() == 0 {
ziogaschr marked this conversation as resolved.
Show resolved Hide resolved
// Not branching, just skip over immediate argument.
*pc += 2
return nil, nil
}
return opRjump(pc, interpreter, scope)
}

// enable4750 applies EIP-4750 (CALLF and RETF opcodes)
func enable4750(jt *JumpTable) {
jt[JUMP].legacyOnly = true
jt[JUMPI].legacyOnly = true
jt[CALLF] = &operation{
execute: opCallf,
constantGas: GasMidStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
eof1Only: true,
}
jt[RETF] = &operation{
execute: opRetf,
constantGas: GasMidStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
eof1Only: true,
}
}

// opCallf implements the CALLF opcode.
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.Container.CodeAt(scope.ActiveSection)
idx = *pc + 1
)
section, err := parseArg(code[idx : idx+2])
if err != nil {
return nil, err
}
caller := scope.RetStack[len(scope.RetStack)-1]
sig := scope.Contract.Container.types[int(section)]
if scope.Stack.Len() < int(caller.StackHeight)+int(sig.Input) {
return nil, fmt.Errorf("too few stack items")
}
if len(scope.RetStack) >= 1024 {
return nil, fmt.Errorf("return stack too deep")
}
context := &SubroutineContext{
Section: scope.ActiveSection,
StackHeight: caller.StackHeight + uint64(scope.Stack.Len()),
Pc: *pc,
}
scope.RetStack = append(scope.RetStack, context)

*pc = 0
*pc -= 1 // hacks xD
scope.ActiveSection = uint64(section)
scope.Stack.Floor = int(context.StackHeight)

return nil, nil
}

// opRetf implements the RETF opcode.
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
sig := scope.Contract.Container.types[scope.ActiveSection]
if scope.Stack.Len() < int(sig.Output) {
return nil, fmt.Errorf("too few stack items")
}

last := len(scope.RetStack) - 1
context := scope.RetStack[last]
scope.RetStack = scope.RetStack[:last]

*pc = context.Pc - 1
scope.ActiveSection = context.Section
scope.Stack.Floor = int(context.StackHeight)

return nil, nil
}
Loading