EVM Changed added

This commit is contained in:
AnilChinchawale 2019-03-22 17:39:27 +05:30
parent 1e77239ede
commit 2f02ac8043
32 changed files with 2145 additions and 1213 deletions

View file

@ -44,7 +44,7 @@ func disasmCmd(ctx *cli.Context) error {
return err return err
} }
code := strings.TrimSpace(string(in)) code := strings.TrimSpace(string(in[:]))
fmt.Printf("%v\n", code) fmt.Printf("%v\n", code)
return asm.PrintDisassembled(code) return asm.PrintDisassembled(code)
} }

View file

@ -25,7 +25,7 @@ import (
func Compile(fn string, src []byte, debug bool) (string, error) { func Compile(fn string, src []byte, debug bool) (string, error) {
compiler := asm.NewCompiler(debug) compiler := asm.NewCompiler(debug)
compiler.Feed(asm.Lex(src, debug)) compiler.Feed(asm.Lex(fn, src, debug))
bin, compileErrors := compiler.Compile() bin, compileErrors := compiler.Compile()
if len(compileErrors) > 0 { if len(compileErrors) > 0 {

View file

@ -110,11 +110,6 @@ var (
Name: "nostack", Name: "nostack",
Usage: "disable stack output", Usage: "disable stack output",
} }
EVMInterpreterFlag = cli.StringFlag{
Name: "vm.evm",
Usage: "External EVM configuration (default = built-in interpreter)",
Value: "",
}
) )
func init() { func init() {
@ -138,7 +133,6 @@ func init() {
ReceiverFlag, ReceiverFlag,
DisableMemoryFlag, DisableMemoryFlag,
DisableStackFlag, DisableStackFlag,
EVMInterpreterFlag,
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
compileCommand, compileCommand,

View file

@ -21,20 +21,20 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
goruntime "runtime"
"runtime/pprof" "runtime/pprof"
"time" "time"
goruntime "runtime"
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
@ -76,20 +76,18 @@ func runCmd(ctx *cli.Context) error {
logconfig := &vm.LogConfig{ logconfig := &vm.LogConfig{
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
DisableStack: ctx.GlobalBool(DisableStackFlag.Name), DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
Debug: ctx.GlobalBool(DebugFlag.Name),
} }
var ( var (
tracer vm.Tracer tracer vm.Tracer
debugLogger *vm.StructLogger debugLogger *vm.StructLogger
statedb *state.StateDB statedb *state.StateDB
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
sender = common.BytesToAddress([]byte("sender")) sender = common.StringToAddress("sender")
receiver = common.BytesToAddress([]byte("receiver")) receiver = common.StringToAddress("receiver")
genesisConfig *core.Genesis
) )
if ctx.GlobalBool(MachineFlag.Name) { if ctx.GlobalBool(MachineFlag.Name) {
tracer = vm.NewJSONLogger(logconfig, os.Stdout) tracer = NewJSONLogger(logconfig, os.Stdout)
} else if ctx.GlobalBool(DebugFlag.Name) { } else if ctx.GlobalBool(DebugFlag.Name) {
debugLogger = vm.NewStructLogger(logconfig) debugLogger = vm.NewStructLogger(logconfig)
tracer = debugLogger tracer = debugLogger
@ -98,14 +96,13 @@ func runCmd(ctx *cli.Context) error {
} }
if ctx.GlobalString(GenesisFlag.Name) != "" { if ctx.GlobalString(GenesisFlag.Name) != "" {
gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
genesisConfig = gen db, _ := ethdb.NewMemDatabase()
db := rawdb.NewMemoryDatabase()
genesis := gen.ToBlock(db) genesis := gen.ToBlock(db)
statedb, _ = state.New(genesis.Root(), state.NewDatabase(db)) statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
chainConfig = gen.Config chainConfig = gen.Config
} else { } else {
statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) db, _ := ethdb.NewMemDatabase()
genesisConfig = new(core.Genesis) statedb, _ = state.New(common.Hash{}, state.NewDatabase(db))
} }
if ctx.GlobalString(SenderFlag.Name) != "" { if ctx.GlobalString(SenderFlag.Name) != "" {
sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name)) sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))
@ -157,23 +154,15 @@ func runCmd(ctx *cli.Context) error {
} }
initialGas := ctx.GlobalUint64(GasFlag.Name) initialGas := ctx.GlobalUint64(GasFlag.Name)
if genesisConfig.GasLimit != 0 {
initialGas = genesisConfig.GasLimit
}
runtimeConfig := runtime.Config{ runtimeConfig := runtime.Config{
Origin: sender, Origin: sender,
State: statedb, State: statedb,
GasLimit: initialGas, GasLimit: initialGas,
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
Value: utils.GlobalBig(ctx, ValueFlag.Name), Value: utils.GlobalBig(ctx, ValueFlag.Name),
Difficulty: genesisConfig.Difficulty,
Time: new(big.Int).SetUint64(genesisConfig.Timestamp),
Coinbase: genesisConfig.Coinbase,
BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: tracer, Tracer: tracer,
Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name),
EVMInterpreter: ctx.GlobalString(EVMInterpreterFlag.Name),
}, },
} }
@ -207,7 +196,6 @@ func runCmd(ctx *cli.Context) error {
execTime := time.Since(tstart) execTime := time.Since(tstart)
if ctx.GlobalBool(DumpFlag.Name) { if ctx.GlobalBool(DumpFlag.Name) {
statedb.Commit(true)
statedb.IntermediateRoot(true) statedb.IntermediateRoot(true)
fmt.Println(string(statedb.Dump())) fmt.Println(string(statedb.Dump()))
} }
@ -246,7 +234,9 @@ Gas used: %d
`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas) `, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
} }
if tracer == nil { if tracer != nil {
tracer.CaptureEnd(ret, initialGas-leftOverGas, execTime, err)
} else {
fmt.Printf("0x%x\n", ret) fmt.Printf("0x%x\n", ret)
if err != nil { if err != nil {
fmt.Printf(" error: %v\n", err) fmt.Printf(" error: %v\n", err)

View file

@ -38,8 +38,6 @@ var stateTestCommand = cli.Command{
ArgsUsage: "<file>", ArgsUsage: "<file>",
} }
// StatetestResult contains the execution status after running a state test, any
// error that might have occurred and a dump of the final state if requested.
type StatetestResult struct { type StatetestResult struct {
Name string `json:"name"` Name string `json:"name"`
Pass bool `json:"pass"` Pass bool `json:"pass"`
@ -68,7 +66,7 @@ func stateTestCmd(ctx *cli.Context) error {
) )
switch { switch {
case ctx.GlobalBool(MachineFlag.Name): case ctx.GlobalBool(MachineFlag.Name):
tracer = vm.NewJSONLogger(config, os.Stderr) tracer = NewJSONLogger(config, os.Stderr)
case ctx.GlobalBool(DebugFlag.Name): case ctx.GlobalBool(DebugFlag.Name):
debugger = vm.NewStructLogger(config) debugger = vm.NewStructLogger(config)
@ -97,10 +95,6 @@ func stateTestCmd(ctx *cli.Context) error {
// Run the test and aggregate the result // Run the test and aggregate the result
result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
state, err := test.Run(st, cfg) state, err := test.Run(st, cfg)
// print state root for evmlab tracing
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
}
if err != nil { if err != nil {
// Test failed, mark as so and dump any state to aid debugging // Test failed, mark as so and dump any state to aid debugging
result.Pass, result.Error = false, err.Error() result.Pass, result.Error = false, err.Error()
@ -109,6 +103,10 @@ func stateTestCmd(ctx *cli.Context) error {
result.State = &dump result.State = &dump
} }
} }
// print state root for evmlab tracing (already committed above, so no need to delete objects again
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
}
results = append(results, *result) results = append(results, *result)

View file

@ -16,34 +16,6 @@
package vm package vm
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// destinations stores one map per contract (keyed by hash of code).
// The maps contain an entry for each location of a JUMPDEST
// instruction.
type destinations map[common.Hash]bitvec
// has checks whether code has a JUMPDEST at dest.
func (d destinations) has(codehash common.Hash, code []byte, dest *big.Int) bool {
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case.
udest := dest.Uint64()
if dest.BitLen() >= 63 || udest >= uint64(len(code)) {
return false
}
m, analysed := d[codehash]
if !analysed {
m = codeBitmap(code)
d[codehash] = m
}
return OpCode(code[udest]) == JUMPDEST && m.codeSegment(udest)
}
// bitvec is a bit vector which maps bytes in a program. // bitvec is a bit vector which maps bytes in a program.
// An unset bit means the byte is an opcode, a set bit means // An unset bit means the byte is an opcode, a set bit means
// it's data (i.e. argument of PUSHxx). // it's data (i.e. argument of PUSHxx).

View file

@ -16,7 +16,11 @@
package vm package vm
import "testing" import (
"testing"
"github.com/ethereum/go-ethereum/crypto"
)
func TestJumpDestAnalysis(t *testing.T) { func TestJumpDestAnalysis(t *testing.T) {
tests := []struct { tests := []struct {
@ -49,5 +53,23 @@ func TestJumpDestAnalysis(t *testing.T) {
t.Fatalf("expected %x, got %02x", test.exp, ret[test.which]) t.Fatalf("expected %x, got %02x", test.exp, ret[test.which])
} }
} }
}
func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) {
// 1.4 ms
code := make([]byte, 1200000)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
codeBitmap(code)
}
bench.StopTimer()
}
func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
// 4 ms
code := make([]byte, 1200000)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
crypto.Keccak256Hash(code)
}
bench.StopTimer()
} }

View file

