Skip to content

Commit 0fbae02

Browse files
committed
core/vm: Add EVMC support
1 parent d987357 commit 0fbae02

File tree

2 files changed

+339
-17
lines changed

2 files changed

+339
-17
lines changed

core/vm/evm.go

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -140,24 +140,21 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
140140
}
141141

142142
if chainConfig.IsEWASM(ctx.BlockNumber) {
143-
// to be implemented by EVM-C and Wagon PRs.
144-
// if vmConfig.EWASMInterpreter != "" {
145-
// extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
146-
// path := extIntOpts[0]
147-
// options := []string{}
148-
// if len(extIntOpts) > 1 {
149-
// options = extIntOpts[1..]
150-
// }
151-
// evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options))
152-
// } else {
153-
// evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
154-
// }
155-
panic("No supported ewasm interpreter yet.")
156-
}
157-
158-
// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
159-
// as we always want to have the built-in EVM as the failover option.
143+
if vmConfig.EWASMInterpreter != "" {
144+
evm.interpreters = append(evm.interpreters, NewEVMC(vmConfig.EWASMInterpreter, evm))
145+
} else {
146+
panic("The default ewasm interpreter not supported yet.")
147+
}
148+
}
149+
150+
if vmConfig.EVMInterpreter != "" {
151+
// Create custom EVM.
152+
evm.interpreters = append(evm.interpreters, NewEVMC(vmConfig.EVMInterpreter, evm))
153+
}
154+
155+
// Keep the built-in EVM as the failover option.
160156
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
157+
161158
evm.interpreter = evm.interpreters[0]
162159

163160
return evm

