Skip to content

Commit

Permalink
imp(vm): Interpreter interface (backport ethereum#2) (ethereum#12)
Browse files Browse the repository at this point in the history
Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Freddy Caceres <facs95@gmail.com>
  • Loading branch information
3 people committed Feb 1, 2023
1 parent 485d066 commit 754e68b
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 21 deletions.
43 changes: 43 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,43 @@

<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) \#<issue-number> message
The issue numbers will later be link-ified during the release process so you do
not have to worry about including a link manually, but you can if you wish.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"Client Breaking" for breaking CLI commands and REST routes used by end-users.
"API Breaking" for breaking exported APIs used by developers building on SDK.
"State Machine Breaking" for any changes that result in a different AppState given same genesisState and txList.
Ref: https://keepachangelog.com/en/1.0.0/
-->

# Changelog

## Unreleased

### Improvements

* [#2](https://github.com/evmos/go-ethereum/pull/2) Define `Interpreter` interface for the EVM.
16 changes: 11 additions & 5 deletions core/vm/evm.go
Expand Up @@ -113,7 +113,7 @@ type EVM struct {
Config Config
// global (to this context) ethereum virtual machine
// used throughout the execution of the tx.
interpreter *EVMInterpreter
interpreter Interpreter
// abort is used to abort the EVM calling operations
// NOTE: must be set atomically
abort int32
Expand All @@ -134,6 +134,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil),
}

evm.interpreter = NewEVMInterpreter(evm, config)
return evm
}
Expand All @@ -157,10 +158,15 @@ func (evm *EVM) Cancelled() bool {
}

// Interpreter returns the current interpreter
func (evm *EVM) Interpreter() *EVMInterpreter {
func (evm *EVM) Interpreter() Interpreter {
return evm.interpreter
}

// WithInterpreter sets the interpreter to the EVM instance
func (evm *EVM) WithInterpreter(interpreter Interpreter) {
evm.interpreter = interpreter
}

// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
Expand Down Expand Up @@ -263,7 +269,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
var snapshot = evm.StateDB.Snapshot()
snapshot := evm.StateDB.Snapshot()

// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Debug {
Expand Down Expand Up @@ -304,7 +310,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
snapshot := evm.StateDB.Snapshot()

// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Debug {
Expand Down Expand Up @@ -348,7 +354,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// after all empty accounts were deleted, so this is not required. However, if we omit this,
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
// We could change this, but for now it's left for legacy reasons
var snapshot = evm.StateDB.Snapshot()
snapshot := evm.StateDB.Snapshot()

// We do an AddBalance of zero here, just in order to trigger a touch.
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
Expand Down
40 changes: 24 additions & 16 deletions core/vm/instructions_test.go
Expand Up @@ -41,9 +41,11 @@ type twoOperandParams struct {
y string
}

var alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
var commonParams []*twoOperandParams
var twoOpMethods map[string]executionFunc
var (
alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
commonParams []*twoOperandParams
twoOpMethods map[string]executionFunc
)

func init() {
// Params is a list of common edgecases that should be used for some common tests
Expand Down Expand Up @@ -92,10 +94,10 @@ func init() {

func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) {
var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
interpreter = env.interpreter
)

for i, test := range tests {
Expand All @@ -104,7 +106,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
stack.push(x)
stack.push(y)
opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
opFn(&pc, interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
}
Expand Down Expand Up @@ -202,7 +204,8 @@ func TestAddMod(t *testing.T) {
z string
expected string
}{
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
{
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
Expand Down Expand Up @@ -246,7 +249,7 @@ func TestWriteExpectedValues(t *testing.T) {
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x)
stack.push(y)
opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
opFn(&pc, interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil})
actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
}
Expand All @@ -258,7 +261,7 @@ func TestWriteExpectedValues(t *testing.T) {
if err != nil {
t.Fatal(err)
}
_ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644)
_ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0o644)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -447,11 +450,13 @@ func BenchmarkOpEq(b *testing.B) {

opBenchmark(b, opEq, x, y)
}

func BenchmarkOpEq2(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201fffffffe"
opBenchmark(b, opEq, x, y)
}

func BenchmarkOpAnd(b *testing.B) {
x := alphabetSoup
y := alphabetSoup
Expand Down Expand Up @@ -502,18 +507,21 @@ func BenchmarkOpSHL(b *testing.B) {

opBenchmark(b, opSHL, x, y)
}

func BenchmarkOpSHR(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"

opBenchmark(b, opSHR, x, y)
}

func BenchmarkOpSAR(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
y := "ff"

opBenchmark(b, opSAR, x, y)
}

func BenchmarkOpIsZero(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
opBenchmark(b, opIszero, x)
Expand Down Expand Up @@ -673,12 +681,12 @@ func TestRandom(t *testing.T) {
{name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})},
} {
var (
env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
interpreter = env.interpreter
)
opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
opRandom(&pc, interpreter.(*EVMInterpreter), &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
Expand Down
10 changes: 10 additions & 0 deletions core/vm/interface.go
Expand Up @@ -88,3 +88,13 @@ type CallContext interface {
// Create a new contract
Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error)
}

// Interpreter is used to run Ethereum based contracts and will utilize the
// passed environment to query external sources for state information.
// The Interpreter will run the byte code VM based on the passed
// configuration.
type Interpreter interface {
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
Run(contract *Contract, input []byte, static bool) ([]byte, error)
}
2 changes: 2 additions & 0 deletions core/vm/interpreter.go
Expand Up @@ -52,6 +52,8 @@ type keccakState interface {
Read([]byte) (int, error)
}

var _ Interpreter = &EVMInterpreter{}

// EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct {
evm *EVM
Expand Down

0 comments on commit 754e68b

Please sign in to comment.