@ -23,13 +23,31 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
) )
// calculates the memory size required for a step // calcMemSize64 calculates the required memory size, and returns
func calcMemSize(off, l *big.Int) *big.Int { // the size and whether the result overflowed uint64
if l.Sign() == 0 { func calcMemSize64(off, l *big.Int) (uint64, bool) {
return common.Big0 if !l.IsUint64() {
return 0, true
} }
return calcMemSize64WithUint(off, l.Uint64())
}
return new(big.Int).Add(off, l) // calcMemSize64WithUint calculates the required memory size, and returns
// the size and whether the result overflowed uint64
// Identical to calcMemSize64, but length is a uint64
func calcMemSize64WithUint(off *big.Int, length64 uint64) (uint64, bool) {
// if length is zero, memsize is always zero, regardless of offset
if length64 == 0 {
return 0, false
}
// Check that offset doesn't overflow
if !off.IsUint64() {
return 0, true
}
offset64 := off.Uint64()
val := offset64 + length64
// if value < either of it's parts, then it overflowed
return val, val < offset64
} }
// getData returns a slice from the data based on the start and size and pads // getData returns a slice from the data based on the start and size and pads
@ -59,7 +77,7 @@ func getDataBig(data []byte, start *big.Int, size *big.Int) []byte {
// bigUint64 returns the integer casted to a uint64 and returns whether it // bigUint64 returns the integer casted to a uint64 and returns whether it
// overflowed in the process. // overflowed in the process.
func bigUint64(v *big.Int) (uint64, bool) { func bigUint64(v *big.Int) (uint64, bool) {
return v.Uint64(), v.BitLen() > 64 return v.Uint64(), !v.IsUint64()
} }
// toWordSize returns the ceiled word size required for memory expansion. // toWordSize returns the ceiled word size required for memory expansion.

View file

@ -40,7 +40,7 @@ type AccountRef common.Address
func (ar AccountRef) Address() common.Address { return (common.Address)(ar) } func (ar AccountRef) Address() common.Address { return (common.Address)(ar) }
// Contract represents an ethereum contract in the state database. It contains // Contract represents an ethereum contract in the state database. It contains
// the the contract code, calling arguments. Contract implements ContractRef // the contract code, calling arguments. Contract implements ContractRef
type Contract struct { type Contract struct {
// CallerAddress is the result of the caller which initialised this // CallerAddress is the result of the caller which initialised this
// contract. However when the "call method" is delegated this value // contract. However when the "call method" is delegated this value
@ -49,7 +49,8 @@ type Contract struct {
caller ContractRef caller ContractRef
self ContractRef self ContractRef
jumpdests destinations // result of JUMPDEST analysis. jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
analysis bitvec // Locally cached result of JUMPDEST analysis
Code []byte Code []byte
CodeHash common.Hash CodeHash common.Hash
@ -58,21 +59,17 @@ type Contract struct {
Gas uint64 Gas uint64
value *big.Int value *big.Int
Args []byte
DelegateCall bool
} }
// NewContract returns a new contract environment for the execution of EVM. // NewContract returns a new contract environment for the execution of EVM.
func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract { func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract {
c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil} c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}
if parent, ok := caller.(*Contract); ok { if parent, ok := caller.(*Contract); ok {
// Reuse JUMPDEST analysis from parent context if available. // Reuse JUMPDEST analysis from parent context if available.
c.jumpdests = parent.jumpdests c.jumpdests = parent.jumpdests
} else { } else {
c.jumpdests = make(destinations) c.jumpdests = make(map[common.Hash]bitvec)
} }
// Gas should be a pointer so it can safely be reduced through the run // Gas should be a pointer so it can safely be reduced through the run
@ -84,10 +81,42 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin
return c return c
} }
func (c *Contract) validJumpdest(dest *big.Int) bool {
udest := dest.Uint64()
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case.
if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) {
return false
}
// Only JUMPDESTs allowed for destinations
if OpCode(c.Code[udest]) != JUMPDEST {
return false
}
// Do we have a contract hash already?
if c.CodeHash != (common.Hash{}) {
// Does parent context have the analysis?
analysis, exist := c.jumpdests[c.CodeHash]
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
}
return analysis.codeSegment(udest)
}
// We don't have the code hash, most likely a piece of initcode not already
// in state trie. In that case, we do an analysis, and save it locally, so
// 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)
}
return c.analysis.codeSegment(udest)
}
// AsDelegate sets the contract to be a delegate call and returns the current // AsDelegate sets the contract to be a delegate call and returns the current
// contract (for chaining calls) // contract (for chaining calls)
func (c *Contract) AsDelegate() *Contract { func (c *Contract) AsDelegate() *Contract {
c.DelegateCall = true
// NOTE: caller must, at all times be a contract. It should never happen // NOTE: caller must, at all times be a contract. It should never happen
// that caller is something other than a Contract. // that caller is something other than a Contract.
parent := c.caller.(*Contract) parent := c.caller.(*Contract)
@ -138,16 +167,18 @@ func (c *Contract) Value() *big.Int {
return c.value return c.value
} }
// SetCode sets the code to the contract
func (self *Contract) SetCode(hash common.Hash, code []byte) {
self.Code = code
self.CodeHash = hash
}
// SetCallCode sets the code of the contract and address of the backing data // SetCallCode sets the code of the contract and address of the backing data
// object // object
func (self *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
self.Code = code c.Code = code
self.CodeHash = hash c.CodeHash = hash
self.CodeAddr = addr 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) {
c.Code = codeAndHash.code
c.CodeHash = codeAndHash.hash
c.CodeAddr = addr
} }

View file

@ -116,7 +116,7 @@ func (c *sha256hash) Run(input []byte) ([]byte, error) {
return h[:], nil return h[:], nil
} }
// RIPMED160 implemented as a native contract. // RIPEMD160 implemented as a native contract.
type ripemd160hash struct{} type ripemd160hash struct{}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.

View file