core/vm/evmc.go

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
// Copyright 2018 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package vm
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"math/big"
23+
"strings"
24+
"sync"
25+
26+
"github.com/ethereum/evmc/bindings/go/evmc"
27+
"github.com/ethereum/go-ethereum/common"
28+
"github.com/ethereum/go-ethereum/core/types"
29+
"github.com/ethereum/go-ethereum/log"
30+
"github.com/ethereum/go-ethereum/params"
31+
)
32+
33+
type EVMC struct {
34+
instance *evmc.Instance
35+
env *EVM
36+
readOnly bool // TODO: The readOnly flag should not be here.
37+
}
38+
39+
var (
40+
createMu sync.Mutex
41+
evmcConfig string // The configuration the instance was created with.
42+
evmcInstance *evmc.Instance
43+
)
44+
45+
func createVM(config string) *evmc.Instance {
46+
createMu.Lock()
47+
defer createMu.Unlock()
48+
49+
if evmcInstance == nil {
50+
options := strings.Split(config, ",")
51+
path := options[0]
52+
53+
if path == "" {
54+
panic("EVMC VM path not provided, set --vm.(evm|ewasm)=/path/to/vm")
55+
}
56+
57+
var err error
58+
evmcInstance, err = evmc.Load(path)
59+
if err != nil {
60+
panic(err.Error())
61+
}
62+
log.Info("EVMC VM loaded", "name", evmcInstance.Name(), "version", evmcInstance.Version(), "path", path)
63+
64+
for _, option := range options[1:] {
65+
if idx := strings.Index(option, "="); idx >= 0 {
66+
name := option[:idx]
67+
value := option[idx+1:]
68+
err := evmcInstance.SetOption(name, value)
69+
if err == nil {
70+
log.Info("EVMC VM option set", "name", name, "value", value)
71+
} else {
72+
log.Warn("EVMC VM option setting failed", "name", name, "error", err)
73+
}
74+
}
75+
}
76+
77+
evm1Cap := evmcInstance.HasCapability(evmc.CapabilityEVM1)
78+
ewasmCap := evmcInstance.HasCapability(evmc.CapabilityEWASM)
79+
log.Info("EVMC VM capabilities", "evm1", evm1Cap, "ewasm", ewasmCap)
80+
81+
evmcConfig = config // Remember the config.
82+
} else if evmcConfig != config {
83+
log.Error("New EVMC VM requested", "newconfig", config, "oldconfig", evmcConfig)
84+
}
85+
return evmcInstance
86+
}
87+
88+
func NewEVMC(options string, env *EVM) *EVMC {
89+
return &EVMC{createVM(options), env, false}
90+
}
91+
92+
// Implements evmc.HostContext interface.
93+
type HostContext struct {
94+
env *EVM
95+
contract *Contract
96+
}
97+
98+
func (host *HostContext) AccountExists(addr common.Address) bool {
99+
env := host.env
100+
eip158 := env.ChainConfig().IsEIP158(env.BlockNumber)
101+
if eip158 {
102+
if !env.StateDB.Empty(addr) {
103+
return true
104+
}
105+
} else if env.StateDB.Exist(addr) {
106+
return true
107+
}
108+
return false
109+
}
110+
111+
func (host *HostContext) GetStorage(addr common.Address, key common.Hash) common.Hash {
112+
env := host.env
113+
return env.StateDB.GetState(addr, key)
114+
}
115+
116+
func (host *HostContext) SetStorage(addr common.Address, key common.Hash, value common.Hash) (status evmc.StorageStatus) {
117+
env := host.env
118+
119+
oldValue := env.StateDB.GetState(addr, key)
120+
if oldValue == value {
121+
return evmc.StorageUnchanged
122+
}
123+
124+
env.StateDB.SetState(addr, key, value)
125+
126+
zero := common.Hash{}
127+
status = evmc.StorageModified
128+
if oldValue == zero {
129+
return evmc.StorageAdded
130+
} else if value == zero {
131+
env.StateDB.AddRefund(params.SstoreRefundGas)
132+
return evmc.StorageDeleted
133+
}
134+
return evmc.StorageModified
135+
}
136+
137+
func (host *HostContext) GetBalance(addr common.Address) common.Hash {
138+
env := host.env
139+
balance := env.StateDB.GetBalance(addr)
140+
return common.BigToHash(balance)
141+
}
142+
143+
func (host *HostContext) GetCodeSize(addr common.Address) int {
144+
env := host.env
145+
return env.StateDB.GetCodeSize(addr)
146+
}
147+
148+
func (host *HostContext) GetCodeHash(addr common.Address) common.Hash {
149+
env := host.env
150+
return env.StateDB.GetCodeHash(addr)
151+
}
152+
153+
func (host *HostContext) GetCode(addr common.Address) []byte {
154+
env := host.env
155+
return env.StateDB.GetCode(addr)
156+
}
157+
158+
func (host *HostContext) Selfdestruct(addr common.Address, beneficiary common.Address) {
159+
env := host.env
160+
db := env.StateDB
161+
if !db.HasSuicided(addr) {
162+
db.AddRefund(params.SuicideRefundGas)
163+
}
164+
balance := db.GetBalance(addr)
165+
db.AddBalance(beneficiary, balance)
166+
db.Suicide(addr)
167+
}
168+
169+
func (host *HostContext) GetTxContext() (gasPrice common.Hash, origin common.Address, coinbase common.Address,
170+
number int64, timestamp int64, gasLimit int64, difficulty common.Hash) {
171+
172+
env := host.env
173+
gasPrice = common.BigToHash(env.GasPrice)
174+
origin = env.Origin
175+
coinbase = env.Coinbase
176+
number = env.BlockNumber.Int64()
177+
timestamp = env.Time.Int64()
178+
gasLimit = int64(env.GasLimit)
179+
difficulty = common.BigToHash(env.Difficulty)
180+
181+
return gasPrice, origin, coinbase, number, timestamp, gasLimit, difficulty
182+
}
183+
184+
func (host *HostContext) GetBlockHash(number int64) common.Hash {
185+
env := host.env
186+
b := env.BlockNumber.Int64()
187+
if number >= (b-256) && number < b {
188+
return env.GetHash(uint64(number))
189+
}
190+
return common.Hash{}
191+
}
192+
193+
func (host *HostContext) EmitLog(addr common.Address, topics []common.Hash, data []byte) {
194+
env := host.env
195+
env.StateDB.AddLog(&types.Log{
196+
Address: addr,
197+
Topics: topics,
198+
Data: data,
199+
BlockNumber: env.BlockNumber.Uint64(),
200+
})
201+
}
202+
203+
func (host *HostContext) Call(kind evmc.CallKind,
204+
destination common.Address, sender common.Address, value *big.Int, input []byte, gas int64, depth int,
205+
static bool) (output []byte, gasLeft int64, createAddr common.Address, err error) {
206+
207+
env := host.env
208+
209+
gasU := uint64(gas)
210+
var gasLeftU uint64
211+
212+
switch kind {
213+
case evmc.Call:
214+
if static {
215+
output, gasLeftU, err = env.StaticCall(host.contract, destination, input, gasU)
216+
} else {
217+
output, gasLeftU, err = env.Call(host.contract, destination, input, gasU, value)
218+
}
219+
case evmc.DelegateCall:
220+
output, gasLeftU, err = env.DelegateCall(host.contract, destination, input, gasU)
221+
case evmc.CallCode:
222+
output, gasLeftU, err = env.CallCode(host.contract, destination, input, gasU, value)
223+
case evmc.Create:
224+
var createOutput []byte
225+
createOutput, createAddr, gasLeftU, err = env.Create(host.contract, input, gasU, value)
226+
isHomestead := env.ChainConfig().IsHomestead(env.BlockNumber)
227+
if !isHomestead && err == ErrCodeStoreOutOfGas {
228+
err = nil
229+
}
230+
if err == errExecutionReverted {
231+
// Assign return buffer from REVERT.
232+
// TODO: Bad API design: return data buffer and the code is returned in the same place. In worst case
233+
// the code is returned also when there is not enough funds to deploy the code.
234+
output = createOutput
235+
}
236+
}
237+
238+
// Map errors.
239+
if err == errExecutionReverted {
240+
err = evmc.Revert
241+
} else if err != nil {
242+
err = evmc.Failure
243+
}
244+
245+
gasLeft = int64(gasLeftU)
246+
return output, gasLeft, createAddr, err
247+
}
248+
249+
func getRevision(env *EVM) evmc.Revision {
250+
n := env.BlockNumber
251+
conf := env.ChainConfig()
252+
if conf.IsConstantinople(n) {
253+
return evmc.Constantinople
254+
}
255+
if conf.IsByzantium(n) {
256+
return evmc.Byzantium
257+
}
258+
if conf.IsEIP158(n) {
259+
return evmc.SpuriousDragon
260+
}
261+
if conf.IsEIP150(n) {
262+
return evmc.TangerineWhistle
263+
}
264+
if conf.IsHomestead(n) {
265+
return evmc.Homestead
266+
}
267+
return evmc.Frontier
268+
}
269+
270+
func (evm *EVMC) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
271+
evm.env.depth++
272+
defer func() { evm.env.depth-- }()
273+
274+
// Don't bother with the execution if there's no code.
275+
if len(contract.Code) == 0 {
276+
return nil, nil
277+
}
278+
279+
kind := evmc.Call
280+
if evm.env.StateDB.GetCodeSize(contract.Address()) == 0 {
281+
// Guess if this is a CREATE.
282+
kind = evmc.Create
283+
}
284+
285+
// Make sure the readOnly is only set if we aren't in readOnly yet.
286+
// This makes also sure that the readOnly flag isn't removed for child calls.
287+
if readOnly && !evm.readOnly {
288+
evm.readOnly = true
289+
defer func() { evm.readOnly = false }()
290+
}
291+
292+
output, gasLeft, err := evm.instance.Execute(
293+
&HostContext{evm.env, contract},
294+
getRevision(evm.env),
295+
kind,
296+
evm.readOnly,
297+
evm.env.depth-1,
298+
int64(contract.Gas),
299+
contract.Address(),
300+
contract.Caller(),
301+
input,
302+
common.BigToHash(contract.value),
303+
contract.Code,
304+
common.Hash{})
305+
306+
contract.Gas = uint64(gasLeft)
307+
308+
if err == evmc.Revert {
309+
err = errExecutionReverted
310+
} else if evmcError, ok := err.(evmc.Error); ok && evmcError.IsInternalError() {
311+
panic(fmt.Sprintf("EVMC VM internal error: %s", evmcError.Error()))
312+
}
313+
314+
return output, err
315+
}
316+
317+
func (evm *EVMC) CanRun(code []byte) bool {
318+
cap := evmc.CapabilityEVM1
319+
wasmPreamble := []byte("\x00asm\x01\x00\x00\x00")
320+
if bytes.HasPrefix(code, wasmPreamble) {
321+
cap = evmc.CapabilityEWASM
322+
}
323+
// FIXME: Optimize. Access capabilities once.
324+
return evm.instance.HasCapability(cap)
325+
}

0 commit comments

Comments
 (0)