@ -18,6 +18,7 @@ package vm
import "errors" import "errors"
// List execution errors
var ( var (
ErrOutOfGas = errors.New("out of gas") ErrOutOfGas = errors.New("out of gas")
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")
@ -25,4 +26,5 @@ var (
ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") ErrTraceLimitReached = errors.New("the number of logs reached the specified limit")
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter")
) )

View file

@ -31,15 +31,17 @@ import (
var emptyCodeHash = crypto.Keccak256Hash(nil) var emptyCodeHash = crypto.Keccak256Hash(nil)
type ( type (
// CanTransferFunc is the signature of a transfer guard function
CanTransferFunc func(StateDB, common.Address, *big.Int) bool CanTransferFunc func(StateDB, common.Address, *big.Int) bool
TransferFunc func(StateDB, common.Address, common.Address, *big.Int) // TransferFunc is the signature of a transfer function
TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
// GetHashFunc returns the nth block hash in the blockchain // GetHashFunc returns the nth block hash in the blockchain
// and is used by the BLOCKHASH EVM op code. // and is used by the BLOCKHASH EVM op code.
GetHashFunc func(uint64) common.Hash GetHashFunc func(uint64) common.Hash
) )
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) { func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
if contract.CodeAddr != nil { if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) { if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
@ -49,7 +51,20 @@ func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
return RunPrecompiledContract(p, input, contract) return RunPrecompiledContract(p, input, contract)
} }
} }
return evm.interpreter.Run(contract, input) for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) {
if evm.interpreter != interpreter {
// Ensure that the interpreter pointer is set back
// to its current value upon return.
defer func(i Interpreter) {
evm.interpreter = i
}(evm.interpreter)
evm.interpreter = interpreter
}
return interpreter.Run(contract, input, readOnly)
}
}
return nil, ErrNoCompatibleInterpreter
} }
// Context provides the EVM with auxiliary information. Once provided // Context provides the EVM with auxiliary information. Once provided
@ -101,7 +116,8 @@ type EVM struct {
vmConfig Config vmConfig Config
// global (to this context) ethereum virtual machine // global (to this context) ethereum virtual machine
// used throughout the execution of the tx. // used throughout the execution of the tx.
interpreter *Interpreter interpreters []Interpreter
interpreter Interpreter
// abort is used to abort the EVM calling operations // abort is used to abort the EVM calling operations
// NOTE: must be set atomically // NOTE: must be set atomically
abort int32 abort int32
@ -115,14 +131,35 @@ type EVM struct {
// only ever be used *once*. // only ever be used *once*.
func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
evm := &EVM{ evm := &EVM{
Context: ctx, Context: ctx,
StateDB: statedb, StateDB: statedb,
vmConfig: vmConfig, vmConfig: vmConfig,
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(ctx.BlockNumber), chainRules: chainConfig.Rules(ctx.BlockNumber),
interpreters: make([]Interpreter, 0, 1),
} }
evm.interpreter = NewInterpreter(evm, vmConfig) if chainConfig.IsEWASM(ctx.BlockNumber) {
// to be implemented by EVM-C and Wagon PRs.
// if vmConfig.EWASMInterpreter != "" {
// extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
// path := extIntOpts[0]
// options := []string{}
// if len(extIntOpts) > 1 {
// options = extIntOpts[1..]
// }
// evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options))
// } else {
// evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
// }
panic("No supported ewasm interpreter yet.")
}
// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
// as we always want to have the built-in EVM as the failover option.
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
evm.interpreter = evm.interpreters[0]
return evm return evm
} }
@ -132,6 +169,11 @@ func (evm *EVM) Cancel() {
atomic.StoreInt32(&evm.abort, 1) atomic.StoreInt32(&evm.abort, 1)
} }
// Interpreter returns the current interpreter
func (evm *EVM) Interpreter() Interpreter {
return evm.interpreter
}
// Call executes the contract associated with the addr with the given input as // Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes // 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 // the necessary steps to create accounts and reverses the state in case of an
@ -160,17 +202,22 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
precompiles = PrecompiledContractsByzantium precompiles = PrecompiledContractsByzantium
} }
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 { if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
}
return nil, gas, nil return nil, gas, nil
} }
evm.StateDB.CreateAccount(addr) evm.StateDB.CreateAccount(addr)
} }
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, value, gas) contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// Even if the account has no code, we need to continue because it might be a precompile
start := time.Now() start := time.Now()
// Capture the tracer start/end events in debug mode // Capture the tracer start/end events in debug mode
@ -181,7 +228,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}() }()
} }
ret, err = run(evm, contract, input) ret, err = run(evm, contract, input, false)
// When an error was returned by the EVM or when setting the creation code // 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 // above we revert to the snapshot and consume any gas remaining. Additionally
@ -220,13 +267,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
snapshot = evm.StateDB.Snapshot() snapshot = evm.StateDB.Snapshot()
to = AccountRef(caller.Address()) to = AccountRef(caller.Address())
) )
// initialise a new contract and set the code that is to be used by the // Initialise a new contract and set the code that is to be used by the EVM.
// EVM. The contract is a scoped environment for this execution context // The contract is a scoped environment for this execution context only.
// only.
contract := NewContract(caller, to, value, gas) contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
ret, err = run(evm, contract, input) ret, err = run(evm, contract, input, false)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != errExecutionReverted {
@ -259,7 +305,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
contract := NewContract(caller, to, nil, gas).AsDelegate() contract := NewContract(caller, to, nil, gas).AsDelegate()
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
ret, err = run(evm, contract, input) ret, err = run(evm, contract, input, false)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != errExecutionReverted {
@ -281,28 +327,26 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
// Make sure the readonly is only set if we aren't in readonly yet
// this makes also sure that the readonly flag isn't removed for
// child calls.
if !evm.interpreter.readOnly {
evm.interpreter.readOnly = true
defer func() { evm.interpreter.readOnly = false }()
}
var ( var (
to = AccountRef(addr) to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot() snapshot = evm.StateDB.Snapshot()
) )
// Initialise a new contract and set the code that is to be used by the // Initialise a new contract and set the code that is to be used by the EVM.
// EVM. The contract is a scoped environment for this execution context // The contract is a scoped environment for this execution context only.
// only.
contract := NewContract(caller, to, new(big.Int), gas) contract := NewContract(caller, to, new(big.Int), gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// 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,
// but is the correct thing to do and matters on other networks, in tests, and potential
// future scenarios
evm.StateDB.AddBalance(addr, bigZero)
// When an error was returned by the EVM or when setting the creation code // 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 // 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. // when we're in Homestead this also counts for code storage gas errors.
ret, err = run(evm, contract, input) ret, err = run(evm, contract, input, true)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != errExecutionReverted {
@ -312,9 +356,20 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
return ret, contract.Gas, err return ret, contract.Gas, err
} }
// Create creates a new contract using code as deployment code. type codeAndHash struct {
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { code []byte
hash common.Hash
}
func (c *codeAndHash) Hash() common.Hash {
if c.hash == (common.Hash{}) {
c.hash = crypto.Keccak256Hash(c.code)
}
return c.hash
}
// 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) ([]byte, common.Address, uint64, error) {
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
@ -323,39 +378,37 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance return nil, common.Address{}, gas, ErrInsufficientBalance
} }
// Ensure there's no existing contract already at the designated address
nonce := evm.StateDB.GetNonce(caller.Address()) nonce := evm.StateDB.GetNonce(caller.Address())
evm.StateDB.SetNonce(caller.Address(), nonce+1) evm.StateDB.SetNonce(caller.Address(), nonce+1)
contractAddr = crypto.CreateAddress(caller.Address(), nonce) // Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(contractAddr) contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(contractAddr) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
return nil, common.Address{}, 0, ErrContractAddressCollision return nil, common.Address{}, 0, ErrContractAddressCollision
} }
// Create a new account on the state // Create a new account on the state
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
evm.StateDB.CreateAccount(contractAddr) evm.StateDB.CreateAccount(address)
if evm.ChainConfig().IsEIP158(evm.BlockNumber) { if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
evm.StateDB.SetNonce(contractAddr, 1) evm.StateDB.SetNonce(address, 1)
} }
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value) evm.Transfer(evm.StateDB, caller.Address(), address, value)
// initialise a new contract and set the code that is to be used by the // Initialise a new contract and set the code that is to be used by the EVM.
// EVM. The contract is a scoped environment for this execution context // The contract is a scoped environment for this execution context only.
// only. contract := NewContract(caller, AccountRef(address), value, gas)
contract := NewContract(caller, AccountRef(contractAddr), value, gas) contract.SetCodeOptionalHash(&address, codeAndHash)
contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, contractAddr, gas, nil return nil, address, gas, nil
} }
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value) evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value)
} }
start := time.Now() start := time.Now()
ret, err = run(evm, contract, nil) ret, err := run(evm, contract, nil, false)
// check whether the max code size has been exceeded // check whether the max code size has been exceeded
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
@ -366,7 +419,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if err == nil && !maxCodeSizeExceeded { if err == nil && !maxCodeSizeExceeded {
createDataGas := uint64(len(ret)) * params.CreateDataGas createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas) { if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(contractAddr, ret) evm.StateDB.SetCode(address, ret)
} else { } else {
err = ErrCodeStoreOutOfGas err = ErrCodeStoreOutOfGas
} }
@ -388,11 +441,25 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
} }
return ret, contractAddr, contract.Gas, err return ret, address, contract.Gas, err
}
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
}
// Create2 creates a new contract using code as deployment code.
//
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
codeAndHash := &codeAndHash{code: code}
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes())
return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
} }
// ChainConfig returns the environment's chain configuration // ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
// Interpreter returns the EVM interpreter
func (evm *EVM) Interpreter() *Interpreter { return evm.interpreter }

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// Gas costs
const ( const (
GasQuickStep uint64 = 2 GasQuickStep uint64 = 2
GasFastestStep uint64 = 3 GasFastestStep uint64 = 3
@ -29,10 +30,6 @@ const (
GasMidStep uint64 = 8 GasMidStep uint64 = 8
GasSlowStep uint64 = 10 GasSlowStep uint64 = 10
GasExtStep uint64 = 20 GasExtStep uint64 = 20
GasReturn uint64 = 0
GasStop uint64 = 0
GasContractByte uint64 = 200
) )
// calcGas returns the actual gas cost of the call. // calcGas returns the actual gas cost of the call.
@ -46,11 +43,11 @@ func callGas(gasTable params.GasTable, availableGas, base uint64, callCost *big.
// If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150 // If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150
// is smaller than the requested amount. Therefor we return the new gas instead // is smaller than the requested amount. Therefor we return the new gas instead
// of returning an error. // of returning an error.
if callCost.BitLen() > 64 || gas < callCost.Uint64() { if !callCost.IsUint64() || gas < callCost.Uint64() {
return gas, nil return gas, nil
} }
} }
if callCost.BitLen() > 64 { if !callCost.IsUint64() {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }

View file

@ -22,7 +22,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// memoryGasCosts calculates the quadratic gas for memory expansion. It does so // memoryGasCost calculates the quadratic gas for memory expansion. It does so
// only for the memory region that is expanded, not the total memory. // only for the memory region that is expanded, not the total memory.
func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
@ -57,12 +57,6 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
return 0, nil return 0, nil
} }
func constGasFunc(gas uint64) gasFunc {
return func(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gas, nil
}
}
func gasCallDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasCallDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
@ -117,24 +111,71 @@ func gasReturnDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *
func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var ( var (
y, x = stack.Back(1), stack.Back(0) y, x = stack.Back(1), stack.Back(0)
val = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
) )
// This checks for 3 scenario's and calculates gas accordingly // The legacy gas metering only takes into consideration the current state
// 1. From a zero-value address to a non-zero value (NEW VALUE) // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283)
// 2. From a non-zero value address to a zero-value address (DELETE) // OR Constantinople is not active
// 3. From a non-zero to a non-zero (CHANGE) if evm.chainRules.IsPetersburg || !evm.chainRules.IsConstantinople {
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { // This checks for 3 scenario's and calculates gas accordingly:
// 0 => non 0 //
return params.SstoreSetGas, nil // 1. From a zero-value address to a non-zero value (NEW VALUE)
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { // 2. From a non-zero value address to a zero-value address (DELETE)
evm.StateDB.AddRefund(params.SstoreRefundGas) // 3. From a non-zero to a non-zero (CHANGE)
switch {
return params.SstoreClearGas, nil case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0
} else { return params.SstoreSetGas, nil
// non 0 => non 0 (or 0 => 0) case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0
return params.SstoreResetGas, nil evm.StateDB.AddRefund(params.SstoreRefundGas)
return params.SstoreClearGas, nil
default: // non 0 => non 0 (or 0 => 0)
return params.SstoreResetGas, nil
}
} }
// The new gas metering is based on net gas costs (EIP-1283):
//
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
// 2. If current value does not equal new value
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context)
// 2.1.1. If original value is 0, 20000 gas is deducted.
// 2.1.2. Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter.
// 2.2. If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
// 2.2.1. If original value is not 0
// 2.2.1.1. If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0.
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
// 2.2.2. If original value equals new value (this storage slot is reset)
// 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
value := common.BigToHash(y)
if current == value { // noop (1)
return params.NetSstoreNoopGas, nil
}
original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x))
if original == current {
if original == (common.Hash{}) { // create slot (2.1.1)
return params.NetSstoreInitGas, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(params.NetSstoreClearRefund)
}
return params.NetSstoreCleanGas, nil // write existing slot (2.1.2)
}
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
evm.StateDB.SubRefund(params.NetSstoreClearRefund)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
evm.StateDB.AddRefund(params.NetSstoreClearRefund)
}
}
if original == value {
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
evm.StateDB.AddRefund(params.NetSstoreResetClearRefund)
} else { // reset to original existing slot (2.2.2.2)
evm.StateDB.AddRefund(params.NetSstoreResetRefund)
}
}
return params.NetSstoreDirtyGas, nil
} }
func makeGasLog(n uint64) gasFunc { func makeGasLog(n uint64) gasFunc {
@ -241,6 +282,10 @@ func gasExtCodeCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *Sta
return gas, nil return gas, nil
} }
func gasExtCodeHash(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gt.ExtcodeHash, nil
}
func gasMLoad(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasMLoad(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var overflow bool var overflow bool
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
@ -289,6 +334,29 @@ func gasCreate(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m
return gas, nil return gas, nil
} }
func gasCreate2(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var overflow bool
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
if gas, overflow = math.SafeAdd(gas, params.Create2Gas); overflow {
return 0, errGasUintOverflow
}
wordGas, overflow := bigUint64(stack.Back(2))
if overflow {
return 0, errGasUintOverflow
}
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
return 0, errGasUintOverflow
}
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
return 0, errGasUintOverflow
}
return gas, nil
}
func gasBalance(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasBalance(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gt.Balance, nil return gt.Balance, nil
} }
@ -308,7 +376,7 @@ func gasExp(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem
gas = expByteLen * gt.ExpByte // no overflow check required. Max is 256 * ExpByte gas gas = expByteLen * gt.ExpByte // no overflow check required. Max is 256 * ExpByte gas
overflow bool overflow bool
) )
if gas, overflow = math.SafeAdd(gas, GasSlowStep); overflow { if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
return 0, errGasUintOverflow return 0, errGasUintOverflow
} }
return gas, nil return gas, nil
@ -447,15 +515,3 @@ func gasStaticCall(gt params.GasTable, evm *EVM, contract *Contract, stack *Stac
} }
return gas, nil return gas, nil
} }
func gasPush(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}
func gasSwap(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}
func gasDup(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}

View file

@ -13,20 +13,22 @@ import (
var _ = (*structLogMarshaling)(nil) var _ = (*structLogMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (s StructLog) MarshalJSON() ([]byte, error) { func (s StructLog) MarshalJSON() ([]byte, error) {
type StructLog struct { type StructLog struct {
Pc uint64 `json:"pc"` Pc uint64 `json:"pc"`
Op OpCode `json:"op"` Op OpCode `json:"op"`
Gas math.HexOrDecimal64 `json:"gas"` Gas math.HexOrDecimal64 `json:"gas"`
GasCost math.HexOrDecimal64 `json:"gasCost"` GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory"` Memory hexutil.Bytes `json:"memory"`
MemorySize int `json:"memSize"` MemorySize int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
Err error `json:"-"` RefundCounter uint64 `json:"refund"`
OpName string `json:"opName"` Err error `json:"-"`
ErrorString string `json:"error"` OpName string `json:"opName"`
ErrorString string `json:"error"`
} }
var enc StructLog var enc StructLog
enc.Pc = s.Pc enc.Pc = s.Pc
@ -43,24 +45,27 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
} }
enc.Storage = s.Storage enc.Storage = s.Storage
enc.Depth = s.Depth enc.Depth = s.Depth
enc.RefundCounter = s.RefundCounter
enc.Err = s.Err enc.Err = s.Err
enc.OpName = s.OpName() enc.OpName = s.OpName()
enc.ErrorString = s.ErrorString() enc.ErrorString = s.ErrorString()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON.
func (s *StructLog) UnmarshalJSON(input []byte) error { func (s *StructLog) UnmarshalJSON(input []byte) error {
type StructLog struct { type StructLog struct {
Pc *uint64 `json:"pc"` Pc *uint64 `json:"pc"`
Op *OpCode `json:"op"` Op *OpCode `json:"op"`
Gas *math.HexOrDecimal64 `json:"gas"` Gas *math.HexOrDecimal64 `json:"gas"`
GasCost *math.HexOrDecimal64 `json:"gasCost"` GasCost *math.HexOrDecimal64 `json:"gasCost"`
Memory *hexutil.Bytes `json:"memory"` Memory *hexutil.Bytes `json:"memory"`
MemorySize *int `json:"memSize"` MemorySize *int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []*math.HexOrDecimal256 `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"` Depth *int `json:"depth"`
Err error `json:"-"` RefundCounter *uint64 `json:"refund"`
Err error `json:"-"`
} }
var dec StructLog var dec StructLog
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -96,6 +101,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
if dec.Depth != nil { if dec.Depth != nil {
s.Depth = *dec.Depth s.Depth = *dec.Depth
} }
if dec.RefundCounter != nil {
s.RefundCounter = *dec.RefundCounter
}
if dec.Err != nil { if dec.Err != nil {
s.Err = dec.Err s.Err = dec.Err
} }

File diff suppressed because it is too large Load diff

View file

@ -17,10 +17,12 @@
package vm package vm
import ( import (
"bytes"
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -30,19 +32,23 @@ type twoOperandTest struct {
expected string expected string
} }
func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) { func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
pc = uint64(0) pc = uint64(0)
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
for i, test := range tests { for i, test := range tests {
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y)) shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected)) expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected))
stack.push(x) stack.push(x)
stack.push(shift) stack.push(shift)
opFn(&pc, env, nil, nil, stack) opFn(&pc, evmInterpreter, nil, nil, stack)
actual := stack.pop() actual := stack.pop()
if actual.Cmp(expected) != 0 { if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual) t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual)
@ -50,13 +56,13 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
// Check pool usage // Check pool usage
// 1.pool is not allowed to contain anything on the stack // 1.pool is not allowed to contain anything on the stack
// 2.pool is not allowed to contain the same pointers twice // 2.pool is not allowed to contain the same pointers twice
if env.interpreter.intPool.pool.len() > 0 { if evmInterpreter.intPool.pool.len() > 0 {
poolvals := make(map[*big.Int]struct{}) poolvals := make(map[*big.Int]struct{})
poolvals[actual] = struct{}{} poolvals[actual] = struct{}{}
for env.interpreter.intPool.pool.len() > 0 { for evmInterpreter.intPool.pool.len() > 0 {
key := env.interpreter.intPool.get() key := evmInterpreter.intPool.get()
if _, exist := poolvals[key]; exist { if _, exist := poolvals[key]; exist {
t.Errorf("Testcase %d, pool contains double-entry", i) t.Errorf("Testcase %d, pool contains double-entry", i)
} }
@ -64,13 +70,18 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
} }
} }
} }
poolOfIntPools.put(evmInterpreter.intPool)
} }
func TestByteOp(t *testing.T) { func TestByteOp(t *testing.T) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
tests := []struct { tests := []struct {
v string v string
th uint64 th uint64
@ -91,12 +102,13 @@ func TestByteOp(t *testing.T) {
th := new(big.Int).SetUint64(test.th) th := new(big.Int).SetUint64(test.th)
stack.push(val) stack.push(val)
stack.push(th) stack.push(th)
opByte(&pc, env, nil, nil, stack) opByte(&pc, evmInterpreter, nil, nil, stack)
actual := stack.pop() actual := stack.pop()
if actual.Cmp(test.expected) != 0 { if actual.Cmp(test.expected) != 0 {
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual) t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
} }
} }
poolOfIntPools.put(evmInterpreter.intPool)
} }
func TestSHL(t *testing.T) { func TestSHL(t *testing.T) {
@ -196,11 +208,15 @@ func TestSLT(t *testing.T) {
testTwoOperandOp(t, tests, opSlt) testTwoOperandOp(t, tests, opSlt)
} }
func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) { func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
// convert args // convert args
byteArgs := make([][]byte, len(args)) byteArgs := make([][]byte, len(args))
for i, arg := range args { for i, arg := range args {
@ -213,9 +229,10 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contr
a := new(big.Int).SetBytes(arg) a := new(big.Int).SetBytes(arg)
stack.push(a) stack.push(a)
} }
op(&pc, env, nil, nil, stack) op(&pc, evmInterpreter, nil, nil, stack)
stack.pop() stack.pop()
} }
poolOfIntPools.put(evmInterpreter.intPool)
} }
func BenchmarkOpAdd64(b *testing.B) { func BenchmarkOpAdd64(b *testing.B) {
@ -425,3 +442,148 @@ func BenchmarkOpIsZero(b *testing.B) {
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff" x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
opBenchmark(b, opIszero, x) opBenchmark(b, opIszero, x)
} }
func TestOpMstore(t *testing.T) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
)
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
mem.Resize(64)
pc := uint64(0)
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0))
opMstore(&pc, evmInterpreter, nil, mem, stack)
if got := common.Bytes2Hex(mem.Get(0, 32)); got != v {
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
}
stack.pushN(big.NewInt(0x1), big.NewInt(0))
opMstore(&pc, evmInterpreter, nil, mem, stack)
if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value")
}
poolOfIntPools.put(evmInterpreter.intPool)
}
func BenchmarkOpMstore(bench *testing.B) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
)
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
mem.Resize(64)
pc := uint64(0)
memStart := big.NewInt(0)
value := big.NewInt(0x1337)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
stack.pushN(value, memStart)
opMstore(&pc, evmInterpreter, nil, mem, stack)
}
poolOfIntPools.put(evmInterpreter.intPool)
}
func BenchmarkOpSHA3(bench *testing.B) {
var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack()
mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
)
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
mem.Resize(32)
pc := uint64(0)
start := big.NewInt(0)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
stack.pushN(big.NewInt(32), start)
opSha3(&pc, evmInterpreter, nil, mem, stack)
}
poolOfIntPools.put(evmInterpreter.intPool)
}
func TestCreate2Addreses(t *testing.T) {
type testcase struct {
origin string
salt string
code string
expected string
}
for i, tt := range []testcase{
{
origin: "0x0000000000000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0x00",
expected: "0x4d1a2e2bb4f88f0250f26ffff098b0b30b26bf38",
},
{
origin: "0xdeadbeef00000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0x00",
expected: "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3",
},
{
origin: "0xdeadbeef00000000000000000000000000000000",
salt: "0xfeed000000000000000000000000000000000000",
code: "0x00",
expected: "0xD04116cDd17beBE565EB2422F2497E06cC1C9833",
},
{
origin: "0x0000000000000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0xdeadbeef",
expected: "0x70f2b2914A2a4b783FaEFb75f459A580616Fcb5e",
},
{
origin: "0x00000000000000000000000000000000deadbeef",
salt: "0xcafebabe",
code: "0xdeadbeef",
expected: "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7",
},
{
origin: "0x00000000000000000000000000000000deadbeef",
salt: "0xcafebabe",
code: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
expected: "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C",
},
{
origin: "0x0000000000000000000000000000000000000000",
salt: "0x0000000000000000000000000000000000000000",
code: "0x",
expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0",
},
} {
origin := common.BytesToAddress(common.FromHex(tt.origin))
salt := common.BytesToHash(common.FromHex(tt.salt))
code := common.FromHex(tt.code)
codeHash := crypto.Keccak256(code)
address := crypto.CreateAddress2(origin, salt, codeHash)
/*
stack := newstack()
// salt, but we don't need that for this test
stack.push(big.NewInt(int64(len(code)))) //size
stack.push(big.NewInt(0)) // memstart
stack.push(big.NewInt(0)) // value
gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0)
fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String())
*/
expected := common.BytesToAddress(common.FromHex(tt.expected))
if !bytes.Equal(expected.Bytes(), address.Bytes()) {
t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String())
}
}
}

View file

@ -40,8 +40,10 @@ type StateDB interface {
GetCodeSize(common.Address) int GetCodeSize(common.Address) int
AddRefund(uint64) AddRefund(uint64)
SubRefund(uint64)
GetRefund() uint64 GetRefund() uint64
GetCommittedState(common.Address, common.Hash) common.Hash
GetState(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash) SetState(common.Address, common.Hash, common.Hash)
@ -64,7 +66,7 @@ type StateDB interface {
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool)
} }
// CallContext provides a basic interface for the EVM calling conventions. The EVM EVM // CallContext provides a basic interface for the EVM calling conventions. The EVM
// depends on this context being implemented for doing subcalls and initialising new EVM contracts. // depends on this context being implemented for doing subcalls and initialising new EVM contracts.
type CallContext interface { type CallContext interface {
// Call another contract // Call another contract

View file

@ -18,8 +18,10 @@ package vm
import ( import (
"fmt" "fmt"
"hash"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -39,24 +41,60 @@ type Config struct {
// may be left uninitialised and will be set to the default // may be left uninitialised and will be set to the default
// table. // table.
JumpTable [256]operation JumpTable [256]operation
// Type of the EWASM interpreter
EWASMInterpreter string
// Type of the EVM interpreter
EVMInterpreter string
} }
// Interpreter is used to run Ethereum based contracts and will utilise the // Interpreter is used to run Ethereum based contracts and will utilise the
// passed environment to query external sources for state information. // passed environment to query external sources for state information.
// The Interpreter will run the byte code VM based on the passed // The Interpreter will run the byte code VM based on the passed
// configuration. // configuration.
type Interpreter struct { 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)
// CanRun tells if the contract, passed as an argument, can be
// run by the current interpreter. This is meant so that the
// caller can do something like:
//
// ```golang
// for _, interpreter := range interpreters {
// if interpreter.CanRun(contract.code) {
// interpreter.Run(contract.code, input)
// }
// }
// ```
CanRun([]byte) bool
}
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
// Read to get a variable amount of data from the hash state. Read is faster than Sum
// because it doesn't copy the internal state, but also modifies the internal state.
type keccakState interface {
hash.Hash
Read([]byte) (int, error)
}
// EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct {
evm *EVM evm *EVM
cfg Config cfg Config
gasTable params.GasTable gasTable params.GasTable
intPool *intPool
intPool *intPool
hasher keccakState // Keccak256 hasher instance shared across opcodes
hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes
readOnly bool // Whether to throw on stateful modifications readOnly bool // Whether to throw on stateful modifications
returnData []byte // Last CALL's return data for subsequent reuse returnData []byte // Last CALL's return data for subsequent reuse
} }
// NewInterpreter returns a new instance of the Interpreter. // NewEVMInterpreter returns a new instance of the Interpreter.
func NewInterpreter(evm *EVM, cfg Config) *Interpreter { func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// We use the STOP instruction whether to see // We use the STOP instruction whether to see
// the jump table was initialised. If it was not // the jump table was initialised. If it was not
// we'll set the default jump table. // we'll set the default jump table.
@ -73,41 +111,39 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
} }
} }
return &Interpreter{ return &EVMInterpreter{
evm: evm, evm: evm,
cfg: cfg, cfg: cfg,
gasTable: evm.ChainConfig().GasTable(evm.BlockNumber), gasTable: evm.ChainConfig().GasTable(evm.BlockNumber),
intPool: newIntPool(),
} }
} }
func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
if in.evm.chainRules.IsByzantium {
if in.readOnly {
// If the interpreter is operating in readonly mode, make sure no
// state-modifying operation is performed. The 3rd stack item
// for a call operation is the value. Transferring value from one
// account to the others means the state is modified and should also
// return with an error.
if operation.writes || (op == CALL && stack.Back(2).BitLen() > 0) {
return errWriteProtection
}
}
}
return nil
}
// Run loops and evaluates the contract's code with the given input data and returns // 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. // the return byte-slice and an error if one occurred.
// //
// It's important to note that any errors returned by the interpreter should be // It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for // considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left. // errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) { func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
if in.intPool == nil {
in.intPool = poolOfIntPools.get()
defer func() {
poolOfIntPools.put(in.intPool)
in.intPool = nil
}()
}
// Increment the call depth which is restricted to 1024 // Increment the call depth which is restricted to 1024
in.evm.depth++ in.evm.depth++
defer func() { in.evm.depth-- }() defer func() { in.evm.depth-- }()
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This makes also sure that the readOnly flag isn't removed for child calls.
if readOnly && !in.readOnly {
in.readOnly = true
defer func() { in.readOnly = false }()
}
// Reset the previous call's return data. It's unimportant to preserve the old buffer // Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway. // as every returning call will return new data anyway.
in.returnData = nil in.returnData = nil
@ -130,9 +166,13 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
pcCopy uint64 // needed for the deferred Tracer pcCopy uint64 // needed for the deferred Tracer
gasCopy uint64 // for Tracer to log gas remaining before execution gasCopy uint64 // for Tracer to log gas remaining before execution
logged bool // deferred Tracer should ignore already logged steps logged bool // deferred Tracer should ignore already logged steps
res []byte // result of the opcode execution function
) )
contract.Input = input contract.Input = input
// Reclaim the stack as an int pool when the execution stops
defer func() { in.intPool.put(stack.data...) }()
if in.cfg.Debug { if in.cfg.Debug {
defer func() { defer func() {
if err != nil { if err != nil {
@ -161,19 +201,35 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
if !operation.valid { if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
} }
if err := operation.validateStack(stack); err != nil { // Validate stack
return nil, err if sLen := stack.len(); sLen < operation.minStack {
return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack)
} else if sLen > operation.maxStack {
return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack)
} }
// If the operation is valid, enforce and write restrictions // If the operation is valid, enforce and write restrictions
if err := in.enforceRestrictions(op, operation, stack); err != nil { if in.readOnly && in.evm.chainRules.IsByzantium {
return nil, err // If the interpreter is operating in readonly mode, make sure no
// state-modifying operation is performed. The 3rd stack item
// for a call operation is the value. Transferring value from one
// account to the others means the state is modified and should also
// return with an error.
if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
return nil, errWriteProtection
}
}
// Static portion of gas
if !contract.UseGas(operation.constantGas) {
return nil, ErrOutOfGas
} }
var memorySize uint64 var memorySize uint64
// calculate the new memory size and expand the memory to fit // calculate the new memory size and expand the memory to fit
// the operation // the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil { if operation.memorySize != nil {
memSize, overflow := bigUint64(operation.memorySize(stack)) memSize, overflow := operation.memorySize(stack)
if overflow { if overflow {
return nil, errGasUintOverflow return nil, errGasUintOverflow
} }
@ -183,11 +239,14 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
return nil, errGasUintOverflow return nil, errGasUintOverflow
} }
} }
// Dynamic portion of gas
// consume the gas and return an error if not enough gas is available. // consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost // cost is explicitly set so that the capture state defer method can get the proper cost
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize) if operation.dynamicGas != nil {
if err != nil || !contract.UseGas(cost) { cost, err = operation.dynamicGas(in.gasTable, in.evm, contract, stack, mem, memorySize)
return nil, ErrOutOfGas if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
@ -199,7 +258,7 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
} }
// execute the operation // execute the operation
res, err := operation.execute(&pc, in.evm, contract, mem, stack) res, err = operation.execute(&pc, in, contract, mem, stack)
// verifyPool is a build flag. Pool verification makes sure the integrity // verifyPool is a build flag. Pool verification makes sure the integrity
// of the integer pool by comparing values to a default value. // of the integer pool by comparing values to a default value.
if verifyPool { if verifyPool {
@ -224,3 +283,9 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
} }
return nil, nil return nil, nil
} }
// CanRun tells if the contract, passed as an argument, can be
// run by the current interpreter.
func (in *EVMInterpreter) CanRun(code []byte) bool {
return true
}

View file

@ -16,7 +16,10 @@
package vm package vm
import "math/big" import (
"math/big"
"sync"
)
var checkVal = big.NewInt(-42) var checkVal = big.NewInt(-42)
@ -65,3 +68,39 @@ func (p *intPool) put(is ...*big.Int) {
p.pool.push(i) p.pool.push(i)
} }
} }
// The intPool pool's default capacity
const poolDefaultCap = 25
// intPoolPool manages a pool of intPools.
type intPoolPool struct {
pools []*intPool
lock sync.Mutex
}
var poolOfIntPools = &intPoolPool{
pools: make([]*intPool, 0, poolDefaultCap),
}
// get is looking for an available pool to return.
func (ipp *intPoolPool) get() *intPool {
ipp.lock.Lock()
defer ipp.lock.Unlock()
if len(poolOfIntPools.pools) > 0 {
ip := ipp.pools[len(ipp.pools)-1]
ipp.pools = ipp.pools[:len(ipp.pools)-1]
return ip
}
return newIntPool()
}
// put a pool that has been allocated with get.
func (ipp *intPoolPool) put(ip *intPool) {
ipp.lock.Lock()
defer ipp.lock.Unlock()
if len(ipp.pools) < cap(ipp.pools) {
ipp.pools = append(ipp.pools, ip)
}
}

55
core/vm/intpool_test.go Normal file
View file

@ -0,0 +1,55 @@
// Copyright 2018 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 <http://www.gnu.org/licenses/>.
package vm
import (
"testing"
)
func TestIntPoolPoolGet(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
if nip == nil {
t.Fatalf("Invalid pool allocation")
}
}
func TestIntPoolPoolPut(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
if len(poolOfIntPools.pools) != 0 {
t.Fatalf("Pool got added to list when none should have been")
}
poolOfIntPools.put(nip)
if len(poolOfIntPools.pools) == 0 {
t.Fatalf("Pool did not get added to list when one should have been")
}
}
func TestIntPoolPoolReUse(t *testing.T) {
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
nip := poolOfIntPools.get()
poolOfIntPools.put(nip)
poolOfIntPools.get()
if len(poolOfIntPools.pools) != 0 {
t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0)
}
}

File diff suppressed because it is too large Load diff

View file

@ -29,11 +29,13 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
// Storage represents a contract's storage.
type Storage map[common.Hash]common.Hash type Storage map[common.Hash]common.Hash
func (self Storage) Copy() Storage { // Copy duplicates the current storage.
func (s Storage) Copy() Storage {
cpy := make(Storage) cpy := make(Storage)
for key, value := range self { for key, value := range s {
cpy[key] = value cpy[key] = value
} }
@ -45,6 +47,7 @@ type LogConfig struct {
DisableMemory bool // disable memory capture DisableMemory bool // disable memory capture
DisableStack bool // disable stack capture DisableStack bool // disable stack capture
DisableStorage bool // disable storage capture DisableStorage bool // disable storage capture
Debug bool // print output during capture end
Limit int // maximum length of output, but zero means unlimited Limit int // maximum length of output, but zero means unlimited
} }
@ -53,16 +56,17 @@ type LogConfig struct {
// StructLog is emitted to the EVM each cycle and lists information about the current internal state // StructLog is emitted to the EVM each cycle and lists information about the current internal state
// prior to the execution of the statement. // prior to the execution of the statement.
type StructLog struct { type StructLog struct {
Pc uint64 `json:"pc"` Pc uint64 `json:"pc"`
Op OpCode `json:"op"` Op OpCode `json:"op"`
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"` GasCost uint64 `json:"gasCost"`
Memory []byte `json:"memory"` Memory []byte `json:"memory"`
MemorySize int `json:"memSize"` MemorySize int `json:"memSize"`
Stack []*big.Int `json:"stack"` Stack []*big.Int `json:"stack"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
Err error `json:"-"` RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
} }
// overrides for gencodec // overrides for gencodec
@ -75,10 +79,12 @@ type structLogMarshaling struct {
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
} }
// OpName formats the operand name in a human-readable format.
func (s *StructLog) OpName() string { func (s *StructLog) OpName() string {
return s.Op.String() return s.Op.String()
} }
// ErrorString formats the log's error as a string.
func (s *StructLog) ErrorString() string { func (s *StructLog) ErrorString() string {
if s.Err != nil { if s.Err != nil {
return s.Err.Error() return s.Err.Error()
@ -123,6 +129,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
return logger return logger
} }
// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
return nil return nil
} }
@ -171,19 +178,28 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
storage = l.changedValues[contract.Address()].Copy() storage = l.changedValues[contract.Address()].Copy()
} }
// create a new snaptshot of the EVM. // create a new snaptshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, err} log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
return nil return nil
} }
// CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode.
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil return nil
} }
// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
l.output = output l.output = output
l.err = err l.err = err
if l.cfg.Debug {
fmt.Printf("0x%x\n", output)
if err != nil {
fmt.Printf(" error: %v\n", err)
}
}
return nil return nil
} }

87
core/vm/logger_json.go Normal file
View file

@ -0,0 +1,87 @@
// Copyright 2017 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 <http://www.gnu.org/licenses/>.
package vm
import (
"encoding/json"
"io"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
)
type JSONLogger struct {
encoder *json.Encoder
cfg *LogConfig
}
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream.
func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
l := &JSONLogger{json.NewEncoder(writer), cfg}
if l.cfg == nil {
l.cfg = &LogConfig{}
}
return l
}
func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
return nil
}
// CaptureState outputs state information on the logger.
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
log := StructLog{
Pc: pc,
Op: op,
Gas: gas,
GasCost: cost,
MemorySize: memory.Len(),
Storage: nil,
Depth: depth,
RefundCounter: env.StateDB.GetRefund(),
Err: err,
}
if !l.cfg.DisableMemory {
log.Memory = memory.Data()
}
if !l.cfg.DisableStack {
log.Stack = stack.Data()
}
return l.encoder.Encode(log)
}
// CaptureFault outputs state information on the logger.
func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil
}
// CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
type endLog struct {
Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
Time time.Duration `json:"time"`
Err string `json:"error,omitempty"`
}
if err != nil {
return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()})
}
return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""})
}

View file

@ -21,6 +21,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -41,14 +42,15 @@ func (d *dummyContractRef) SetBalance(*big.Int) {}
func (d *dummyContractRef) SetNonce(uint64) {} func (d *dummyContractRef) SetNonce(uint64) {}
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
type dummyStateDB struct { type dummyStatedb struct {
NoopStateDB state.StateDB
ref *dummyContractRef
} }
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{})
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
mem = NewMemory() mem = NewMemory()
stack = newstack() stack = newstack()
@ -56,9 +58,7 @@ func TestStoreCapture(t *testing.T) {
) )
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
stack.push(big.NewInt(0)) stack.push(big.NewInt(0))
var index common.Hash var index common.Hash
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil) logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil)
if len(logger.changedValues[contract.Address()]) == 0 { if len(logger.changedValues[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()])) t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()]))

View file

@ -16,7 +16,12 @@
package vm package vm
import "fmt" import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common/math"
)
// Memory implements a simple memory model for the ethereum virtual machine. // Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct { type Memory struct {
@ -24,25 +29,39 @@ type Memory struct {
lastGasCost uint64 lastGasCost uint64
} }
// NewMemory returns a new memory model.
func NewMemory() *Memory { func NewMemory() *Memory {
return &Memory{} return &Memory{}
} }
// Set sets offset + size to value // Set sets offset + size to value
func (m *Memory) Set(offset, size uint64, value []byte) { func (m *Memory) Set(offset, size uint64, value []byte) {
// length of store may never be less than offset + size.
// The store should be resized PRIOR to setting the memory
if size > uint64(len(m.store)) {
panic("INVALID memory: store empty")
}
// It's possible the offset is greater than 0 and size equals 0. This is because // It's possible the offset is greater than 0 and size equals 0. This is because
// the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP) // the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP)
if size > 0 { if size > 0 {
// length of store may never be less than offset + size.
// The store should be resized PRIOR to setting the memory
if offset+size > uint64(len(m.store)) {
panic("invalid memory: store empty")
}
copy(m.store[offset:offset+size], value) copy(m.store[offset:offset+size], value)
} }
} }
// Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to
// 32 bytes.
func (m *Memory) Set32(offset uint64, val *big.Int) {
// length of store may never be less than offset + size.
// The store should be resized PRIOR to setting the memory
if offset+32 > uint64(len(m.store)) {
panic("invalid memory: store empty")
}
// Zero the memory area
copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
// Fill in relevant bits
math.ReadBits(val, m.store[offset:offset+32])
}
// Resize resizes the memory to size // Resize resizes the memory to size
func (m *Memory) Resize(size uint64) { func (m *Memory) Resize(size uint64) {
if uint64(m.Len()) < size { if uint64(m.Len()) < size {
@ -51,14 +70,14 @@ func (m *Memory) Resize(size uint64) {
} }
// Get returns offset + size as a new slice // Get returns offset + size as a new slice
func (self *Memory) Get(offset, size int64) (cpy []byte) { func (m *Memory) Get(offset, size int64) (cpy []byte) {
if size == 0 { if size == 0 {
return nil return nil
} }
if len(self.store) > int(offset) { if len(m.store) > int(offset) {
cpy = make([]byte, size) cpy = make([]byte, size)
copy(cpy, self.store[offset:offset+size]) copy(cpy, m.store[offset:offset+size])
return return
} }
@ -67,13 +86,13 @@ func (self *Memory) Get(offset, size int64) (cpy []byte) {
} }
// GetPtr returns the offset + size // GetPtr returns the offset + size
func (self *Memory) GetPtr(offset, size int64) []byte { func (m *Memory) GetPtr(offset, size int64) []byte {
if size == 0 { if size == 0 {
return nil return nil
} }
if len(self.store) > int(offset) { if len(m.store) > int(offset) {
return self.store[offset : offset+size] return m.store[offset : offset+size]
} }
return nil return nil
@ -89,6 +108,7 @@ func (m *Memory) Data() []byte {
return m.store return m.store
} }
// Print dumps the content of the memory.
func (m *Memory) Print() { func (m *Memory) Print() {
fmt.Printf("### mem %d bytes ###\n", len(m.store)) fmt.Printf("### mem %d bytes ###\n", len(m.store))
if len(m.store) > 0 { if len(m.store) > 0 {

View file

@ -16,84 +16,98 @@
package vm package vm
import ( func memorySha3(stack *Stack) (uint64, bool) {
"math/big" return calcMemSize64(stack.Back(0), stack.Back(1))
"github.com/ethereum/go-ethereum/common/math"
)
func memorySha3(stack *Stack) *big.Int {
return calcMemSize(stack.Back(0), stack.Back(1))
} }
func memoryCallDataCopy(stack *Stack) *big.Int { func memoryCallDataCopy(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), stack.Back(2)) return calcMemSize64(stack.Back(0), stack.Back(2))
} }
func memoryReturnDataCopy(stack *Stack) *big.Int { func memoryReturnDataCopy(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), stack.Back(2)) return calcMemSize64(stack.Back(0), stack.Back(2))
} }
func memoryCodeCopy(stack *Stack) *big.Int { func memoryCodeCopy(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), stack.Back(2)) return calcMemSize64(stack.Back(0), stack.Back(2))
} }
func memoryExtCodeCopy(stack *Stack) *big.Int { func memoryExtCodeCopy(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(1), stack.Back(3)) return calcMemSize64(stack.Back(1), stack.Back(3))
} }
func memoryMLoad(stack *Stack) *big.Int { func memoryMLoad(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), big.NewInt(32)) return calcMemSize64WithUint(stack.Back(0), 32)
} }
func memoryMStore8(stack *Stack) *big.Int { func memoryMStore8(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), big.NewInt(1)) return calcMemSize64WithUint(stack.Back(0), 1)
} }
func memoryMStore(stack *Stack) *big.Int { func memoryMStore(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), big.NewInt(32)) return calcMemSize64WithUint(stack.Back(0), 32)
} }
func memoryCreate(stack *Stack) *big.Int { func memoryCreate(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(1), stack.Back(2)) return calcMemSize64(stack.Back(1), stack.Back(2))
} }
func memoryCall(stack *Stack) *big.Int { func memoryCreate2(stack *Stack) (uint64, bool) {
x := calcMemSize(stack.Back(5), stack.Back(6)) return calcMemSize64(stack.Back(1), stack.Back(2))
y := calcMemSize(stack.Back(3), stack.Back(4))
return math.BigMax(x, y)
} }
func memoryCallCode(stack *Stack) *big.Int { func memoryCall(stack *Stack) (uint64, bool) {
x := calcMemSize(stack.Back(5), stack.Back(6)) x, overflow := calcMemSize64(stack.Back(5), stack.Back(6))
y := calcMemSize(stack.Back(3), stack.Back(4)) if overflow {
return 0, true
return math.BigMax(x, y) }
y, overflow := calcMemSize64(stack.Back(3), stack.Back(4))
if overflow {
return 0, true
}
if x > y {
return x, false
}
return y, false
} }
func memoryDelegateCall(stack *Stack) *big.Int { func memoryDelegateCall(stack *Stack) (uint64, bool) {
x := calcMemSize(stack.Back(4), stack.Back(5)) x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
y := calcMemSize(stack.Back(2), stack.Back(3)) if overflow {
return 0, true
return math.BigMax(x, y) }
y, overflow := calcMemSize64(stack.Back(2), stack.Back(3))
if overflow {
return 0, true
}
if x > y {
return x, false
}
return y, false
} }
func memoryStaticCall(stack *Stack) *big.Int { func memoryStaticCall(stack *Stack) (uint64, bool) {
x := calcMemSize(stack.Back(4), stack.Back(5)) x, overflow := calcMemSize64(stack.Back(4), stack.Back(5))
y := calcMemSize(stack.Back(2), stack.Back(3)) if overflow {
return 0, true
return math.BigMax(x, y) }
y, overflow := calcMemSize64(stack.Back(2), stack.Back(3))
if overflow {
return 0, true
}
if x > y {
return x, false
}
return y, false
} }
func memoryReturn(stack *Stack) *big.Int { func memoryReturn(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), stack.Back(1)) return calcMemSize64(stack.Back(0), stack.Back(1))
} }
func memoryRevert(stack *Stack) *big.Int { func memoryRevert(stack *Stack) (uint64, bool) {
return calcMemSize(stack.Back(0), stack.Back(1)) return calcMemSize64(stack.Back(0), stack.Back(1))
} }
func memoryLog(stack *Stack) *big.Int { func memoryLog(stack *Stack) (uint64, bool) {
mSize, mStart := stack.Back(1), stack.Back(0) return calcMemSize64(stack.Back(0), stack.Back(1))
return calcMemSize(mStart, mSize)
} }

View file

@ -23,6 +23,7 @@ import (
// OpCode is an EVM opcode // OpCode is an EVM opcode
type OpCode byte type OpCode byte
// IsPush specifies if an opcode is a PUSH opcode.
func (op OpCode) IsPush() bool { func (op OpCode) IsPush() bool {
switch op { switch op {
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
@ -31,12 +32,13 @@ func (op OpCode) IsPush() bool {
return false return false
} }
// IsStaticJump specifies if an opcode is JUMP.
func (op OpCode) IsStaticJump() bool { func (op OpCode) IsStaticJump() bool {
return op == JUMP return op == JUMP
} }
// 0x0 range - arithmetic ops.
const ( const (
// 0x0 range - arithmetic ops
STOP OpCode = iota STOP OpCode = iota
ADD ADD
MUL MUL
@ -51,6 +53,7 @@ const (
SIGNEXTEND SIGNEXTEND
) )
// 0x10 range - comparison ops.
const ( const (
LT OpCode = iota + 0x10 LT OpCode = iota + 0x10
GT GT
@ -70,8 +73,8 @@ const (
SHA3 = 0x20 SHA3 = 0x20
) )
// 0x30 range - closure state.
const ( const (
// 0x30 range - closure state
ADDRESS OpCode = 0x30 + iota ADDRESS OpCode = 0x30 + iota
BALANCE BALANCE
ORIGIN ORIGIN
@ -87,10 +90,11 @@ const (
EXTCODECOPY EXTCODECOPY
RETURNDATASIZE RETURNDATASIZE
RETURNDATACOPY RETURNDATACOPY
EXTCODEHASH
) )
// 0x40 range - block operations.
const ( const (
// 0x40 range - block operations
BLOCKHASH OpCode = 0x40 + iota BLOCKHASH OpCode = 0x40 + iota
COINBASE COINBASE
TIMESTAMP TIMESTAMP
@ -99,8 +103,8 @@ const (
GASLIMIT GASLIMIT
) )
// 0x50 range - 'storage' and execution.
const ( const (
// 0x50 range - 'storage' and execution
POP OpCode = 0x50 + iota POP OpCode = 0x50 + iota
MLOAD MLOAD
MSTORE MSTORE
@ -115,8 +119,8 @@ const (
JUMPDEST JUMPDEST
) )
// 0x60 range.
const ( const (
// 0x60 range
PUSH1 OpCode = 0x60 + iota PUSH1 OpCode = 0x60 + iota
PUSH2 PUSH2
PUSH3 PUSH3
@ -183,6 +187,7 @@ const (
SWAP16 SWAP16
) )
// 0xa0 range - logging ops.
const ( const (
LOG0 OpCode = 0xa0 + iota LOG0 OpCode = 0xa0 + iota
LOG1 LOG1
@ -191,29 +196,30 @@ const (
LOG4 LOG4
) )
// unofficial opcodes used for parsing // unofficial opcodes used for parsing.
const ( const (
PUSH OpCode = 0xb0 + iota PUSH OpCode = 0xb0 + iota
DUP DUP
SWAP SWAP
) )
// 0xf0 range - closures.
const ( const (
// 0xf0 range - closures
CREATE OpCode = 0xf0 + iota CREATE OpCode = 0xf0 + iota
CALL CALL
CALLCODE CALLCODE
RETURN RETURN
DELEGATECALL DELEGATECALL
CREATE2
STATICCALL = 0xfa STATICCALL = 0xfa
REVERT = 0xfd REVERT = 0xfd
SELFDESTRUCT = 0xff SELFDESTRUCT = 0xff
) )
// Since the opcodes aren't all in order we can't use a regular slice // Since the opcodes aren't all in order we can't use a regular slice.
var opCodeToString = map[OpCode]string{ var opCodeToString = map[OpCode]string{
// 0x0 range - arithmetic ops // 0x0 range - arithmetic ops.
STOP: "STOP", STOP: "STOP",
ADD: "ADD", ADD: "ADD",
MUL: "MUL", MUL: "MUL",
@ -232,7 +238,7 @@ var opCodeToString = map[OpCode]string{
ISZERO: "ISZERO", ISZERO: "ISZERO",
SIGNEXTEND: "SIGNEXTEND", SIGNEXTEND: "SIGNEXTEND",
// 0x10 range - bit ops // 0x10 range - bit ops.
AND: "AND", AND: "AND",
OR: "OR", OR: "OR",
XOR: "XOR", XOR: "XOR",
@ -243,10 +249,10 @@ var opCodeToString = map[OpCode]string{
ADDMOD: "ADDMOD", ADDMOD: "ADDMOD",
MULMOD: "MULMOD", MULMOD: "MULMOD",
// 0x20 range - crypto // 0x20 range - crypto.
SHA3: "SHA3", SHA3: "SHA3",
// 0x30 range - closure state // 0x30 range - closure state.
ADDRESS: "ADDRESS", ADDRESS: "ADDRESS",
BALANCE: "BALANCE", BALANCE: "BALANCE",
ORIGIN: "ORIGIN", ORIGIN: "ORIGIN",
@ -262,8 +268,9 @@ var opCodeToString = map[OpCode]string{
EXTCODECOPY: "EXTCODECOPY", EXTCODECOPY: "EXTCODECOPY",
RETURNDATASIZE: "RETURNDATASIZE", RETURNDATASIZE: "RETURNDATASIZE",
RETURNDATACOPY: "RETURNDATACOPY", RETURNDATACOPY: "RETURNDATACOPY",
EXTCODEHASH: "EXTCODEHASH",
// 0x40 range - block operations // 0x40 range - block operations.
BLOCKHASH: "BLOCKHASH", BLOCKHASH: "BLOCKHASH",
COINBASE: "COINBASE", COINBASE: "COINBASE",
TIMESTAMP: "TIMESTAMP", TIMESTAMP: "TIMESTAMP",
@ -271,7 +278,7 @@ var opCodeToString = map[OpCode]string{
DIFFICULTY: "DIFFICULTY", DIFFICULTY: "DIFFICULTY",
GASLIMIT: "GASLIMIT", GASLIMIT: "GASLIMIT",
// 0x50 range - 'storage' and execution // 0x50 range - 'storage' and execution.
POP: "POP", POP: "POP",
//DUP: "DUP", //DUP: "DUP",
//SWAP: "SWAP", //SWAP: "SWAP",
@ -287,7 +294,7 @@ var opCodeToString = map[OpCode]string{
GAS: "GAS", GAS: "GAS",
JUMPDEST: "JUMPDEST", JUMPDEST: "JUMPDEST",
// 0x60 range - push // 0x60 range - push.
PUSH1: "PUSH1", PUSH1: "PUSH1",
PUSH2: "PUSH2", PUSH2: "PUSH2",
PUSH3: "PUSH3", PUSH3: "PUSH3",
@ -360,12 +367,13 @@ var opCodeToString = map[OpCode]string{
LOG3: "LOG3", LOG3: "LOG3",
LOG4: "LOG4", LOG4: "LOG4",
// 0xf0 range // 0xf0 range.
CREATE: "CREATE", CREATE: "CREATE",
CALL: "CALL", CALL: "CALL",
RETURN: "RETURN", RETURN: "RETURN",
CALLCODE: "CALLCODE", CALLCODE: "CALLCODE",
DELEGATECALL: "DELEGATECALL", DELEGATECALL: "DELEGATECALL",
CREATE2: "CREATE2",
STATICCALL: "STATICCALL", STATICCALL: "STATICCALL",
REVERT: "REVERT", REVERT: "REVERT",
SELFDESTRUCT: "SELFDESTRUCT", SELFDESTRUCT: "SELFDESTRUCT",
@ -375,10 +383,10 @@ var opCodeToString = map[OpCode]string{
SWAP: "SWAP", SWAP: "SWAP",
} }
func (o OpCode) String() string { func (op OpCode) String() string {
str := opCodeToString[o] str := opCodeToString[op]
if len(str) == 0 { if len(str) == 0 {
return fmt.Sprintf("Missing opcode 0x%x", int(o)) return fmt.Sprintf("Missing opcode 0x%x", int(op))
} }
return str return str
@ -429,6 +437,7 @@ var stringToOp = map[string]OpCode{
"EXTCODECOPY": EXTCODECOPY, "EXTCODECOPY": EXTCODECOPY,
"RETURNDATASIZE": RETURNDATASIZE, "RETURNDATASIZE": RETURNDATASIZE,
"RETURNDATACOPY": RETURNDATACOPY, "RETURNDATACOPY": RETURNDATACOPY,
"EXTCODEHASH": EXTCODEHASH,
"BLOCKHASH": BLOCKHASH, "BLOCKHASH": BLOCKHASH,
"COINBASE": COINBASE, "COINBASE": COINBASE,
"TIMESTAMP": TIMESTAMP, "TIMESTAMP": TIMESTAMP,
@ -517,6 +526,7 @@ var stringToOp = map[string]OpCode{
"LOG3": LOG3, "LOG3": LOG3,
"LOG4": LOG4, "LOG4": LOG4,
"CREATE": CREATE, "CREATE": CREATE,
"CREATE2": CREATE2,
"CALL": CALL, "CALL": CALL,
"RETURN": RETURN, "RETURN": RETURN,
"CALLCODE": CALLCODE, "CALLCODE": CALLCODE,
@ -524,6 +534,7 @@ var stringToOp = map[string]OpCode{
"SELFDESTRUCT": SELFDESTRUCT, "SELFDESTRUCT": SELFDESTRUCT,
} }
// StringToOp finds the opcode whose name is stored in `str`.
func StringToOp(str string) OpCode { func StringToOp(str string) OpCode {
return stringToOp[str] return stringToOp[str]
} }

View file

@ -22,10 +22,10 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -52,7 +52,7 @@ type Config struct {
func setDefaults(cfg *Config) { func setDefaults(cfg *Config) {
if cfg.ChainConfig == nil { if cfg.ChainConfig == nil {
cfg.ChainConfig = &params.ChainConfig{ cfg.ChainConfig = &params.ChainConfig{
ChainId: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: new(big.Int), HomesteadBlock: new(big.Int),
DAOForkBlock: new(big.Int), DAOForkBlock: new(big.Int),
DAOForkSupport: false, DAOForkSupport: false,
@ -99,11 +99,10 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
setDefaults(cfg) setDefaults(cfg)
if cfg.State == nil { if cfg.State == nil {
db, _ := ethdb.NewMemDatabase() cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db))
} }
var ( var (
address = common.StringToAddress("contract") address = common.BytesToAddress([]byte("contract"))
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
) )
@ -113,7 +112,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
// Call the code with the given configuration. // Call the code with the given configuration.
ret, _, err := vmenv.Call( ret, _, err := vmenv.Call(
sender, sender,
common.StringToAddress("contract"), common.BytesToAddress([]byte("contract")),
input, input,
cfg.GasLimit, cfg.GasLimit,
cfg.Value, cfg.Value,
@ -130,8 +129,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
setDefaults(cfg) setDefaults(cfg)
if cfg.State == nil { if cfg.State == nil {
db, _ := ethdb.NewMemDatabase() cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db))
} }
var ( var (
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)

View file

@ -23,9 +23,10 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params"
) )
func TestDefaults(t *testing.T) { func TestDefaults(t *testing.T) {
@ -94,8 +95,7 @@ func TestExecute(t *testing.T) {
} }
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
db, _ := ethdb.NewMemDatabase() state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
state, _ := state.New(common.Hash{}, state.NewDatabase(db))
address := common.HexToAddress("0x0a") address := common.HexToAddress("0x0a")
state.SetCode(address, []byte{ state.SetCode(address, []byte{
byte(vm.PUSH1), 10, byte(vm.PUSH1), 10,
@ -149,3 +149,57 @@ func BenchmarkCall(b *testing.B) {
} }
} }
} }
func benchmarkEVM_Create(bench *testing.B, code string) {
var (
statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
sender = common.BytesToAddress([]byte("sender"))
receiver = common.BytesToAddress([]byte("receiver"))
)
statedb.CreateAccount(sender)
statedb.SetCode(receiver, common.FromHex(code))
runtimeConfig := Config{
Origin: sender,
State: statedb,
GasLimit: 10000000,
Difficulty: big.NewInt(0x200000),
Time: new(big.Int).SetUint64(0),
Coinbase: common.Address{},
BlockNumber: new(big.Int).SetUint64(1),
ChainConfig: &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: new(big.Int),
ByzantiumBlock: new(big.Int),
ConstantinopleBlock: new(big.Int),
DAOForkBlock: new(big.Int),
DAOForkSupport: false,
EIP150Block: new(big.Int),
EIP155Block: new(big.Int),
EIP158Block: new(big.Int),
},
EVMConfig: vm.Config{},
}
// Warm up the intpools and stuff
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
Call(receiver, []byte{}, &runtimeConfig)
}
bench.StopTimer()
}
func BenchmarkEVM_CREATE_500(bench *testing.B) {
// initcode size 500K, repeatedly calls CREATE and then modifies the mem contents
benchmarkEVM_Create(bench, "5b6207a120600080f0600152600056")
}
func BenchmarkEVM_CREATE2_500(bench *testing.B) {
// initcode size 500K, repeatedly calls CREATE2 and then modifies the mem contents
benchmarkEVM_Create(bench, "5b586207a120600080f5600152600056")
}
func BenchmarkEVM_CREATE_1200(bench *testing.B) {
// initcode size 1200K, repeatedly calls CREATE and then modifies the mem contents
benchmarkEVM_Create(bench, "5b62124f80600080f0600152600056")
}
func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
}

View file

@ -21,7 +21,7 @@ import (
"math/big" "math/big"
) )
// stack is an object for basic stack operations. Items popped to the stack are // Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly // expected to be changed and modified. stack does not take care of adding newly
// initialised objects. // initialised objects.
type Stack struct { type Stack struct {
@ -32,6 +32,7 @@ func newstack() *Stack {
return &Stack{data: make([]*big.Int, 0, 1024)} return &Stack{data: make([]*big.Int, 0, 1024)}
} }
// Data returns the underlying big.Int array.
func (st *Stack) Data() []*big.Int { func (st *Stack) Data() []*big.Int {
return st.data return st.data
} }
@ -80,6 +81,7 @@ func (st *Stack) require(n int) error {
return nil return nil
} }
// Print dumps the content of the stack
func (st *Stack) Print() { func (st *Stack) Print() {
fmt.Println("### stack ###") fmt.Println("### stack ###")
if len(st.data) > 0 { if len(st.data) > 0 {

View file

@ -17,28 +17,26 @@
package vm package vm
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
func makeStackFunc(pop, push int) stackValidationFunc { func minSwapStack(n int) int {
return func(stack *Stack) error { return minStack(n, n)
if err := stack.require(pop); err != nil { }
return err func maxSwapStack(n int) int {
} return maxStack(n, n)
if stack.len()+push-pop > int(params.StackLimit) {
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit)
}
return nil
}
} }
func makeDupStackFunc(n int) stackValidationFunc { func minDupStack(n int) int {
return makeStackFunc(n, n+1) return minStack(n, n+1)
}
func maxDupStack(n int) int {
return maxStack(n, n+1)
} }
func makeSwapStackFunc(n int) stackValidationFunc { func maxStack(pop, push int) int {
return makeStackFunc(n, n) return int(params.StackLimit) + pop - push
}
func minStack(pops, push int) int {
return pops
} }