This commit is contained in:
Jeffrey Wilcke 2016-10-12 18:37:26 +00:00 committed by GitHub
commit ebf1381954
52 changed files with 1543 additions and 1777 deletions

View file

@ -222,7 +222,14 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
from.SetBalance(common.MaxBig) from.SetBalance(common.MaxBig)
// Execute the call. // Execute the call.
msg := callmsg{call} msg := callmsg{call}
vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{})
backend := &core.EVMBackend{
GetHashFn: core.GetHashFn(block.ParentHash(), b.blockchain),
State: statedb,
}
context := core.ToEVMContext(chainConfig, msg, block.Header())
vmenv := vm.NewEnvironment(context, backend, chainConfig, chainConfig.VmConfig)
gaspool := new(core.GasPool).AddGas(common.MaxBig) gaspool := new(core.GasPool).AddGas(common.MaxBig)
ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
return ret, gasUsed, err return ret, gasUsed, err

View file

@ -28,7 +28,6 @@ import (
"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/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"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/ethdb"
@ -45,33 +44,55 @@ var (
Name: "debug", Name: "debug",
Usage: "output full trace logs", Usage: "output full trace logs",
} }
ForceJitFlag = cli.BoolFlag{
Name: "forcejit",
Usage: "forces jit compilation",
}
DisableJitFlag = cli.BoolFlag{
Name: "nojit",
Usage: "disabled jit compilation",
}
CodeFlag = cli.StringFlag{ CodeFlag = cli.StringFlag{
Name: "code", Name: "code",
Usage: "EVM code", Usage: "EVM code",
} }
GasFlag = cli.StringFlag{ GasFlag = cli.StringFlag{
Name: "gas", Name: "gas",
Usage: "gas limit for the evm", Usage: "set the gas limit for the EVM execution",
Value: "10000000000", Value: "10000000000",
} }
PriceFlag = cli.StringFlag{ PriceFlag = cli.StringFlag{
Name: "price", Name: "price",
Usage: "price set for the evm", Usage: "set the price for the EVM execution",
Value: "0", Value: "0",
} }
ValueFlag = cli.StringFlag{ ValueFlag = cli.StringFlag{
Name: "value", Name: "value",
Usage: "value set for the evm", Usage: "set the value for the EVM execution",
Value: "0", Value: "0",
} }
SenderFlag = cli.StringFlag{
Name: "origin",
Usage: "set the origin for the EVM execution",
Value: "0x",
}
CoinbaseFlag = cli.StringFlag{
Name: "coinbase",
Usage: "coinbase set for the evm",
Value: "0x",
}
BlockNumberFlag = cli.StringFlag{
Name: "blocknumber",
Usage: "set the block number for the EVM execution",
Value: "1",
}
BlockTimeFlag = cli.StringFlag{
Name: "blocktime",
Usage: "set the block time for the EVM execution",
Value: "1",
}
BlockDifficultyFlag = cli.StringFlag{
Name: "difficulty",
Usage: "set the block difficulty for the EVM execution",
Value: "1",
}
GasLimitFlag = cli.StringFlag{
Name: "gaslimit",
Usage: "sets the gas limit for the EVM execution",
Value: "1",
}
DumpFlag = cli.BoolFlag{ DumpFlag = cli.BoolFlag{
Name: "dump", Name: "dump",
Usage: "dumps the state after the run", Usage: "dumps the state after the run",
@ -99,8 +120,6 @@ func init() {
CreateFlag, CreateFlag,
DebugFlag, DebugFlag,
VerbosityFlag, VerbosityFlag,
ForceJitFlag,
DisableJitFlag,
SysStatFlag, SysStatFlag,
CodeFlag, CodeFlag,
GasFlag, GasFlag,
@ -108,6 +127,11 @@ func init() {
ValueFlag, ValueFlag,
DumpFlag, DumpFlag,
InputFlag, InputFlag,
GasLimitFlag,
CoinbaseFlag,
SenderFlag,
BlockNumberFlag,
BlockTimeFlag,
} }
app.Action = run app.Action = run
} }
@ -117,23 +141,48 @@ func run(ctx *cli.Context) error {
glog.SetV(ctx.GlobalInt(VerbosityFlag.Name)) glog.SetV(ctx.GlobalInt(VerbosityFlag.Name))
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, db)
sender := statedb.CreateAccount(common.StringToAddress("sender"))
logger := vm.NewStructLogger(nil) logger := vm.NewStructLogger(nil)
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), vm.Config{ db, err := ethdb.NewMemDatabase()
Debug: ctx.GlobalBool(DebugFlag.Name), if err != nil {
ForceJit: ctx.GlobalBool(ForceJitFlag.Name), panic(err)
EnableJit: !ctx.GlobalBool(DisableJitFlag.Name), }
Tracer: logger, st, err := state.New(common.Hash{}, db)
}) if err != nil {
panic(err)
}
backend := &core.EVMBackend{
GetHashFn: func(n uint64) common.Hash {
return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
},
State: st,
}
context := vm.Context{
CallContext: core.EVMCallContext{core.CanTransfer, core.Transfer},
Origin: common.StringToAddress(ctx.GlobalString(SenderFlag.Name)),
Coinbase: common.StringToAddress(ctx.GlobalString(CoinbaseFlag.Name)),
BlockNumber: common.String2Big(ctx.GlobalString(BlockNumberFlag.Name)),
Time: common.String2Big(ctx.GlobalString(BlockTimeFlag.Name)),
Difficulty: common.String2Big(ctx.GlobalString(BlockDifficultyFlag.Name)),
GasLimit: common.String2Big(ctx.GlobalString(GasLimitFlag.Name)),
GasPrice: common.String2Big(ctx.GlobalString(PriceFlag.Name)),
}
tstart := time.Now() vmenv := vm.NewEnvironment(
context,
backend,
ruleSet{},
vm.Config{
Debug: ctx.GlobalBool(DebugFlag.Name),
Tracer: logger,
},
)
var ( var (
ret []byte ret []byte
err error tstart = time.Now()
sender = st.CreateAccount(context.Origin)
) )
if ctx.GlobalBool(CreateFlag.Name) { if ctx.GlobalBool(CreateFlag.Name) {
@ -142,12 +191,10 @@ func run(ctx *cli.Context) error {
sender, sender,
input, input,
common.Big(ctx.GlobalString(GasFlag.Name)), common.Big(ctx.GlobalString(GasFlag.Name)),
common.Big(ctx.GlobalString(PriceFlag.Name)),
common.Big(ctx.GlobalString(ValueFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)),
) )
} else { } else {
receiver := statedb.CreateAccount(common.StringToAddress("receiver")) receiver := st.CreateAccount(common.StringToAddress("receiver"))
code := common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)) code := common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name))
receiver.SetCode(crypto.Keccak256Hash(code), code) receiver.SetCode(crypto.Keccak256Hash(code), code)
ret, err = vmenv.Call( ret, err = vmenv.Call(
@ -155,15 +202,13 @@ func run(ctx *cli.Context) error {
receiver.Address(), receiver.Address(),
common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
common.Big(ctx.GlobalString(GasFlag.Name)), common.Big(ctx.GlobalString(GasFlag.Name)),
common.Big(ctx.GlobalString(PriceFlag.Name)),
common.Big(ctx.GlobalString(ValueFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)),
) )
} }
vmdone := time.Since(tstart) vmdone := time.Since(tstart)
if ctx.GlobalBool(DumpFlag.Name) { if ctx.GlobalBool(DumpFlag.Name) {
statedb.Commit() fmt.Println(string(st.Dump()))
fmt.Println(string(statedb.Dump()))
} }
vm.StdErrFormat(logger.StructLogs()) vm.StdErrFormat(logger.StructLogs())
@ -195,83 +240,7 @@ func main() {
} }
} }
type VMEnv struct {
state *state.StateDB
block *types.Block
transactor *common.Address
value *big.Int
depth int
Gas *big.Int
time *big.Int
logs []vm.StructLog
evm *vm.EVM
}
func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int, cfg vm.Config) *VMEnv {
env := &VMEnv{
state: state,
transactor: &transactor,
value: value,
time: big.NewInt(time.Now().Unix()),
}
env.evm = vm.New(env, cfg)
return env
}
// ruleSet implements vm.RuleSet and will always default to the homestead rule set. // ruleSet implements vm.RuleSet and will always default to the homestead rule set.
type ruleSet struct{} type ruleSet struct{}
func (ruleSet) IsHomestead(*big.Int) bool { return true } func (ruleSet) IsHomestead(*big.Int) bool { return true }
func (self *VMEnv) RuleSet() vm.RuleSet { return ruleSet{} }
func (self *VMEnv) Vm() vm.Vm { return self.evm }
func (self *VMEnv) Db() vm.Database { return self.state }
func (self *VMEnv) SnapshotDatabase() int { return self.state.Snapshot() }
func (self *VMEnv) RevertToSnapshot(snap int) { self.state.RevertToSnapshot(snap) }
func (self *VMEnv) Origin() common.Address { return *self.transactor }
func (self *VMEnv) BlockNumber() *big.Int { return common.Big0 }
func (self *VMEnv) Coinbase() common.Address { return *self.transactor }
func (self *VMEnv) Time() *big.Int { return self.time }
func (self *VMEnv) Difficulty() *big.Int { return common.Big1 }
func (self *VMEnv) BlockHash() []byte { return make([]byte, 32) }
func (self *VMEnv) Value() *big.Int { return self.value }
func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) }
func (self *VMEnv) VmType() vm.Type { return vm.StdVmTy }
func (self *VMEnv) Depth() int { return 0 }
func (self *VMEnv) SetDepth(i int) { self.depth = i }
func (self *VMEnv) GetHash(n uint64) common.Hash {
if self.block.Number().Cmp(big.NewInt(int64(n))) == 0 {
return self.block.Hash()
}
return common.Hash{}
}
func (self *VMEnv) AddLog(log *vm.Log) {
self.state.AddLog(log)
}
func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
return self.state.GetBalance(from).Cmp(balance) >= 0
}
func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
core.Transfer(from, to, amount)
}
func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
self.Gas = gas
return core.Call(self, caller, addr, data, gas, price, value)
}
func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return core.CallCode(self, caller, addr, data, gas, price, value)
}
func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return core.DelegateCall(self, caller, addr, data, gas, price)
}
func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return core.Create(self, caller, data, gas, price, value)
}

View file

@ -171,9 +171,7 @@ participating.
utils.WhisperEnabledFlag, utils.WhisperEnabledFlag,
utils.DevModeFlag, utils.DevModeFlag,
utils.TestNetFlag, utils.TestNetFlag,
utils.VMForceJitFlag, utils.VMCacheFlag,
utils.VMJitCacheFlag,
utils.VMEnableJitFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.RPCCORSDomainFlag, utils.RPCCORSDomainFlag,
utils.MetricsEnabledFlag, utils.MetricsEnabledFlag,

View file

@ -144,9 +144,7 @@ var AppHelpFlagGroups = []flagGroup{
{ {
Name: "VIRTUAL MACHINE", Name: "VIRTUAL MACHINE",
Flags: []cli.Flag{ Flags: []cli.Flag{
utils.VMEnableJitFlag, utils.VMCacheFlag,
utils.VMForceJitFlag,
utils.VMJitCacheFlag,
}, },
}, },
{ {

View file

@ -22,19 +22,18 @@ import (
"io/ioutil" "io/ioutil"
"math" "math"
"math/big" "math/big"
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"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/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -213,18 +212,10 @@ var (
Value: "", Value: "",
} }
VMForceJitFlag = cli.BoolFlag{ VMCacheFlag = cli.IntFlag{
Name: "forcejit", Name: "vmcache",
Usage: "Force the JIT VM to take precedence", Usage: "Amount of cached VM programs",
} Value: 1024,
VMJitCacheFlag = cli.IntFlag{
Name: "jitcache",
Usage: "Amount of cached JIT VM programs",
Value: 64,
}
VMEnableJitFlag = cli.BoolFlag{
Name: "jitvm",
Usage: "Enable the JIT VM",
} }
// logging and debug settings // logging and debug settings
@ -454,9 +445,6 @@ func makeNodeUserIdent(ctx *cli.Context) string {
if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 { if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 {
comps = append(comps, identity) comps = append(comps, identity)
} }
if ctx.GlobalBool(VMEnableJitFlag.Name) {
comps = append(comps, "JIT")
}
return strings.Join(comps, "/") return strings.Join(comps, "/")
} }
@ -636,6 +624,8 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name), WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)), WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
} }
vm.SetJITCacheSize(ctx.GlobalInt(VMCacheFlag.Name))
if ctx.GlobalBool(DevModeFlag.Name) { if ctx.GlobalBool(DevModeFlag.Name) {
if !ctx.GlobalIsSet(DataDirFlag.Name) { if !ctx.GlobalIsSet(DataDirFlag.Name) {
config.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode") config.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode")
@ -665,16 +655,6 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
Fatalf("The %v flags are mutually exclusive", netFlags) Fatalf("The %v flags are mutually exclusive", netFlags)
} }
// initialise new random number generator
rand := rand.New(rand.NewSource(time.Now().UnixNano()))
// get enabled jit flag
jitEnabled := ctx.GlobalBool(VMEnableJitFlag.Name)
// if the jit is not enabled enable it for 10 pct of the people
if !jitEnabled && rand.Float64() < 0.1 {
jitEnabled = true
glog.V(logger.Info).Infoln("You're one of the lucky few that will try out the JIT VM (random). If you get a consensus failure please be so kind to report this incident with the block hash that failed. You can switch to the regular VM by setting --jitvm=false")
}
ethConf := &eth.Config{ ethConf := &eth.Config{
Etherbase: MakeEtherbase(stack.AccountManager(), ctx), Etherbase: MakeEtherbase(stack.AccountManager(), ctx),
ChainConfig: MakeChainConfig(ctx, stack), ChainConfig: MakeChainConfig(ctx, stack),
@ -686,8 +666,6 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
ExtraData: MakeMinerExtra(extra, ctx), ExtraData: MakeMinerExtra(extra, ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
DocRoot: ctx.GlobalString(DocRootFlag.Name), DocRoot: ctx.GlobalString(DocRootFlag.Name),
EnableJit: jitEnabled,
ForceJit: ctx.GlobalBool(VMForceJitFlag.Name),
GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)),
GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)), GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)),
GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)), GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)),

View file

@ -16,7 +16,10 @@
package common package common
import "math/big" import (
"math"
"math/big"
)
// Common big integers often used // Common big integers often used
var ( var (
@ -33,6 +36,7 @@ var (
Big256 = big.NewInt(0xff) Big256 = big.NewInt(0xff)
Big257 = big.NewInt(257) Big257 = big.NewInt(257)
MaxBig = String2Big("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") MaxBig = String2Big("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
Max64Bit = new(big.Int).SetUint64(math.MaxUint64)
) )
// Big pow // Big pow

22
common/math/integer.go Normal file
View file

@ -0,0 +1,22 @@
package math
import gmath "math"
/*
* NOTE: The following methods need to be optimised using either bit checking or asm
*/
// IsMinSafe returns whether the subtraction is safe and does not overflow.
func IsSubSafe(x, y uint64) bool {
return x >= y
}
// IsAddSafe returns whether the addition is safe and does not overflow.
func IsAddSafe(x, y uint64) bool {
return y <= gmath.MaxUint64-x
}
// IsAddSafe returns whether the multiplication is safe and does not overflow.
func IsMulSafe(x, y uint64) bool {
return x == 0 || y == 0 || y <= gmath.MaxUint64/x
}

View file

@ -0,0 +1,50 @@
package math
import (
gmath "math"
"testing"
)
type operation byte
const (
sub operation = iota
add
mul
)
func TestIsAddSafe(t *testing.T) {
for i, test := range []struct {
x uint64
y uint64
safe bool
op operation
}{
// add operations
{gmath.MaxUint64, 1, false, add},
{gmath.MaxUint64 - 1, 1, true, add},
// sub operations
{0, 1, false, sub},
{0, 0, true, sub},
// mul operations
{10, 10, true, mul},
{gmath.MaxUint64, 2, false, mul},
{gmath.MaxUint64, 1, true, mul},
} {
var isSafe bool
switch test.op {
case sub:
isSafe = IsSubSafe(test.x, test.y)
case add:
isSafe = IsAddSafe(test.x, test.y)
case mul:
isSafe = IsMulSafe(test.x, test.y)
}
if test.safe != isSafe {
t.Errorf("%d failed. Expected test to be %v, got %v", i, test.safe, isSafe)
}
}
}

View file

@ -194,7 +194,16 @@ func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr
} }
header := be.bc.CurrentBlock().Header() header := be.bc.CurrentBlock().Header()
vmenv := core.NewEnv(statedb, be.config, be.bc, msg, header, vm.Config{})
backend := &core.EVMBackend{
GetHashFn: core.GetHashFn(header.ParentHash, be.bc),
State: statedb,
}
context := core.ToEVMContext(be.config, msg, header)
vmenv := vm.NewEnvironment(context, backend, be.config, vm.Config{})
//vmenv := vm.NewEnvironment(false, statedb, be.config, be.bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(common.MaxBig) gp := new(core.GasPool).AddGas(common.MaxBig)
res, gas, err := core.ApplyMessage(vmenv, msg, gp) res, gas, err := core.ApplyMessage(vmenv, msg, gp)

View file

@ -974,6 +974,12 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
} }
stats.processed++ stats.processed++
if time.Since(bstart) > 1200*time.Millisecond {
glog.Infof("#### inserted slow block ####")
glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart))
glog.Infof("#############################")
}
if glog.V(logger.Info) { if glog.V(logger.Info) {
stats.report(chain, i) stats.report(chain, i)
} }

96
core/evm.go Normal file
View file

@ -0,0 +1,96 @@
// Copyright 2014 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 core
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)
// ToEVMContext creates a new context for use in the EVM.
func ToEVMContext(config *ChainConfig, msg Message, header *types.Header) vm.Context {
var from common.Address
if config.IsHomestead(header.Number) {
from, _ = msg.From()
} else {
from, _ = msg.FromFrontier()
}
return vm.Context{
CallContext: EVMCallContext{CanTransfer, Transfer},
Origin: from,
Coinbase: header.Coinbase,
BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).Set(header.Time),
Difficulty: new(big.Int).Set(header.Difficulty),
GasLimit: new(big.Int).Set(header.GasLimit),
GasPrice: new(big.Int).Set(msg.GasPrice()),
}
}
// GetHashFn returns a function for which the VM env can query block hashes through
// up to the limit defined by the Yellow Paper and uses the given block chain
// to query for information.
func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash {
return func(n uint64) common.Hash {
for block := chain.GetBlockByHash(ref); block != nil; block = chain.GetBlock(block.ParentHash(), block.NumberU64()-1) {
if block.NumberU64() == n {
return block.Hash()
}
}
return common.Hash{}
}
}
// CanTransfer checks wether there are enough funds in the address' account to make a transfer.
func CanTransfer(db vm.Database, addr common.Address, amount *big.Int) bool {
return db.GetBalance(addr).Cmp(amount) >= 0
}
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.Database, sender, recipient common.Address, amount *big.Int) {
db.SubBalance(sender, amount)
db.AddBalance(recipient, amount)
}
// EVMBackend implements vm.Backend and provides an interface to keep track of the current
// state.
type EVMBackend struct {
GetHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes
State *state.StateDB
}
// MakeSnapshot returns a copy of the state.
func (b *EVMBackend) SnapshotDatabase() int {
return b.State.Snapshot()
}
// Get returns the state
func (b *EVMBackend) Get() vm.Database { return b.State }
// Set sets the current state
func (b *EVMBackend) RevertToSnapshot(revision int) { b.State.RevertToSnapshot(revision) }
// GetHash returns the canonical hash referenced by the depth
func (b *EVMBackend) GetHash(n uint64) common.Hash {
return b.GetHashFn(n)
}

View file

@ -25,31 +25,36 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
type EVMCallContext struct {
CanTransfer func(db vm.Database, addr common.Address, amount *big.Int) bool
Transfer func(db vm.Database, sender, recipient common.Address, amount *big.Int)
}
// Call executes within the given contract // Call executes within the given contract
func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { func (c EVMCallContext) Call(env *vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) {
ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value) ret, _, err = c.exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value)
return ret, err return ret, err
} }
// CallCode executes the given address' code as the given contract address // CallCode executes the given address' code as the given contract address
func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { func (c EVMCallContext) CallCode(env *vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) {
callerAddr := caller.Address() callerAddr := caller.Address()
ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value) ret, _, err = c.exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value)
return ret, err return ret, err
} }
// DelegateCall is equivalent to CallCode except that sender and value propagates from parent scope to child scope // DelegateCall is equivalent to CallCode except that sender and value propagates from parent scope to child scope
func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice *big.Int) (ret []byte, err error) { func (c EVMCallContext) DelegateCall(env *vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas *big.Int) (ret []byte, err error) {
callerAddr := caller.Address() callerAddr := caller.Address()
originAddr := env.Origin() originAddr := env.Origin
callerValue := caller.Value() callerValue := caller.Value()
ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, callerValue) ret, _, err = c.execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, callerValue)
return ret, err return ret, err
} }
// Create creates a new contract with the given code // Create creates a new contract with the given code
func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) { func (c EVMCallContext) Create(env *vm.Environment, caller vm.ContractRef, code []byte, gas, value *big.Int) (ret []byte, address common.Address, err error) {
ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, gasPrice, value) ret, address, err = c.exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, value)
// Here we get an error if we run into maximum stack depth, // Here we get an error if we run into maximum stack depth,
// See: https://github.com/ethereum/yellowpaper/pull/131 // See: https://github.com/ethereum/yellowpaper/pull/131
// and YP definitions for CREATE instruction // and YP definitions for CREATE instruction
@ -59,18 +64,18 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPric
return ret, address, err return ret, address, err
} }
func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { func (c EVMCallContext) exec(env *vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) {
evm := env.Vm() evm := env.Vm()
// 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 env.Depth() > int(params.CallCreateDepth.Int64()) { if env.Depth > int(params.CallCreateDepth.Int64()) {
caller.ReturnGas(gas, gasPrice) caller.ReturnGas(gas.Uint64())
return nil, common.Address{}, vm.DepthError return nil, common.Address{}, vm.DepthError
} }
if !env.CanTransfer(caller.Address(), value) { if !c.CanTransfer(env.Db(), caller.Address(), value) {
caller.ReturnGas(gas, gasPrice) caller.ReturnGas(gas.Uint64())
return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address()))
} }
@ -85,7 +90,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
createAccount = true createAccount = true
} }
snapshotPreTransfer := env.SnapshotDatabase() snapshotPreTransfer := env.Backend.SnapshotDatabase()
var ( var (
from = env.Db().GetAccount(caller.Address()) from = env.Db().GetAccount(caller.Address())
to vm.Account to vm.Account
@ -99,12 +104,12 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
to = env.Db().GetAccount(*address) to = env.Db().GetAccount(*address)
} }
} }
env.Transfer(from, to, value) c.Transfer(env.Db(), from.Address(), to.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. The contract is a scoped environment for this execution context // EVM. The contract is a scoped environment for this execution context
// only. // only.
contract := vm.NewContract(caller, to, value, gas, gasPrice) contract := vm.NewContract(caller, to, value, gas)
contract.SetCallCode(codeAddr, codeHash, code) contract.SetCallCode(codeAddr, codeHash, code)
defer contract.Finalise() defer contract.Finalise()
@ -116,7 +121,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
if err == nil && createAccount { if err == nil && createAccount {
dataGas := big.NewInt(int64(len(ret))) dataGas := big.NewInt(int64(len(ret)))
dataGas.Mul(dataGas, params.CreateDataGas) dataGas.Mul(dataGas, params.CreateDataGas)
if contract.UseGas(dataGas) { if contract.UseGas(dataGas.Uint64()) {
env.Db().SetCode(*address, ret) env.Db().SetCode(*address, ret)
} else { } else {
err = vm.CodeStoreOutOfGasError err = vm.CodeStoreOutOfGasError
@ -126,25 +131,25 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
// 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.
if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) { if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber) || err != vm.CodeStoreOutOfGasError) {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas())
env.RevertToSnapshot(snapshotPreTransfer) env.Backend.RevertToSnapshot(snapshotPreTransfer)
} }
return ret, addr, err return ret, addr, err
} }
func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { func (c EVMCallContext) execDelegateCall(env *vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) {
evm := env.Vm() evm := env.Vm()
// 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 env.Depth() > int(params.CallCreateDepth.Int64()) { if env.Depth > int(params.CallCreateDepth.Int64()) {
caller.ReturnGas(gas, gasPrice) caller.ReturnGas(gas.Uint64())
return nil, common.Address{}, vm.DepthError return nil, common.Address{}, vm.DepthError
} }
snapshot := env.SnapshotDatabase() snapshot := env.Backend.SnapshotDatabase()
var to vm.Account var to vm.Account
if !env.Db().Exist(*toAddr) { if !env.Db().Exist(*toAddr) {
@ -154,22 +159,16 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA
} }
// Iinitialise a new contract and make initialise the delegate values // Iinitialise a new contract and make initialise the delegate values
contract := vm.NewContract(caller, to, value, gas, gasPrice).AsDelegate() contract := vm.NewContract(caller, to, value, gas).AsDelegate()
contract.SetCallCode(codeAddr, codeHash, code) contract.SetCallCode(codeAddr, codeHash, code)
defer contract.Finalise() defer contract.Finalise()
ret, err = evm.Run(contract, input) ret, err = evm.Run(contract, input)
if err != nil { if err != nil {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas())
env.RevertToSnapshot(snapshot) env.Backend.RevertToSnapshot(snapshot)
} }
return ret, addr, err return ret, addr, err
} }
// generic transfer method
func Transfer(from, to vm.Account, amount *big.Int) {
from.SubBalance(amount)
to.AddBalance(amount)
}

View file

@ -67,6 +67,24 @@ func (self *StateDB) RawDump() Dump {
} }
dump.Accounts[common.Bytes2Hex(addr)] = account dump.Accounts[common.Bytes2Hex(addr)] = account
} }
for addr, stateObject := range self.stateObjects {
account := DumpAccount{
Balance: stateObject.Balance().String(),
Nonce: stateObject.Nonce(),
Root: common.Bytes2Hex(stateObject.data.Root[:]),
CodeHash: common.Bytes2Hex(stateObject.CodeHash()),
Code: common.Bytes2Hex(stateObject.Code(self.db)),
Storage: make(map[string]string),
}
storageIt := stateObject.getTrie(self.db).Iterator()
for storageIt.Next() {
account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value)
}
for storageAddr, value := range stateObject.cachedStorage {
account.Storage[common.Bytes2Hex(storageAddr[:])] = value.Hex()
}
dump.Accounts[common.Bytes2Hex(addr[:])] = account
}
return dump return dump
} }

View file

@ -260,7 +260,7 @@ func (self *StateObject) setBalance(amount *big.Int) {
} }
// Return the gas back to the origin. Used by the Virtual machine or Closures // Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *StateObject) ReturnGas(gas, price *big.Int) {} func (c *StateObject) ReturnGas(gas uint64) {}
func (self *StateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *StateObject { func (self *StateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *StateObject {
stateObject := newObject(db, self.address, self.data, onDirty) stateObject := newObject(db, self.address, self.data, onDirty)

View file

@ -259,6 +259,7 @@ func (self *StateDB) GetCodeSize(addr common.Address) int {
return size return size
} }
// GetCodeHash returns the hash of the code associated with the address.
func (self *StateDB) GetCodeHash(addr common.Address) common.Hash { func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
stateObject := self.GetStateObject(addr) stateObject := self.GetStateObject(addr)
if stateObject == nil { if stateObject == nil {
@ -294,6 +295,13 @@ func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
} }
} }
func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) {
stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SubBalance(amount)
}
}
func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) { func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) {
stateObject := self.GetOrNewStateObject(addr) stateObject := self.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {

View file

@ -90,7 +90,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// ApplyTransactions returns the generated receipts and vm logs during the // ApplyTransactions returns the generated receipts and vm logs during the
// execution of the state transition phase. // execution of the state transition phase.
func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) { func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) {
_, gas, err := ApplyMessage(NewEnv(statedb, config, bc, tx, header, cfg), tx, gp) backend := &EVMBackend{
GetHashFn: GetHashFn(header.ParentHash, bc),
State: statedb,
}
context := ToEVMContext(config, tx, header)
env := vm.NewEnvironment(context, backend, config, cfg)
_, gas, err := ApplyMessage(env, tx, gp)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -105,13 +113,12 @@ func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb
receipt.ContractAddress = crypto.CreateAddress(from, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(from, tx.Nonce())
} }
logs := statedb.GetLogs(tx.Hash()) receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Logs = logs
receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
glog.V(logger.Debug).Infoln(receipt) glog.V(logger.Debug).Infoln(receipt)
return receipt, logs, gas, err return receipt, receipt.Logs, gas, err
} }
// AccumulateRewards credits the coinbase of the given block with the // AccumulateRewards credits the coinbase of the given block with the

View file

@ -57,7 +57,7 @@ type StateTransition struct {
data []byte data []byte
state vm.Database state vm.Database
env vm.Environment env *vm.Environment
} }
// Message represents a message sent to a contract. // Message represents a message sent to a contract.
@ -106,7 +106,7 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int {
} }
// NewStateTransition initialises and returns a new state transition object. // NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTransition { func NewStateTransition(env *vm.Environment, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{ return &StateTransition{
gp: gp, gp: gp,
env: env, env: env,
@ -127,7 +127,7 @@ func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTran
// the gas used (which includes gas refunds) and an error if it failed. An error always // the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular // indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block. // state and would never be accepted within a block.
func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) { func ApplyMessage(env *vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
st := NewStateTransition(env, msg, gp) st := NewStateTransition(env, msg, gp)
ret, _, gasUsed, err := st.TransitionDb() ret, _, gasUsed, err := st.TransitionDb()
@ -139,7 +139,7 @@ func (self *StateTransition) from() (vm.Account, error) {
f common.Address f common.Address
err error err error
) )
if self.env.RuleSet().IsHomestead(self.env.BlockNumber()) { if self.env.RuleSet().IsHomestead(self.env.BlockNumber) {
f, err = self.msg.From() f, err = self.msg.From()
} else { } else {
f, err = self.msg.FromFrontier() f, err = self.msg.FromFrontier()
@ -234,7 +234,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
msg := self.msg msg := self.msg
sender, _ := self.from() // err checked in preCheck sender, _ := self.from() // err checked in preCheck
homestead := self.env.RuleSet().IsHomestead(self.env.BlockNumber()) homestead := self.env.RuleSet().IsHomestead(self.env.BlockNumber)
contractCreation := MessageCreatesContract(msg) contractCreation := MessageCreatesContract(msg)
// Pay intrinsic gas // Pay intrinsic gas
if err = self.useGas(IntrinsicGas(self.data, contractCreation, homestead)); err != nil { if err = self.useGas(IntrinsicGas(self.data, contractCreation, homestead)); err != nil {
@ -244,7 +244,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
vmenv := self.env vmenv := self.env
//var addr common.Address //var addr common.Address
if contractCreation { if contractCreation {
ret, _, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value) ret, _, err = vmenv.Create(sender, self.data, self.gas, self.value)
if homestead && err == vm.CodeStoreOutOfGasError { if homestead && err == vm.CodeStoreOutOfGasError {
self.gas = Big0 self.gas = Big0
} }
@ -256,7 +256,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
} else { } else {
// Increment the nonce for the next transaction // Increment the nonce for the next transaction
self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1) self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1)
ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value) ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.value)
if err != nil { if err != nil {
glog.V(logger.Core).Infoln("VM call err:", err) glog.V(logger.Core).Infoln("VM call err:", err)
} }
@ -274,7 +274,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
requiredGas = new(big.Int).Set(self.gasUsed()) requiredGas = new(big.Int).Set(self.gasUsed())
self.refundGas() self.refundGas()
self.state.AddBalance(self.env.Coinbase(), new(big.Int).Mul(self.gasUsed(), self.gasPrice)) self.env.Db().AddBalance(self.env.Coinbase, new(big.Int).Mul(self.gasUsed(), self.gasPrice))
return ret, requiredGas, self.gasUsed(), err return ret, requiredGas, self.gasUsed(), err
} }

View file

@ -21,7 +21,6 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
) )
// Type is the VM type accepted by **NewVm** // Type is the VM type accepted by **NewVm**
@ -45,41 +44,6 @@ var (
max = big.NewInt(math.MaxInt64) // Maximum 64 bit integer max = big.NewInt(math.MaxInt64) // Maximum 64 bit integer
) )
// calculates the memory size required for a step
func calcMemSize(off, l *big.Int) *big.Int {
if l.Cmp(common.Big0) == 0 {
return common.Big0
}
return new(big.Int).Add(off, l)
}
// calculates the quadratic gas
func quadMemGas(mem *Memory, newMemSize, gas *big.Int) {
if newMemSize.Cmp(common.Big0) > 0 {
newMemSizeWords := toWordSize(newMemSize)
newMemSize.Mul(newMemSizeWords, u256(32))
if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 {
// be careful reusing variables here when changing.
// The order has been optimised to reduce allocation
oldSize := toWordSize(big.NewInt(int64(mem.Len())))
pow := new(big.Int).Exp(oldSize, common.Big2, Zero)
linCoef := oldSize.Mul(oldSize, params.MemoryGas)
quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv)
oldTotalFee := new(big.Int).Add(linCoef, quadCoef)
pow.Exp(newMemSizeWords, common.Big2, Zero)
linCoef = linCoef.Mul(newMemSizeWords, params.MemoryGas)
quadCoef = quadCoef.Div(pow, params.QuadCoeffDiv)
newTotalFee := linCoef.Add(linCoef, quadCoef)
fee := newTotalFee.Sub(newTotalFee, oldTotalFee)
gas.Add(gas, fee)
}
}
}
// Simple helper // Simple helper
func u256(n int64) *big.Int { func u256(n int64) *big.Int {
return big.NewInt(n) return big.NewInt(n)

View file

@ -24,7 +24,7 @@ import (
// ContractRef is a reference to the contract's backing object // ContractRef is a reference to the contract's backing object
type ContractRef interface { type ContractRef interface {
ReturnGas(*big.Int, *big.Int) ReturnGas(uint64)
Address() common.Address Address() common.Address
Value() *big.Int Value() *big.Int
SetCode(common.Hash, []byte) SetCode(common.Hash, []byte)
@ -48,7 +48,8 @@ type Contract struct {
CodeAddr *common.Address CodeAddr *common.Address
Input []byte Input []byte
value, Gas, UsedGas, Price *big.Int value, gas *big.Int
gas64 uint64
Args []byte Args []byte
@ -56,7 +57,7 @@ type Contract struct {
} }
// 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, gas, price *big.Int) *Contract { func NewContract(caller ContractRef, object ContractRef, value, gas *big.Int) *Contract {
c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil} c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil}
if parent, ok := caller.(*Contract); ok { if parent, ok := caller.(*Contract); ok {
@ -68,12 +69,9 @@ func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.
// 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
// This pointer will be off the state transition // This pointer will be off the state transition
c.Gas = gas //new(big.Int).Set(gas) c.gas = gas //new(big.Int).Set(gas)
c.gas64 = gas.Uint64()
c.value = new(big.Int).Set(value) c.value = new(big.Int).Set(value)
// In most cases price and value are pointers to transaction objects
// and we don't want the transaction's values to change.
c.Price = new(big.Int).Set(price)
c.UsedGas = new(big.Int)
return c return c
} }
@ -102,6 +100,11 @@ func (c *Contract) GetByte(n uint64) byte {
return 0 return 0
} }
// Gas returns the current gas left
func (c *Contract) Gas() uint64 {
return c.gas64
}
// Caller returns the caller of the contract. // Caller returns the caller of the contract.
// //
// Caller will recursively call caller when the contract is a delegate // Caller will recursively call caller when the contract is a delegate
@ -113,24 +116,24 @@ func (c *Contract) Caller() common.Address {
// Finalise finalises the contract and returning any remaining gas to the original // Finalise finalises the contract and returning any remaining gas to the original
// caller. // caller.
func (c *Contract) Finalise() { func (c *Contract) Finalise() {
c.gas.SetUint64(c.gas64)
// Return the remaining gas to the caller // Return the remaining gas to the caller
c.caller.ReturnGas(c.Gas, c.Price) c.caller.ReturnGas(c.gas64)
} }
// UseGas attempts the use gas and subtracts it and returns true on success // UseGas attempts the use gas and subtracts it and returns true on success
func (c *Contract) UseGas(gas *big.Int) (ok bool) { func (c *Contract) UseGas(gas uint64) (ok bool) {
ok = useGas(c.Gas, gas) if c.gas64 < gas {
if ok { return false
c.UsedGas.Add(c.UsedGas, gas)
} }
return c.gas64 -= gas
return true
} }
// ReturnGas adds the given gas back to itself. // ReturnGas adds the given gas back to itself.
func (c *Contract) ReturnGas(gas, price *big.Int) { func (c *Contract) ReturnGas(gas uint64) {
// Return the gas to the context // Return the gas to the context
c.Gas.Add(c.Gas, gas) c.gas64 += gas
c.UsedGas.Sub(c.UsedGas, gas)
} }
// Address returns the contracts address // Address returns the contracts address

View file

@ -26,64 +26,45 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
// PrecompiledAccount represents a native ethereum contract // Precompiled contract is the basic interface for native Go contracts. The implementation
type PrecompiledAccount struct { // requires a deterministic gas count based on the input size of the Run method of the
Gas func(l int) *big.Int // contract.
fn func(in []byte) []byte type PrecompiledContract interface {
} RequiredGas(inputSize int) *big.Int // RequiredPrice calculates the contract gas use
Run(input []byte) []byte // Run runs the precompiled contract
// Call calls the native function
func (self PrecompiledAccount) Call(in []byte) []byte {
return self.fn(in)
} }
// Precompiled contains the default set of ethereum contracts // Precompiled contains the default set of ethereum contracts
var Precompiled = PrecompiledContracts() var PrecompiledContracts = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256{},
common.BytesToAddress([]byte{3}): &ripemd160{},
common.BytesToAddress([]byte{4}): &dataCopy{},
}
// PrecompiledContracts returns the default set of precompiled ethereum // RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go
// contracts defined by the ethereum yellow paper. func RunPrecompiled(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
func PrecompiledContracts() map[string]*PrecompiledAccount { gas := p.RequiredGas(len(input))
return map[string]*PrecompiledAccount{ if contract.UseGas(gas.Uint64()) {
// ECRECOVER ret = p.Run(input)
string(common.LeftPadBytes([]byte{1}, 20)): &PrecompiledAccount{func(l int) *big.Int {
return params.EcrecoverGas
}, ecrecoverFunc},
// SHA256 return ret, nil
string(common.LeftPadBytes([]byte{2}, 20)): &PrecompiledAccount{func(l int) *big.Int { } else {
n := big.NewInt(int64(l+31) / 32) return nil, OutOfGasError
n.Mul(n, params.Sha256WordGas)
return n.Add(n, params.Sha256Gas)
}, sha256Func},
// RIPEMD160
string(common.LeftPadBytes([]byte{3}, 20)): &PrecompiledAccount{func(l int) *big.Int {
n := big.NewInt(int64(l+31) / 32)
n.Mul(n, params.Ripemd160WordGas)
return n.Add(n, params.Ripemd160Gas)
}, ripemd160Func},
string(common.LeftPadBytes([]byte{4}, 20)): &PrecompiledAccount{func(l int) *big.Int {
n := big.NewInt(int64(l+31) / 32)
n.Mul(n, params.IdentityWordGas)
return n.Add(n, params.IdentityGas)
}, memCpy},
} }
} }
func sha256Func(in []byte) []byte { // ECRECOVER implemented as a native contract
return crypto.Sha256(in) type ecrecover struct{}
func (c *ecrecover) RequiredGas(inputSize int) *big.Int {
return params.EcrecoverGas
} }
func ripemd160Func(in []byte) []byte { func (c *ecrecover) Run(in []byte) []byte {
return common.LeftPadBytes(crypto.Ripemd160(in), 32) const ecRecoverInputLength = 128
}
const ecRecoverInputLength = 128 in = common.RightPadBytes(in, ecRecoverInputLength)
func ecrecoverFunc(in []byte) []byte {
in = common.RightPadBytes(in, 128)
// "in" is (hash, v, r, s), each 32 bytes // "in" is (hash, v, r, s), each 32 bytes
// but for ecrecover we want (r, s, v) // but for ecrecover we want (r, s, v)
@ -114,6 +95,39 @@ func ecrecoverFunc(in []byte) []byte {
return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32) return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32)
} }
func memCpy(in []byte) []byte { // SHA256 implemented as a native contract
type sha256 struct{}
func (c *sha256) RequiredGas(inputSize int) *big.Int {
n := big.NewInt(int64(inputSize+31) / 32)
n.Mul(n, params.Sha256WordGas)
return n.Add(n, params.Sha256Gas)
}
func (c *sha256) Run(in []byte) []byte {
return crypto.Sha256(in)
}
// RIPMED160 implemented as a native contract
type ripemd160 struct{}
func (c *ripemd160) RequiredGas(inputSize int) *big.Int {
n := big.NewInt(int64(inputSize+31) / 32)
n.Mul(n, params.Ripemd160WordGas)
return n.Add(n, params.Ripemd160Gas)
}
func (c *ripemd160) Run(in []byte) []byte {
return common.LeftPadBytes(crypto.Ripemd160(in), 32)
}
// data copy implemented as a native contract
type dataCopy struct{}
func (c *dataCopy) RequiredGas(inputSize int) *big.Int {
n := big.NewInt(int64(inputSize+31) / 32)
n.Mul(n, params.IdentityWordGas)
return n.Add(n, params.IdentityGas)
}
func (c *dataCopy) Run(in []byte) []byte {
return in return in
} }

View file

@ -17,16 +17,10 @@
/* /*
Package vm implements the Ethereum Virtual Machine. Package vm implements the Ethereum Virtual Machine.
The vm package implements two EVMs, a byte code VM and a JIT VM. The BC The VM, when invoked, loops around a set of pre-defined instructions until
(Byte Code) VM loops over a set of bytes and executes them according to the set
of rules defined in the Ethereum yellow paper. When the BC VM is invoked it
invokes the JIT VM in a separate goroutine and compiles the byte code in JIT
instructions.
The JIT VM, when invoked, loops around a set of pre-defined instructions until
it either runs of gas, causes an internal error, returns or stops. it either runs of gas, causes an internal error, returns or stops.
The JIT optimiser attempts to pre-compile instructions in to chunks or segments The optimiser attempts to pre-compile instructions in to chunks or segments
such as multiple PUSH operations and static JUMPs. It does this by analysing the such as multiple PUSH operations and static JUMPs. It does this by analysing the
opcodes and attempts to match certain regions to known sets. Whenever the opcodes and attempts to match certain regions to known sets. Whenever the
optimiser finds said segments it creates a new instruction and replaces the optimiser finds said segments it creates a new instruction and replaces the

View file

@ -1,128 +0,0 @@
// Copyright 2014 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 (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// RuleSet is an interface that defines the current rule set during the
// execution of the EVM instructions (e.g. whether it's homestead)
type RuleSet interface {
IsHomestead(*big.Int) bool
}
// Environment is an EVM requirement and helper which allows access to outside
// information such as states.
type Environment interface {
// The current ruleset
RuleSet() RuleSet
// The state database
Db() Database
// Creates a restorable snapshot
SnapshotDatabase() int
// Set database to previous snapshot
RevertToSnapshot(int)
// Address of the original invoker (first occurrence of the VM invoker)
Origin() common.Address
// The block number this VM is invoked on
BlockNumber() *big.Int
// The n'th hash ago from this block number
GetHash(uint64) common.Hash
// The handler's address
Coinbase() common.Address
// The current time (block time)
Time() *big.Int
// Difficulty set on the current block
Difficulty() *big.Int
// The gas limit of the block
GasLimit() *big.Int
// Determines whether it's possible to transact
CanTransfer(from common.Address, balance *big.Int) bool
// Transfers amount from one account to the other
Transfer(from, to Account, amount *big.Int)
// Adds a LOG to the state
AddLog(*Log)
// Type of the VM
Vm() Vm
// Get the curret calling depth
Depth() int
// Set the current calling depth
SetDepth(i int)
// Call another contract
Call(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
// Take another's contract code and execute within our own context
CallCode(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
// Same as CallCode except sender and value is propagated from parent to child scope
DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error)
// Create a new contract
Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error)
}
// Vm is the basic interface for an implementation of the EVM.
type Vm interface {
// Run should execute the given contract with the input given in in
// and return the contract execution return bytes or an error if it
// failed.
Run(c *Contract, in []byte) ([]byte, error)
}
// Database is a EVM database for full state querying.
type Database interface {
GetAccount(common.Address) Account
CreateAccount(common.Address) Account
AddBalance(common.Address, *big.Int)
GetBalance(common.Address) *big.Int
GetNonce(common.Address) uint64
SetNonce(common.Address, uint64)
GetCodeHash(common.Address) common.Hash
GetCodeSize(common.Address) int
GetCode(common.Address) []byte
SetCode(common.Address, []byte)
AddRefund(*big.Int)
GetRefund() *big.Int
GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash)
Suicide(common.Address) bool
HasSuicided(common.Address) bool
// Exist reports whether the given account exists in state.
// Notably this should also return true for suicided accounts.
Exist(common.Address) bool
}
// Account represents a contract or basic ethereum account.
type Account interface {
SubBalance(amount *big.Int)
AddBalance(amount *big.Int)
SetBalance(*big.Int)
SetNonce(uint64)
Balance() *big.Int
Address() common.Address
ReturnGas(*big.Int, *big.Int)
SetCode(common.Hash, []byte)
ForEachStorage(cb func(key, value common.Hash) bool)
Value() *big.Int
}

View file

@ -1,145 +1,175 @@
// Copyright 2015 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 package vm
import ( import (
"fmt" "fmt"
gmath "math"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
var ( var (
GasQuickStep = big.NewInt(2) maxInt63 = new(big.Int).Exp(big.NewInt(2), big.NewInt(63), big.NewInt(0))
GasFastestStep = big.NewInt(3) maxIntCap = new(big.Int).Sub(maxInt63, big.NewInt(1))
GasFastStep = big.NewInt(5)
GasMidStep = big.NewInt(8)
GasSlowStep = big.NewInt(10)
GasExtStep = big.NewInt(20)
GasReturn = big.NewInt(0)
GasStop = big.NewInt(0)
GasContractByte = big.NewInt(200)
) )
// baseCheck checks for any stack error underflows var (
func baseCheck(op OpCode, stack *Stack, gas *big.Int) error { StackLimit64 = params.StackLimit.Uint64()
// PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit GasQuickStep64 uint64 = 2
// PUSH is also allowed to calculate the same price for all PUSHes GasFastestStep64 uint64 = 3
// DUP requirements are handled elsewhere (except for the stack limit check) GasFastStep64 uint64 = 5
if op >= PUSH1 && op <= PUSH32 { GasMidStep64 uint64 = 8
op = PUSH1 GasSlowStep64 uint64 = 10
} GasExtStep64 uint64 = 20
if op >= DUP1 && op <= DUP16 {
op = DUP1
}
if r, ok := _baseCheck[op]; ok { GasReturn64 uint64 = 0
err := stack.require(r.stackPop) GasStop64 uint64 = 0
if err != nil {
return err
}
if r.stackPush > 0 && stack.len()-r.stackPop+r.stackPush > int(params.StackLimit.Int64()) { GasContractByte64 uint64 = 200
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) LogGas64 = params.LogGas.Uint64()
} LogTopicGas64 = params.LogTopicGas.Uint64()
LogDataGas64 = params.LogDataGas.Uint64()
gas.Add(gas, r.gas) ExpByteGas64 = params.ExpByteGas.Uint64()
} SstoreSetGas64 = params.SstoreSetGas.Uint64()
return nil SstoreClearGas64 = params.SstoreClearGas.Uint64()
} SstoreResetGas64 = params.SstoreResetGas.Uint64()
KeccakWordGas64 = params.Sha3WordGas.Uint64()
CopyGas64 = params.CopyGas.Uint64()
CallNewAccountGas64 = params.CallNewAccountGas.Uint64()
CallValueTransferGas64 = params.CallValueTransferGas.Uint64()
MemoryGas64 = params.MemoryGas.Uint64()
QuadCoeffDiv64 = params.QuadCoeffDiv.Uint64()
)
// casts a arbitrary number to the amount of words (sets of 32 bytes) // casts a arbitrary number to the amount of words (sets of 32 bytes)
func toWordSize(size *big.Int) *big.Int { func toWordSize(size uint64) uint64 {
tmp := new(big.Int) return (size + 31) / 32
tmp.Add(size, u256(31)) }
tmp.Div(tmp, u256(32))
return tmp // calculates the memory size required for a step
func calcMemSize(off, l *big.Int) (uint64, bool) {
if l.Cmp(common.Big0) == 0 {
return 0, false
}
size := new(big.Int).Add(off, l)
if size.Cmp(maxIntCap) > 0 {
return 0, true
}
return size.Uint64(), false
}
// calculates the quadratic gas
// TODO this function requires guarding of overflows
func calcQuadMemGas(mem *Memory, newMemSize uint64) (uint64, bool) {
oldTotalFee := mem.cost
if newMemSize > 0 {
newMemSizeWords := toWordSize(newMemSize)
newMemSize = newMemSizeWords * 32
if newMemSize > uint64(mem.Len()) {
pow := uint64(gmath.Pow(float64(newMemSizeWords), 2))
linCoef := newMemSizeWords * MemoryGas64
quadCoef := pow / QuadCoeffDiv64
newTotalFee := linCoef + quadCoef
fee := newTotalFee - oldTotalFee
mem.cost = newTotalFee
return fee, false
}
}
return 0, false
}
// baseCalc is the same as baseCheck except it doesn't do the look up in the
// gas table. This is done during compilation instead.
func baseCalc(instr instruction, stack *Stack) (uint64, error) {
err := stack.require(instr.spop)
if err != nil {
return 0, err
}
if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(StackLimit64) {
return 0, fmt.Errorf("stack limit reached %d (%d)", stack.len(), StackLimit64)
}
// 0 on gas means no base calculation
if instr.gas == 0 {
return 0, nil
}
return instr.gas, nil
} }
type req struct { type req struct {
stackPop int stackPop int
gas *big.Int gas uint64
stackPush int stackPush int
} }
var _baseCheck = map[OpCode]req{ var _baseCheck = map[OpCode]req{
// opcode | stack pop | gas price | stack push // opcode | stack pop | gas price | stack push
ADD: {2, GasFastestStep, 1}, ADD: {2, GasFastestStep64, 1},
LT: {2, GasFastestStep, 1}, LT: {2, GasFastestStep64, 1},
GT: {2, GasFastestStep, 1}, GT: {2, GasFastestStep64, 1},
SLT: {2, GasFastestStep, 1}, SLT: {2, GasFastestStep64, 1},
SGT: {2, GasFastestStep, 1}, SGT: {2, GasFastestStep64, 1},
EQ: {2, GasFastestStep, 1}, EQ: {2, GasFastestStep64, 1},
ISZERO: {1, GasFastestStep, 1}, ISZERO: {1, GasFastestStep64, 1},
SUB: {2, GasFastestStep, 1}, SUB: {2, GasFastestStep64, 1},
AND: {2, GasFastestStep, 1}, AND: {2, GasFastestStep64, 1},
OR: {2, GasFastestStep, 1}, OR: {2, GasFastestStep64, 1},
XOR: {2, GasFastestStep, 1}, XOR: {2, GasFastestStep64, 1},
NOT: {1, GasFastestStep, 1}, NOT: {1, GasFastestStep64, 1},
BYTE: {2, GasFastestStep, 1}, BYTE: {2, GasFastestStep64, 1},
CALLDATALOAD: {1, GasFastestStep, 1}, CALLDATALOAD: {1, GasFastestStep64, 1},
CALLDATACOPY: {3, GasFastestStep, 1}, CALLDATACOPY: {3, GasFastestStep64, 1},
MLOAD: {1, GasFastestStep, 1}, MLOAD: {1, GasFastestStep64, 1},
MSTORE: {2, GasFastestStep, 0}, MSTORE: {2, GasFastestStep64, 0},
MSTORE8: {2, GasFastestStep, 0}, MSTORE8: {2, GasFastestStep64, 0},
CODECOPY: {3, GasFastestStep, 0}, CODECOPY: {3, GasFastestStep64, 0},
MUL: {2, GasFastStep, 1}, MUL: {2, GasFastStep64, 1},
DIV: {2, GasFastStep, 1}, DIV: {2, GasFastStep64, 1},
SDIV: {2, GasFastStep, 1}, SDIV: {2, GasFastStep64, 1},
MOD: {2, GasFastStep, 1}, MOD: {2, GasFastStep64, 1},
SMOD: {2, GasFastStep, 1}, SMOD: {2, GasFastStep64, 1},
SIGNEXTEND: {2, GasFastStep, 1}, SIGNEXTEND: {2, GasFastStep64, 1},
ADDMOD: {3, GasMidStep, 1}, ADDMOD: {3, GasMidStep64, 1},
MULMOD: {3, GasMidStep, 1}, MULMOD: {3, GasMidStep64, 1},
JUMP: {1, GasMidStep, 0}, JUMP: {1, GasMidStep64, 0},
JUMPI: {2, GasSlowStep, 0}, JUMPI: {2, GasSlowStep64, 0},
EXP: {2, GasSlowStep, 1}, EXP: {2, GasSlowStep64, 1},
ADDRESS: {0, GasQuickStep, 1}, ADDRESS: {0, GasQuickStep64, 1},
ORIGIN: {0, GasQuickStep, 1}, ORIGIN: {0, GasQuickStep64, 1},
CALLER: {0, GasQuickStep, 1}, CALLER: {0, GasQuickStep64, 1},
CALLVALUE: {0, GasQuickStep, 1}, CALLVALUE: {0, GasQuickStep64, 1},
CODESIZE: {0, GasQuickStep, 1}, CODESIZE: {0, GasQuickStep64, 1},
GASPRICE: {0, GasQuickStep, 1}, GASPRICE: {0, GasQuickStep64, 1},
COINBASE: {0, GasQuickStep, 1}, COINBASE: {0, GasQuickStep64, 1},
TIMESTAMP: {0, GasQuickStep, 1}, TIMESTAMP: {0, GasQuickStep64, 1},
NUMBER: {0, GasQuickStep, 1}, NUMBER: {0, GasQuickStep64, 1},
CALLDATASIZE: {0, GasQuickStep, 1}, CALLDATASIZE: {0, GasQuickStep64, 1},
DIFFICULTY: {0, GasQuickStep, 1}, DIFFICULTY: {0, GasQuickStep64, 1},
GASLIMIT: {0, GasQuickStep, 1}, GASLIMIT: {0, GasQuickStep64, 1},
POP: {1, GasQuickStep, 0}, POP: {1, GasQuickStep64, 0},
PC: {0, GasQuickStep, 1}, PC: {0, GasQuickStep64, 1},
MSIZE: {0, GasQuickStep, 1}, MSIZE: {0, GasQuickStep64, 1},
GAS: {0, GasQuickStep, 1}, GAS: {0, GasQuickStep64, 1},
BLOCKHASH: {1, GasExtStep, 1}, BLOCKHASH: {1, GasExtStep64, 1},
BALANCE: {1, GasExtStep, 1}, BALANCE: {1, GasExtStep64, 1},
EXTCODESIZE: {1, GasExtStep, 1}, EXTCODESIZE: {1, GasExtStep64, 1},
EXTCODECOPY: {4, GasExtStep, 0}, EXTCODECOPY: {4, GasExtStep64, 0},
SLOAD: {1, params.SloadGas, 1}, SLOAD: {1, params.SloadGas.Uint64(), 1},
SSTORE: {2, Zero, 0}, SSTORE: {2, 0, 0},
SHA3: {2, params.Sha3Gas, 1}, SHA3: {2, params.Sha3Gas.Uint64(), 1},
CREATE: {3, params.CreateGas, 1}, CREATE: {3, params.CreateGas.Uint64(), 1},
CALL: {7, params.CallGas, 1}, CALL: {7, params.CallGas.Uint64(), 1},
CALLCODE: {7, params.CallGas, 1}, CALLCODE: {7, params.CallGas.Uint64(), 1},
DELEGATECALL: {6, params.CallGas, 1}, DELEGATECALL: {6, params.CallGas.Uint64(), 1},
JUMPDEST: {0, params.JumpdestGas, 0}, JUMPDEST: {0, params.JumpdestGas.Uint64(), 0},
SUICIDE: {1, Zero, 0}, SUICIDE: {1, 0, 0},
RETURN: {2, Zero, 0}, RETURN: {2, 0, 0},
PUSH1: {0, GasFastestStep, 1}, PUSH1: {0, GasFastestStep64, 1},
DUP1: {0, Zero, 1}, DUP1: {0, 0, 1},
} }

View file

@ -27,14 +27,16 @@ import (
type programInstruction interface { type programInstruction interface {
// executes the program instruction and allows the instruction to modify the state of the program // executes the program instruction and allows the instruction to modify the state of the program
do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
// returns whether the program instruction halts the execution of the JIT // returns whether the program instruction halts the execution of the JIT
halts() bool halts() bool
// Returns the current op code (debugging purposes) // Returns the current op code (debugging purposes)
Op() OpCode Op() OpCode
} }
type instrFn func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) //[static memory], if calldata goto jump table
type instrFn func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack)
type instruction struct { type instruction struct {
op OpCode op OpCode
@ -42,13 +44,20 @@ type instruction struct {
fn instrFn fn instrFn
data *big.Int data *big.Int
gas *big.Int gas uint64
spop int spop int
spush int spush int
returns bool returns bool
} }
func (instr instruction) String() string {
if instr.op == PUSH1 {
return fmt.Sprintf("PUSH %v", instr.data)
}
return fmt.Sprintf("%v", instr.op)
}
func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) { func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) {
if !validDest(destinations, to) { if !validDest(destinations, to) {
nop := contract.GetOp(to.Uint64()) nop := contract.GetOp(to.Uint64())
@ -58,12 +67,13 @@ func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract
return mapping[to.Uint64()], nil return mapping[to.Uint64()], nil
} }
func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func (instr instruction) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// calculate the new memory size and gas price for the current executing opcode // calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, env.Db(), memory, stack) newMemSize, cost, err := calculateGasAndSize(env, contract, instr, env.Db(), memory, stack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
//fmt.Printf("%04d: %-8v %v\n", *pc, instr.op, cost)
// Use the calculated gas. When insufficient gas is present, use all gas and return an // Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error // Out Of Gas error
@ -71,7 +81,7 @@ func (instr instruction) do(program *Program, pc *uint64, env Environment, contr
return nil, OutOfGasError return nil, OutOfGasError
} }
// Resize the memory calculated previously // Resize the memory calculated previously
memory.Resize(newMemSize.Uint64()) memory.Resize(newMemSize)
// These opcodes return an argument and are therefor handled // These opcodes return an argument and are therefor handled
// differently from the rest of the opcodes // differently from the rest of the opcodes
@ -114,26 +124,26 @@ func (instr instruction) Op() OpCode {
return instr.op return instr.op
} }
func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
ret.Set(instr.data) ret.Set(instr.data)
} }
func opAdd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opAdd(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(U256(x.Add(x, y))) stack.push(U256(x.Add(x, y)))
} }
func opSub(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSub(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(U256(x.Sub(x, y))) stack.push(U256(x.Sub(x, y)))
} }
func opMul(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMul(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(U256(x.Mul(x, y))) stack.push(U256(x.Mul(x, y)))
} }
func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opDiv(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
if y.Cmp(common.Big0) != 0 { if y.Cmp(common.Big0) != 0 {
stack.push(U256(x.Div(x, y))) stack.push(U256(x.Div(x, y)))
@ -142,7 +152,7 @@ func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, m
} }
} }
func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSdiv(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop()) x, y := S256(stack.pop()), S256(stack.pop())
if y.Cmp(common.Big0) == 0 { if y.Cmp(common.Big0) == 0 {
stack.push(new(big.Int)) stack.push(new(big.Int))
@ -162,7 +172,7 @@ func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract,
} }
} }
func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
if y.Cmp(common.Big0) == 0 { if y.Cmp(common.Big0) == 0 {
stack.push(new(big.Int)) stack.push(new(big.Int))
@ -171,7 +181,7 @@ func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, m
} }
} }
func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop()) x, y := S256(stack.pop()), S256(stack.pop())
if y.Cmp(common.Big0) == 0 { if y.Cmp(common.Big0) == 0 {
@ -191,12 +201,12 @@ func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract,
} }
} }
func opExp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opExp(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(U256(x.Exp(x, y, Pow256))) stack.push(U256(x.Exp(x, y, Pow256)))
} }
func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSignExtend(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
back := stack.pop() back := stack.pop()
if back.Cmp(big.NewInt(31)) < 0 { if back.Cmp(big.NewInt(31)) < 0 {
bit := uint(back.Uint64()*8 + 7) bit := uint(back.Uint64()*8 + 7)
@ -213,12 +223,12 @@ func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Cont
} }
} }
func opNot(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opNot(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x := stack.pop() x := stack.pop()
stack.push(U256(x.Not(x))) stack.push(U256(x.Not(x)))
} }
func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opLt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
if x.Cmp(y) < 0 { if x.Cmp(y) < 0 {
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
@ -227,7 +237,7 @@ func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, me
} }
} }
func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opGt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
if x.Cmp(y) > 0 { if x.Cmp(y) > 0 {
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
@ -236,7 +246,7 @@ func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, me
} }
} }
func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSlt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop()) x, y := S256(stack.pop()), S256(stack.pop())
if x.Cmp(S256(y)) < 0 { if x.Cmp(S256(y)) < 0 {
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
@ -245,7 +255,7 @@ func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, m
} }
} }
func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSgt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := S256(stack.pop()), S256(stack.pop()) x, y := S256(stack.pop()), S256(stack.pop())
if x.Cmp(y) > 0 { if x.Cmp(y) > 0 {
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
@ -254,7 +264,7 @@ func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, m
} }
} }
func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opEq(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
if x.Cmp(y) == 0 { if x.Cmp(y) == 0 {
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
@ -263,7 +273,7 @@ func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, me
} }
} }
func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opIszero(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x := stack.pop() x := stack.pop()
if x.Cmp(common.Big0) > 0 { if x.Cmp(common.Big0) > 0 {
stack.push(new(big.Int)) stack.push(new(big.Int))
@ -272,19 +282,19 @@ func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract
} }
} }
func opAnd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opAnd(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(x.And(x, y)) stack.push(x.And(x, y))
} }
func opOr(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opOr(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(x.Or(x, y)) stack.push(x.Or(x, y))
} }
func opXor(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opXor(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y := stack.pop(), stack.pop() x, y := stack.pop(), stack.pop()
stack.push(x.Xor(x, y)) stack.push(x.Xor(x, y))
} }
func opByte(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opByte(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
th, val := stack.pop(), stack.pop() th, val := stack.pop(), stack.pop()
if th.Cmp(big.NewInt(32)) < 0 { if th.Cmp(big.NewInt(32)) < 0 {
byte := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()])) byte := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()]))
@ -293,7 +303,7 @@ func opByte(instr instruction, pc *uint64, env Environment, contract *Contract,
stack.push(new(big.Int)) stack.push(new(big.Int))
} }
} }
func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opAddmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y, z := stack.pop(), stack.pop(), stack.pop() x, y, z := stack.pop(), stack.pop(), stack.pop()
if z.Cmp(Zero) > 0 { if z.Cmp(Zero) > 0 {
add := x.Add(x, y) add := x.Add(x, y)
@ -303,7 +313,7 @@ func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract
stack.push(new(big.Int)) stack.push(new(big.Int))
} }
} }
func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMulmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
x, y, z := stack.pop(), stack.pop(), stack.pop() x, y, z := stack.pop(), stack.pop(), stack.pop()
if z.Cmp(Zero) > 0 { if z.Cmp(Zero) > 0 {
mul := x.Mul(x, y) mul := x.Mul(x, y)
@ -314,45 +324,45 @@ func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract
} }
} }
func opSha3(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSha3(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
offset, size := stack.pop(), stack.pop() offset, size := stack.pop(), stack.pop()
hash := crypto.Keccak256(memory.Get(offset.Int64(), size.Int64())) hash := crypto.Keccak256(memory.GetPtr(offset.Int64(), size.Int64()))
stack.push(common.BytesToBig(hash)) stack.push(common.BytesToBig(hash))
} }
func opAddress(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opAddress(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(common.Bytes2Big(contract.Address().Bytes())) stack.push(common.Bytes2Big(contract.Address().Bytes()))
} }
func opBalance(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opBalance(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
addr := common.BigToAddress(stack.pop()) addr := common.BigToAddress(stack.pop())
balance := env.Db().GetBalance(addr) balance := env.Db().GetBalance(addr)
stack.push(new(big.Int).Set(balance)) stack.push(new(big.Int).Set(balance))
} }
func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opOrigin(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(env.Origin().Big()) stack.push(env.Origin.Big())
} }
func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCaller(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(contract.Caller().Big()) stack.push(contract.Caller().Big())
} }
func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCallValue(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(contract.value)) stack.push(new(big.Int).Set(contract.value))
} }
func opCalldataLoad(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCalldataLoad(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(common.Bytes2Big(getData(contract.Input, stack.pop(), common.Big32))) stack.push(common.Bytes2Big(getData(contract.Input, stack.pop(), common.Big32)))
} }
func opCalldataSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCalldataSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(big.NewInt(int64(len(contract.Input)))) stack.push(big.NewInt(int64(len(contract.Input))))
} }
func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCalldataCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var ( var (
mOff = stack.pop() mOff = stack.pop()
cOff = stack.pop() cOff = stack.pop()
@ -361,18 +371,18 @@ func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Co
memory.Set(mOff.Uint64(), l.Uint64(), getData(contract.Input, cOff, l)) memory.Set(mOff.Uint64(), l.Uint64(), getData(contract.Input, cOff, l))
} }
func opExtCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opExtCodeSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
addr := common.BigToAddress(stack.pop()) addr := common.BigToAddress(stack.pop())
l := big.NewInt(int64(env.Db().GetCodeSize(addr))) l := big.NewInt(int64(env.Db().GetCodeSize(addr)))
stack.push(l) stack.push(l)
} }
func opCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCodeSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
l := big.NewInt(int64(len(contract.Code))) l := big.NewInt(int64(len(contract.Code)))
stack.push(l) stack.push(l)
} }
func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCodeCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var ( var (
mOff = stack.pop() mOff = stack.pop()
cOff = stack.pop() cOff = stack.pop()
@ -383,7 +393,7 @@ func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contra
memory.Set(mOff.Uint64(), l.Uint64(), codeCopy) memory.Set(mOff.Uint64(), l.Uint64(), codeCopy)
} }
func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opExtCodeCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var ( var (
addr = common.BigToAddress(stack.pop()) addr = common.BigToAddress(stack.pop())
mOff = stack.pop() mOff = stack.pop()
@ -395,58 +405,58 @@ func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Con
memory.Set(mOff.Uint64(), l.Uint64(), codeCopy) memory.Set(mOff.Uint64(), l.Uint64(), codeCopy)
} }
func opGasprice(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opGasprice(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(contract.Price)) stack.push(new(big.Int).Set(env.GasPrice))
} }
func opBlockhash(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opBlockhash(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
num := stack.pop() num := stack.pop()
n := new(big.Int).Sub(env.BlockNumber(), common.Big257) n := new(big.Int).Sub(env.BlockNumber, common.Big257)
if num.Cmp(n) > 0 && num.Cmp(env.BlockNumber()) < 0 { if num.Cmp(n) > 0 && num.Cmp(env.BlockNumber) < 0 {
stack.push(env.GetHash(num.Uint64()).Big()) stack.push(env.GetHash(num.Uint64()).Big())
} else { } else {
stack.push(new(big.Int)) stack.push(new(big.Int))
} }
} }
func opCoinbase(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCoinbase(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(env.Coinbase().Big()) stack.push(env.Coinbase.Big())
} }
func opTimestamp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opTimestamp(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(U256(new(big.Int).Set(env.Time()))) stack.push(U256(new(big.Int).Set(env.Time)))
} }
func opNumber(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opNumber(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(U256(new(big.Int).Set(env.BlockNumber()))) stack.push(U256(new(big.Int).Set(env.BlockNumber)))
} }
func opDifficulty(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opDifficulty(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(U256(new(big.Int).Set(env.Difficulty()))) stack.push(U256(new(big.Int).Set(env.Difficulty)))
} }
func opGasLimit(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opGasLimit(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(U256(new(big.Int).Set(env.GasLimit()))) stack.push(U256(new(big.Int).Set(env.GasLimit)))
} }
func opPop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opPop(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.pop() stack.pop()
} }
func opPush(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opPush(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(instr.data)) stack.push(new(big.Int).Set(instr.data))
} }
func opDup(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opDup(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.dup(int(instr.data.Int64())) stack.dup(int(instr.data.Int64()))
} }
func opSwap(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSwap(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.swap(int(instr.data.Int64())) stack.swap(int(instr.data.Int64()))
} }
func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opLog(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
n := int(instr.data.Int64()) n := int(instr.data.Int64())
topics := make([]common.Hash, n) topics := make([]common.Hash, n)
mStart, mSize := stack.pop(), stack.pop() mStart, mSize := stack.pop(), stack.pop()
@ -455,72 +465,72 @@ func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, m
} }
d := memory.Get(mStart.Int64(), mSize.Int64()) d := memory.Get(mStart.Int64(), mSize.Int64())
log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64()) log := NewLog(contract.Address(), topics, d, env.BlockNumber.Uint64())
env.AddLog(log) env.Db().AddLog(log)
} }
func opMload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMload(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
offset := stack.pop() offset := stack.pop()
val := common.BigD(memory.Get(offset.Int64(), 32)) val := common.BigD(memory.GetPtr(offset.Int64(), 32))
stack.push(val) stack.push(val)
} }
func opMstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMstore(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
// pop value of the stack // pop value of the stack
mStart, val := stack.pop(), stack.pop() mStart, val := stack.pop(), stack.pop()
memory.Set(mStart.Uint64(), 32, common.BigToBytes(val, 256)) memory.Set(mStart.Uint64(), 32, common.BigToBytes(val, 256))
} }
func opMstore8(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMstore8(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
off, val := stack.pop().Int64(), stack.pop().Int64() off, val := stack.pop().Int64(), stack.pop().Int64()
memory.store[off] = byte(val & 0xff) memory.store[off] = byte(val & 0xff)
} }
func opSload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSload(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
loc := common.BigToHash(stack.pop()) loc := common.BigToHash(stack.pop())
val := env.Db().GetState(contract.Address(), loc).Big() val := env.Db().GetState(contract.Address(), loc).Big()
stack.push(val) stack.push(val)
} }
func opSstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSstore(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
loc := common.BigToHash(stack.pop()) loc := common.BigToHash(stack.pop())
val := stack.pop() val := stack.pop()
env.Db().SetState(contract.Address(), loc, common.BigToHash(val)) env.Db().SetState(contract.Address(), loc, common.BigToHash(val))
} }
func opJump(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opJump(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
} }
func opJumpi(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opJumpi(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
} }
func opJumpdest(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opJumpdest(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
} }
func opPc(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opPc(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(instr.data)) stack.push(new(big.Int).Set(instr.data))
} }
func opMsize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opMsize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(big.NewInt(int64(memory.Len()))) stack.push(big.NewInt(int64(memory.Len())))
} }
func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opGas(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.push(new(big.Int).Set(contract.Gas)) stack.push(new(big.Int).SetUint64(contract.gas64))
} }
func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCreate(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
var ( var (
value = stack.pop() value = stack.pop()
offset, size = stack.pop(), stack.pop() offset, size = stack.pop(), stack.pop()
input = memory.Get(offset.Int64(), size.Int64()) input = memory.Get(offset.Int64(), size.Int64())
gas = new(big.Int).Set(contract.Gas) gas = new(big.Int).SetUint64(contract.gas64)
) )
contract.UseGas(contract.Gas) contract.UseGas(contract.gas64)
_, addr, suberr := env.Create(contract, input, gas, contract.Price, value) _, addr, suberr := env.Create(contract, input, gas, value)
// Push item on the stack based on the returned error. If the ruleset is // Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only // homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must // rule) and treat as an error, if the ruleset is frontier we must
// ignore this error and pretend the operation was successful. // ignore this error and pretend the operation was successful.
if env.RuleSet().IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError { if env.RuleSet().IsHomestead(env.BlockNumber) && suberr == CodeStoreOutOfGasError {
stack.push(new(big.Int)) stack.push(new(big.Int))
} else if suberr != nil && suberr != CodeStoreOutOfGasError { } else if suberr != nil && suberr != CodeStoreOutOfGasError {
stack.push(new(big.Int)) stack.push(new(big.Int))
@ -529,7 +539,7 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
} }
} }
func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCall(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
gas := stack.pop() gas := stack.pop()
// pop gas and value of the stack. // pop gas and value of the stack.
addr, value := stack.pop(), stack.pop() addr, value := stack.pop(), stack.pop()
@ -548,7 +558,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract,
gas.Add(gas, params.CallStipend) gas.Add(gas, params.CallStipend)
} }
ret, err := env.Call(contract, address, args, gas, contract.Price, value) ret, err := env.Call(contract, address, args, gas, value)
if err != nil { if err != nil {
stack.push(new(big.Int)) stack.push(new(big.Int))
@ -560,7 +570,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract,
} }
} }
func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opCallCode(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
gas := stack.pop() gas := stack.pop()
// pop gas and value of the stack. // pop gas and value of the stack.
addr, value := stack.pop(), stack.pop() addr, value := stack.pop(), stack.pop()
@ -579,7 +589,7 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra
gas.Add(gas, params.CallStipend) gas.Add(gas, params.CallStipend)
} }
ret, err := env.CallCode(contract, address, args, gas, contract.Price, value) ret, err := env.CallCode(contract, address, args, gas, value)
if err != nil { if err != nil {
stack.push(new(big.Int)) stack.push(new(big.Int))
@ -591,12 +601,12 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra
} }
} }
func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opDelegateCall(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
gas, to, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() gas, to, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.BigToAddress(to) toAddr := common.BigToAddress(to)
args := memory.Get(inOffset.Int64(), inSize.Int64()) args := memory.Get(inOffset.Int64(), inSize.Int64())
ret, err := env.DelegateCall(contract, toAddr, args, gas, contract.Price) ret, err := env.DelegateCall(contract, toAddr, args, gas)
if err != nil { if err != nil {
stack.push(new(big.Int)) stack.push(new(big.Int))
} else { } else {
@ -605,12 +615,12 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co
} }
} }
func opReturn(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opReturn(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
} }
func opStop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opStop(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
} }
func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { func opSuicide(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
balance := env.Db().GetBalance(contract.Address()) balance := env.Db().GetBalance(contract.Address())
env.Db().AddBalance(common.BigToAddress(stack.pop()), balance) env.Db().AddBalance(common.BigToAddress(stack.pop()), balance)
@ -621,7 +631,7 @@ func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contrac
// make log instruction function // make log instruction function
func makeLog(size int) instrFn { func makeLog(size int) instrFn {
return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
topics := make([]common.Hash, size) topics := make([]common.Hash, size)
mStart, mSize := stack.pop(), stack.pop() mStart, mSize := stack.pop(), stack.pop()
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
@ -629,14 +639,14 @@ func makeLog(size int) instrFn {
} }
d := memory.Get(mStart.Int64(), mSize.Int64()) d := memory.Get(mStart.Int64(), mSize.Int64())
log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64()) log := NewLog(contract.Address(), topics, d, env.BlockNumber.Uint64())
env.AddLog(log) env.Db().AddLog(log)
} }
} }
// make push instruction function // make push instruction function
func makePush(size uint64, bsize *big.Int) instrFn { func makePush(size uint64, bsize *big.Int) instrFn {
return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
byts := getData(contract.Code, new(big.Int).SetUint64(*pc+1), bsize) byts := getData(contract.Code, new(big.Int).SetUint64(*pc+1), bsize)
stack.push(common.Bytes2Big(byts)) stack.push(common.Bytes2Big(byts))
*pc += size *pc += size
@ -645,7 +655,7 @@ func makePush(size uint64, bsize *big.Int) instrFn {
// make push instruction function // make push instruction function
func makeDup(size int64) instrFn { func makeDup(size int64) instrFn {
return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.dup(int(size)) stack.dup(int(size))
} }
} }
@ -654,7 +664,7 @@ func makeDup(size int64) instrFn {
func makeSwap(size int64) instrFn { func makeSwap(size int64) instrFn {
// switch n + 1 otherwise n would be swapped with n // switch n + 1 otherwise n would be swapped with n
size += 1 size += 1
return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) {
stack.swap(int(size)) stack.swap(int(size))
} }
} }

199
core/vm/interface.go Normal file
View file

@ -0,0 +1,199 @@
// Copyright 2014 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 (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// Vm is the basic interface for an implementation of the EVM.
type Vm interface {
// Run should execute the given contract with the input given in in
// and return the contract execution return bytes or an error if it
// failed.
Run(c *Contract, in []byte) ([]byte, error)
}
// RuleSet is an interface that defines the current rule set during the
// execution of the EVM instructions (e.g. whether it's homestead)
type RuleSet interface {
IsHomestead(*big.Int) bool
}
// Database is a EVM database for full state querying.
type Database interface {
GetAccount(common.Address) Account
CreateAccount(common.Address) Account
SubBalance(common.Address, *big.Int)
AddBalance(common.Address, *big.Int)
GetBalance(common.Address) *big.Int
GetNonce(common.Address) uint64
SetNonce(common.Address, uint64)
GetCodeHash(common.Address) common.Hash
GetCode(common.Address) []byte
SetCode(common.Address, []byte)
GetCodeSize(common.Address) int
AddRefund(*big.Int)
GetRefund() *big.Int
GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash)
Suicide(common.Address) bool
HasSuicided(common.Address) bool
// Exist reports whether the given account exists in state.
// Notably this should also return true for suicided accounts.
Exist(common.Address) bool
AddLog(*Log)
}
// Account represents a contract or basic ethereum account.
type Account interface {
SubBalance(amount *big.Int)
AddBalance(amount *big.Int)
SetBalance(*big.Int)
SetNonce(uint64)
Balance() *big.Int
Address() common.Address
ReturnGas(uint64)
SetCode(common.Hash, []byte)
ForEachStorage(cb func(key, value common.Hash) bool)
Value() *big.Int
}
// Context provides the EVM with auxilary information. Once provided it shouldn't be modified.
type Context struct {
CallContext
// Message information
Origin common.Address // Provides information for ORIGIN
Coinbase common.Address // Provides information for COINBASE
GasPrice *big.Int // Provides information for GASPRICE
// Block information
GasLimit *big.Int // Provides information for GASLIMIT
BlockNumber *big.Int // Provides information for NUMBER
Time *big.Int // Provides information for TIME
Difficulty *big.Int // Provides information for DIFFICULTY
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM Environment
// depends on this context being implemented for doing subcalls and initialising new EVM contracts.
type CallContext interface {
// Call another contract
Call(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)
// Take another's contract code and execute within our own context
CallCode(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)
// Same as CallCode except sender and value is propagated from parent to child scope
DelegateCall(env *Environment, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error)
// Create a new contract
Create(env *Environment, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error)
}
// Backend is the basic interface for keeping track of state and taking care of
// returning ancestory data.
type Backend interface {
// GetHash returns the hash corresponding to n
GetHash(n uint64) common.Hash
// Creates a restorable snapshot
SnapshotDatabase() int
// Set database to previous snapshot
RevertToSnapshot(int)
// Get returns the database
Get() Database
}
type Environment struct {
Context
Backend Backend
ruleSet RuleSet
vmConfig Config
evm Vm
Depth int
}
func NewEnvironment(context Context, backend Backend, ruleSet RuleSet, vmCfg Config) *Environment {
env := &Environment{
Context: context,
Backend: backend,
vmConfig: vmCfg,
ruleSet: ruleSet,
}
env.evm = New(env, vmCfg)
return env
}
func (env *Environment) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
if env.vmConfig.Test && env.Depth > 0 {
caller.ReturnGas(gas.Uint64())
return nil, nil
}
return env.CallContext.Call(env, caller, addr, data, gas, value)
}
// Take another's contract code and execute within our own context
func (env *Environment) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
if env.vmConfig.Test && env.Depth > 0 {
caller.ReturnGas(gas.Uint64())
return nil, nil
}
return env.CallContext.CallCode(env, caller, addr, data, gas, value)
}
// Same as CallCode except sender and value is propagated from parent to child scope
func (env *Environment) DelegateCall(caller ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
if env.vmConfig.Test && env.Depth > 0 {
caller.ReturnGas(gas.Uint64())
return nil, nil
}
return env.CallContext.DelegateCall(env, caller, addr, data, gas)
}
// Create a new contract
func (env *Environment) Create(caller ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
if env.vmConfig.Test && env.Depth > 0 {
caller.ReturnGas(gas.Uint64())
return nil, common.Address{}, nil
}
return env.CallContext.Create(env, caller, data, gas, value)
}
func (env *Environment) RuleSet() RuleSet { return env.ruleSet }
func (env *Environment) Vm() Vm { return env.evm }
func (env *Environment) Db() Database { return env.Backend.Get() }
func (env *Environment) GetHash(n uint64) common.Hash {
return env.Backend.GetHash(n)
}

View file

@ -1,211 +0,0 @@
// Copyright 2015 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 (
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
const maxRun = 1000
func TestSegmenting(t *testing.T) {
prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0})
err := CompileProgram(prog)
if err != nil {
t.Fatal(err)
}
if instr, ok := prog.instructions[0].(pushSeg); ok {
if len(instr.data) != 2 {
t.Error("expected 2 element width pushSegment, got", len(instr.data))
}
} else {
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
}
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
err = CompileProgram(prog)
if err != nil {
t.Fatal(err)
}
if _, ok := prog.instructions[1].(jumpSeg); ok {
} else {
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
}
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
err = CompileProgram(prog)
if err != nil {
t.Fatal(err)
}
if instr, ok := prog.instructions[0].(pushSeg); ok {
if len(instr.data) != 2 {
t.Error("expected 2 element width pushSegment, got", len(instr.data))
}
} else {
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
}
if _, ok := prog.instructions[2].(jumpSeg); ok {
} else {
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
}
}
func TestCompiling(t *testing.T) {
prog := NewProgram([]byte{0x60, 0x10})
err := CompileProgram(prog)
if err != nil {
t.Error("didn't expect compile error")
}
if len(prog.instructions) != 1 {
t.Error("expected 1 compiled instruction, got", len(prog.instructions))
}
}
func TestResetInput(t *testing.T) {
var sender account
env := NewEnv(&Config{EnableJit: true, ForceJit: true})
contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
contract.CodeAddr = &common.Address{}
program := NewProgram([]byte{})
RunProgram(program, env, contract, []byte{0xbe, 0xef})
if contract.Input != nil {
t.Errorf("expected input to be nil, got %x", contract.Input)
}
}
func TestPcMappingToInstruction(t *testing.T) {
program := NewProgram([]byte{byte(PUSH2), 0xbe, 0xef, byte(ADD)})
CompileProgram(program)
if program.mapping[3] != 1 {
t.Error("expected mapping PC 4 to me instr no. 2, got", program.mapping[4])
}
}
var benchmarks = map[string]vmBench{
"pushes": vmBench{
false, false, false,
common.Hex2Bytes("600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01"), nil,
},
}
func BenchmarkPushes(b *testing.B) {
runVmBench(benchmarks["pushes"], b)
}
type vmBench struct {
precompile bool // compile prior to executing
nojit bool // ignore jit (sets DisbaleJit = true
forcejit bool // forces the jit, precompile is ignored
code []byte
input []byte
}
type account struct{}
func (account) SubBalance(amount *big.Int) {}
func (account) AddBalance(amount *big.Int) {}
func (account) SetAddress(common.Address) {}
func (account) Value() *big.Int { return nil }
func (account) SetBalance(*big.Int) {}
func (account) SetNonce(uint64) {}
func (account) Balance() *big.Int { return nil }
func (account) Address() common.Address { return common.Address{} }
func (account) ReturnGas(*big.Int, *big.Int) {}
func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
func runVmBench(test vmBench, b *testing.B) {
var sender account
if test.precompile && !test.forcejit {
NewProgram(test.code)
}
env := NewEnv(&Config{EnableJit: !test.nojit, ForceJit: test.forcejit})
b.ResetTimer()
for i := 0; i < b.N; i++ {
context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
context.Code = test.code
context.CodeAddr = &common.Address{}
_, err := env.Vm().Run(context, test.input)
if err != nil {
b.Error(err)
b.FailNow()
}
}
}
type Env struct {
gasLimit *big.Int
depth int
evm *EVM
}
func NewEnv(config *Config) *Env {
env := &Env{gasLimit: big.NewInt(10000), depth: 0}
env.evm = New(env, *config)
return env
}
func (self *Env) RuleSet() RuleSet { return ruleSet{new(big.Int)} }
func (self *Env) Vm() Vm { return self.evm }
func (self *Env) Origin() common.Address { return common.Address{} }
func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) }
//func (self *Env) PrevHash() []byte { return self.parent }
func (self *Env) Coinbase() common.Address { return common.Address{} }
func (self *Env) SnapshotDatabase() int { return 0 }
func (self *Env) RevertToSnapshot(int) {}
func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) }
func (self *Env) Difficulty() *big.Int { return big.NewInt(0) }
func (self *Env) Db() Database { return nil }
func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) VmType() Type { return StdVmTy }
func (self *Env) GetHash(n uint64) common.Hash {
return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
}
func (self *Env) AddLog(log *Log) {
}
func (self *Env) Depth() int { return self.depth }
func (self *Env) SetDepth(i int) { self.depth = i }
func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
return true
}
func (self *Env) Transfer(from, to Account, amount *big.Int) {}
func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return nil, nil
}
func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return nil, nil
}
func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return nil, common.Address{}, nil
}
func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return nil, nil
}

View file

@ -64,7 +64,7 @@ type StructLog struct {
// Note that reference types are actual VM data structures; make copies // Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call. // if you need to retain them beyond the current call.
type Tracer interface { type Tracer interface {
CaptureState(env Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) CaptureState(env *Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error)
} }
// StructLogger is an EVM state logger and implements Tracer. // StructLogger is an EVM state logger and implements Tracer.
@ -93,7 +93,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
// captureState logs a new structured log message and pushes it out to the environment // captureState logs a new structured log message and pushes it out to the environment
// //
// captureState also tracks SSTORE ops to track dirty values. // captureState also tracks SSTORE ops to track dirty values.
func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) { func (l *StructLogger) CaptureState(env *Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) {
// initialise new changed values storage container for this contract // initialise new changed values storage container for this contract
// if not present. // if not present.
if l.changedValues[contract.Address()] == nil { if l.changedValues[contract.Address()] == nil {
@ -149,7 +149,7 @@ func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas,
} }
} }
// create a new snaptshot of the EVM. // create a new snaptshot of the EVM.
log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, env.Depth(), err} log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, env.Depth, err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
} }

View file

@ -23,11 +23,15 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type ruleset struct{}
func (ruleset) IsHomestead(*big.Int) bool { return true }
type dummyContractRef struct { type dummyContractRef struct {
calledForEach bool calledForEach bool
} }
func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {} func (dummyContractRef) ReturnGas(uint64) {}
func (dummyContractRef) Address() common.Address { return common.Address{} } func (dummyContractRef) Address() common.Address { return common.Address{} }
func (dummyContractRef) Value() *big.Int { return new(big.Int) } func (dummyContractRef) Value() *big.Int { return new(big.Int) }
func (dummyContractRef) SetCode(common.Hash, []byte) {} func (dummyContractRef) SetCode(common.Hash, []byte) {}
@ -41,13 +45,13 @@ 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 dummyEnv struct { type dummyEnv struct {
*Env *Environment
ref *dummyContractRef ref *dummyContractRef
} }
func newDummyEnv(ref *dummyContractRef) *dummyEnv { func newDummyEnv(ref *dummyContractRef) *dummyEnv {
return &dummyEnv{ return &dummyEnv{
Env: NewEnv(&Config{EnableJit: false, ForceJit: false}), Environment: NewEnvironment(Context{}, nil, ruleset{}, Config{}),
ref: ref, ref: ref,
} }
} }
@ -57,11 +61,11 @@ func (d dummyEnv) GetAccount(common.Address) Account {
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
env = NewEnv(&Config{EnableJit: false, ForceJit: false}) env = NewEnvironment(Context{}, nil, ruleset{}, Config{})
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
mem = NewMemory() mem = NewMemory()
stack = newstack() stack = newstack()
contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int), new(big.Int)) contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int))
) )
stack.push(big.NewInt(1)) stack.push(big.NewInt(1))
stack.push(big.NewInt(0)) stack.push(big.NewInt(0))
@ -83,20 +87,20 @@ func TestStorageCapture(t *testing.T) {
t.Skip("implementing this function is difficult. it requires all sort of interfaces to be implemented which isn't trivial. The value (the actual test) isn't worth it") t.Skip("implementing this function is difficult. it requires all sort of interfaces to be implemented which isn't trivial. The value (the actual test) isn't worth it")
var ( var (
ref = &dummyContractRef{} ref = &dummyContractRef{}
contract = NewContract(ref, ref, new(big.Int), new(big.Int), new(big.Int)) contract = NewContract(ref, ref, new(big.Int), new(big.Int))
env = newDummyEnv(ref) env = newDummyEnv(ref)
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
mem = NewMemory() mem = NewMemory()
stack = newstack() stack = newstack()
) )
logger.CaptureState(env, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil) logger.CaptureState(env.Environment, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil)
if ref.calledForEach { if ref.calledForEach {
t.Error("didn't expect for each to be called") t.Error("didn't expect for each to be called")
} }
logger = NewStructLogger(&LogConfig{FullStorage: true}) logger = NewStructLogger(&LogConfig{FullStorage: true})
logger.CaptureState(env, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil) logger.CaptureState(env.Environment, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil)
if !ref.calledForEach { if !ref.calledForEach {
t.Error("expected for each to be called") t.Error("expected for each to be called")
} }

View file

@ -21,10 +21,11 @@ import "fmt"
// 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 {
store []byte store []byte
cost uint64
} }
func NewMemory() *Memory { func NewMemory() *Memory {
return &Memory{nil} return &Memory{}
} }
// Set sets offset + size to value // Set sets offset + size to value

View file

@ -17,12 +17,13 @@
package vm package vm
import ( import (
"fmt" gmath "math"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
@ -39,7 +40,7 @@ const (
progReady // ready for use status progReady // ready for use status
progError // error status (usually caused during compilation) progError // error status (usually caused during compilation)
defaultJitMaxCache int = 64 // maximum amount of jit cached programs defaultJitMaxCache int = 1024 // maximum amount of jit cached programs
) )
var MaxProgSize int // Max cache size for JIT programs var MaxProgSize int // Max cache size for JIT programs
@ -81,8 +82,6 @@ type Program struct {
Id common.Hash // Id of the program Id common.Hash // Id of the program
status int32 // status should be accessed atomically status int32 // status should be accessed atomically
contract *Contract
instructions []programInstruction // instruction set instructions []programInstruction // instruction set
mapping map[uint64]uint64 // real PC mapping to array indices mapping map[uint64]uint64 // real PC mapping to array indices
destinations map[uint64]struct{} // cached jump destinations destinations map[uint64]struct{} // cached jump destinations
@ -124,17 +123,13 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) {
} }
// CompileProgram compiles the given program and return an error when it fails // CompileProgram compiles the given program and return an error when it fails
func CompileProgram(program *Program) (err error) { func CompileProgram(program *Program) {
if progStatus(atomic.LoadInt32(&program.status)) == progCompile { if progStatus(atomic.LoadInt32(&program.status)) == progCompile {
return nil return
} }
atomic.StoreInt32(&program.status, int32(progCompile)) atomic.StoreInt32(&program.status, int32(progCompile))
defer func() { defer func() {
if err != nil {
atomic.StoreInt32(&program.status, int32(progError))
} else {
atomic.StoreInt32(&program.status, int32(progReady)) atomic.StoreInt32(&program.status, int32(progReady))
}
}() }()
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
glog.Infof("compiling %x\n", program.Id[:4]) glog.Infof("compiling %x\n", program.Id[:4])
@ -291,56 +286,12 @@ func CompileProgram(program *Program) (err error) {
program.addInstr(op, pc, nil, nil) program.addInstr(op, pc, nil, nil)
} }
} }
// reset the program code. It's no longer required by the program itself.
optimiseProgram(program) //program.code = nil
return nil
} }
// RunProgram runs the program given the environment and contract and returns an func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) {
// error if the execution failed (non-consensus) return New(env, Config{}).Run(contract, input)
func RunProgram(program *Program, env Environment, contract *Contract, input []byte) ([]byte, error) {
return runProgram(program, 0, NewMemory(), newstack(), env, contract, input)
}
func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env Environment, contract *Contract, input []byte) ([]byte, error) {
contract.Input = input
var (
pc uint64 = program.mapping[pcstart]
instrCount = 0
)
if glog.V(logger.Debug) {
glog.Infof("running JIT program %x\n", program.Id[:4])
tstart := time.Now()
defer func() {
glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount)
}()
}
homestead := env.RuleSet().IsHomestead(env.BlockNumber())
for pc < uint64(len(program.instructions)) {
instrCount++
instr := program.instructions[pc]
if instr.Op() == DELEGATECALL && !homestead {
return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op())
}
ret, err := instr.do(program, &pc, env, contract, mem, stack)
if err != nil {
return nil, err
}
if instr.halts() {
return ret, nil
}
}
contract.Input = nil
return nil, nil
} }
// validDest checks if the given destination is a valid one given the // validDest checks if the given destination is a valid one given the
@ -355,16 +306,17 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool {
return ok return ok
} }
// jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory. // the operation. This does not reduce gas or resizes the memory.
func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) { func calculateGasAndSize(env *Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (uint64, uint64, error) {
var ( var (
gas = new(big.Int) newMemSize uint64
newMemSize *big.Int = new(big.Int) sizeFault bool
) )
err := jitBaseCheck(instr, stack, gas)
gas, err := baseCalc(instr, stack)
if err != nil { if err != nil {
return nil, nil, err return 0, 0, err
} }
// stack Check, memory resize & gas phase // stack Check, memory resize & gas phase
@ -373,40 +325,57 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
n := int(op - SWAP1 + 2) n := int(op - SWAP1 + 2)
err := stack.require(n) err := stack.require(n)
if err != nil { if err != nil {
return nil, nil, err return 0, 0, err
} }
gas.Set(GasFastestStep) gas = GasFastestStep64
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1) n := int(op - DUP1 + 1)
err := stack.require(n) err := stack.require(n)
if err != nil { if err != nil {
return nil, nil, err return 0, 0, err
} }
gas.Set(GasFastestStep) gas = GasFastestStep64
case LOG0, LOG1, LOG2, LOG3, LOG4: case LOG0, LOG1, LOG2, LOG3, LOG4:
n := int(op - LOG0) n := int(op - LOG0)
err := stack.require(n + 2) err := stack.require(n + 2)
if err != nil { if err != nil {
return nil, nil, err return 0, 0, err
} }
mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1] mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1]
if mSize.BitLen() > 64 {
return 0, 0, OutOfGasError
}
msize64 := mSize.Uint64()
add := new(big.Int) gas = (gas + LogGas64) + (uint64(n) * LogTopicGas64)
gas.Add(gas, params.LogGas) if !math.IsMulSafe(msize64, LogDataGas64) {
gas.Add(gas, add.Mul(big.NewInt(int64(n)), params.LogTopicGas)) return 0, 0, OutOfGasError
gas.Add(gas, add.Mul(mSize, params.LogDataGas)) }
gasLogData := msize64 * LogDataGas64
newMemSize = calcMemSize(mStart, mSize) if !math.IsAddSafe(gas, gasLogData) {
return 0, 0, OutOfGasError
}
gas += gasLogData
newMemSize, sizeFault = calcMemSize(mStart, mSize)
case EXP: case EXP:
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas)) x := uint64(len(stack.data[stack.len()-2].Bytes()))
if !math.IsMulSafe(x, ExpByteGas64) {
return 0, 0, OutOfGasError
}
x *= ExpByteGas64
if !math.IsAddSafe(gas, x) {
return 0, 0, OutOfGasError
}
gas += x
case SSTORE: case SSTORE:
err := stack.require(2) err := stack.require(2)
if err != nil { if err != nil {
return nil, nil, err return 0, 0, err
} }
var g *big.Int
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1] y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
val := statedb.GetState(contract.Address(), common.BigToHash(x)) val := statedb.GetState(contract.Address(), common.BigToHash(x))
@ -415,98 +384,166 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
// 2. From a non-zero value address to a zero-value address (DELETE) // 2. From a non-zero value address to a zero-value address (DELETE)
// 3. From a non-zero to a non-zero (CHANGE) // 3. From a non-zero to a non-zero (CHANGE)
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) {
g = params.SstoreSetGas gas = SstoreSetGas64
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { } else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
statedb.AddRefund(params.SstoreRefundGas) statedb.AddRefund(params.SstoreRefundGas)
g = params.SstoreClearGas gas = SstoreClearGas64
} else { } else {
g = params.SstoreResetGas gas = SstoreResetGas64
} }
gas.Set(g)
case SUICIDE: case SUICIDE:
if !statedb.HasSuicided(contract.Address()) { if !statedb.HasSuicided(contract.Address()) {
statedb.AddRefund(params.SuicideRefundGas) statedb.AddRefund(params.SuicideRefundGas)
} }
case MLOAD: case MLOAD:
newMemSize = calcMemSize(stack.peek(), u256(32)) newMemSize, sizeFault = calcMemSize(stack.peek(), u256(32))
case MSTORE8: case MSTORE8:
newMemSize = calcMemSize(stack.peek(), u256(1)) newMemSize, sizeFault = calcMemSize(stack.peek(), u256(1))
case MSTORE: case MSTORE:
newMemSize = calcMemSize(stack.peek(), u256(32)) newMemSize, sizeFault = calcMemSize(stack.peek(), u256(32))
case RETURN: case RETURN:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-2])
case SHA3: case SHA3:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-2])
if sizeFault {
return 0, 0, OutOfGasError
}
words := toWordSize(stack.data[stack.len()-2]) if stack.data[stack.len()-2].BitLen() > 64 {
gas.Add(gas, words.Mul(words, params.Sha3WordGas)) return 0, 0, OutOfGasError
}
words := toWordSize(stack.data[stack.len()-2].Uint64())
if !math.IsMulSafe(words, KeccakWordGas64) {
return 0, 0, OutOfGasError
}
wordsGas := words * KeccakWordGas64
if !math.IsAddSafe(gas, wordsGas) {
return 0, 0, OutOfGasError
}
gas += wordsGas
case CALLDATACOPY: case CALLDATACOPY:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-3])
if sizeFault {
return 0, 0, OutOfGasError
}
words := toWordSize(stack.data[stack.len()-3]) words := toWordSize(stack.data[stack.len()-3].Uint64())
gas.Add(gas, words.Mul(words, params.CopyGas)) if !math.IsMulSafe(words, CopyGas64) {
return 0, 0, OutOfGasError
}
wordsGas := words * CopyGas64
if !math.IsAddSafe(gas, wordsGas) {
return 0, 0, OutOfGasError
}
gas += wordsGas
case CODECOPY: case CODECOPY:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-3])
if sizeFault {
return 0, 0, OutOfGasError
}
words := toWordSize(stack.data[stack.len()-3]) words := toWordSize(stack.data[stack.len()-3].Uint64())
gas.Add(gas, words.Mul(words, params.CopyGas)) if !math.IsMulSafe(words, CopyGas64) {
return 0, 0, OutOfGasError
}
wordsGas := words * CopyGas64
if !math.IsAddSafe(gas, wordsGas) {
return 0, 0, OutOfGasError
}
gas += wordsGas
case EXTCODECOPY: case EXTCODECOPY:
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4]) newMemSize, sizeFault = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4])
if sizeFault {
words := toWordSize(stack.data[stack.len()-4]) return 0, 0, OutOfGasError
gas.Add(gas, words.Mul(words, params.CopyGas)) }
words := toWordSize(stack.data[stack.len()-4].Uint64())
if !math.IsMulSafe(words, CopyGas64) {
return 0, 0, OutOfGasError
}
wordsGas := words * CopyGas64
if !math.IsAddSafe(gas, wordsGas) {
return 0, 0, OutOfGasError
}
gas += wordsGas
case CREATE: case CREATE:
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3]) newMemSize, sizeFault = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3])
case CALL, CALLCODE: case CALL, CALLCODE:
gas.Add(gas, stack.data[stack.len()-1]) callGas := stack.data[stack.len()-1]
if callGas.BitLen() > 64 {
return 0, 0, OutOfGasError
}
gas += callGas.Uint64()
if op == CALL { if op == CALL {
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas) if !math.IsAddSafe(gas, CallNewAccountGas64) {
return 0, 0, OutOfGasError
}
gas += CallNewAccountGas64
} }
} }
if len(stack.data[stack.len()-3].Bytes()) > 0 { if len(stack.data[stack.len()-3].Bytes()) > 0 {
gas.Add(gas, params.CallValueTransferGas) if !math.IsAddSafe(gas, CallValueTransferGas64) {
return 0, 0, OutOfGasError
}
gas += CallValueTransferGas64
} }
x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) x, xSizeFault := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7])
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) if xSizeFault {
return 0, 0, OutOfGasError
}
y, ySizeFault := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5])
if ySizeFault {
return 0, 0, OutOfGasError
}
newMemSize = common.BigMax(x, y) newMemSize = x
if y > newMemSize {
newMemSize = y
}
case DELEGATECALL: case DELEGATECALL:
gas.Add(gas, stack.data[stack.len()-1]) callGas := stack.data[stack.len()-1]
if callGas.BitLen() > 64 {
x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) return 0, 0, OutOfGasError
y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4])
newMemSize = common.BigMax(x, y)
} }
quadMemGas(mem, newMemSize, gas) gas += callGas.Uint64()
return newMemSize, gas, nil x, xSizeFault := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6])
if xSizeFault {
return 0, 0, OutOfGasError
}
y, ySizeFault := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4])
if ySizeFault {
return 0, 0, OutOfGasError
}
newMemSize = uint64(gmath.Max(float64(x), float64(y)))
}
if sizeFault {
return 0, 0, OutOfGasError
}
memGas, _ := calcQuadMemGas(mem, newMemSize)
if !math.IsAddSafe(gas, memGas) {
return 0, 0, OutOfGasError
}
return toWordSize(newMemSize) * 32, gas + memGas, nil
} }
// jitBaseCheck is the same as baseCheck except it doesn't do the look up in the // waitCompile returns a new channel to broadcast the new result after
// gas table. This is done during compilation instead. // a compilation has started.
func jitBaseCheck(instr instruction, stack *Stack, gas *big.Int) error { func WaitCompile(id common.Hash) chan progStatus {
err := stack.require(instr.spop) ch := make(chan progStatus)
if err != nil { go func() {
return err defer close(ch)
for GetProgramStatus(id) == progCompile {
time.Sleep(time.Microsecond * 10)
} }
ch <- GetProgramStatus(id)
if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(params.StackLimit.Int64()) { }()
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) return ch
}
// nil on gas means no base calculation
if instr.gas == nil {
return nil
}
gas.Add(gas, instr.gas)
return nil
} }

View file

@ -17,47 +17,64 @@
package vm package vm
import ( import (
"fmt"
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
) )
type fnId string
// optimeProgram optimises a JIT program creating segments out of program // optimeProgram optimises a JIT program creating segments out of program
// instructions. Currently covered are multi-pushes and static jumps // instructions. Currently covered are multi-pushes and static jumps
func optimiseProgram(program *Program) { func OptimiseProgram(program *Program) {
var load []instruction var load []instruction
var ( var (
statsJump = 0 statsJump = 0
statsPush = 0 statsOldPush = 0
statsNewPush = 0
) )
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
glog.Infof("optimising %x\n", program.Id[:4]) glog.Infof("optimising %x\n", program.Id[:4])
tstart := time.Now() tstart := time.Now()
defer func() { defer func() {
glog.Infof("optimised %x done in %v with JMP: %d PSH: %d\n", program.Id[:4], time.Since(tstart), statsJump, statsPush) glog.Infof("optimised %x done in %v with JMP: %d PSH: %d/%d\n", program.Id[:4], time.Since(tstart), statsJump, statsNewPush, statsOldPush)
}() }()
} }
/*
code := Parse(program.code) code := Parse(program.code)
for _, test := range [][]OpCode{ for _, test := range [][]OpCode{
[]OpCode{PUSH, PUSH, ADD}, []OpCode{PUSH, DUP, EQ, PUSH, JUMPI},
[]OpCode{PUSH, PUSH, SUB},
[]OpCode{PUSH, PUSH, MUL},
[]OpCode{PUSH, PUSH, DIV},
} { } {
matchCount := 0 matchCount := 0
MatchFn(code, test, func(i int) bool {
matchCount++
return true
})
fmt.Printf("found %d match count on: %v\n", matchCount, test) fmt.Printf("found %d match count on: %v\n", matchCount, test)
} }
*/
MatchFn(code, []OpCode{PUSH, PUSH, EXP}, func(i int) bool {
// TODO optimise this instruction
return true
})
funcTable := make(map[fnId]uint64)
MatchFn(code, []OpCode{DUP, PUSH, EQ, PUSH, JUMPI}, func(i int) bool {
pushOp := code[i+1]
size := int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
funcId := fnId(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size)))
pushOp = code[i+3]
size = int64(program.code[pushOp.pc]) - int64(PUSH1) + 1
position := common.Bytes2Big(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))).Uint64()
glog.Infof("jumpTable entry: %x => %d\n", funcId, position)
funcTable[funcId] = position
return true
})
for i := 0; i < len(program.instructions); i++ { for i := 0; i < len(program.instructions); i++ {
instr := program.instructions[i].(instruction) instr := program.instructions[i].(instruction)
@ -65,6 +82,7 @@ func optimiseProgram(program *Program) {
switch { switch {
case instr.op.IsPush(): case instr.op.IsPush():
load = append(load, instr) load = append(load, instr)
statsOldPush++
case instr.op.IsStaticJump(): case instr.op.IsStaticJump():
if len(load) == 0 { if len(load) == 0 {
continue continue
@ -74,7 +92,7 @@ func optimiseProgram(program *Program) {
if len(load) > 2 { if len(load) > 2 {
seg, size := makePushSeg(load[:len(load)-1]) seg, size := makePushSeg(load[:len(load)-1])
program.instructions[i-size-1] = seg program.instructions[i-size-1] = seg
statsPush++ statsNewPush++
} }
// create a segment consisting of a pre determined // create a segment consisting of a pre determined
// jump, destination and validity. // jump, destination and validity.
@ -88,7 +106,7 @@ func optimiseProgram(program *Program) {
if len(load) > 1 { if len(load) > 1 {
seg, size := makePushSeg(load) seg, size := makePushSeg(load)
program.instructions[i-size] = seg program.instructions[i-size] = seg
statsPush++ statsNewPush++
} }
load = nil load = nil
} }
@ -99,12 +117,12 @@ func optimiseProgram(program *Program) {
func makePushSeg(instrs []instruction) (pushSeg, int) { func makePushSeg(instrs []instruction) (pushSeg, int) {
var ( var (
data []*big.Int data []*big.Int
gas = new(big.Int) gas uint64
) )
for _, instr := range instrs { for _, instr := range instrs {
data = append(data, instr.data) data = append(data, instr.data)
gas.Add(gas, instr.gas) gas += instr.gas
} }
return pushSeg{data, gas}, len(instrs) return pushSeg{data, gas}, len(instrs)
@ -113,9 +131,7 @@ func makePushSeg(instrs []instruction) (pushSeg, int) {
// makeStaticJumpSeg creates a new static jump segment from a predefined // makeStaticJumpSeg creates a new static jump segment from a predefined
// destination (PUSH, JUMP). // destination (PUSH, JUMP).
func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg { func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg {
gas := new(big.Int) gas := _baseCheck[PUSH1].gas + _baseCheck[JUMP].gas
gas.Add(gas, _baseCheck[PUSH1].gas)
gas.Add(gas, _baseCheck[JUMP].gas)
contract := &Contract{Code: program.code} contract := &Contract{Code: program.code}
pos, err := jump(program.mapping, program.destinations, contract, to) pos, err := jump(program.mapping, program.destinations, contract, to)

93
core/vm/program_test.go Normal file
View file

@ -0,0 +1,93 @@
// Copyright 2015 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"
const maxRun = 1000
func TestSegmenting(t *testing.T) {
prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0})
CompileProgram(prog)
OptimiseProgram(prog)
if instr, ok := prog.instructions[0].(pushSeg); ok {
if len(instr.data) != 2 {
t.Error("expected 2 element width pushSegment, got", len(instr.data))
}
} else {
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
}
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
CompileProgram(prog)
OptimiseProgram(prog)
if _, ok := prog.instructions[1].(jumpSeg); ok {
} else {
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
}
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
CompileProgram(prog)
OptimiseProgram(prog)
if instr, ok := prog.instructions[0].(pushSeg); ok {
if len(instr.data) != 2 {
t.Error("expected 2 element width pushSegment, got", len(instr.data))
}
} else {
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
}
if _, ok := prog.instructions[2].(jumpSeg); ok {
} else {
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
}
}
func TestCompiling(t *testing.T) {
prog := NewProgram([]byte{0x60, 0x10})
CompileProgram(prog)
if len(prog.instructions) != 1 {
t.Error("expected 1 compiled instruction, got", len(prog.instructions))
}
}
/*
func TestResetInput(t *testing.T) {
var sender Account
env := NewEnv(&Config{})
contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000))
contract.CodeAddr = &common.Address{}
program := NewProgram([]byte{})
RunProgram(program, env, contract, []byte{0xbe, 0xef})
if contract.Input != nil {
t.Errorf("expected input to be nil, got %x", contract.Input)
}
}
*/
func TestPcMappingToInstruction(t *testing.T) {
program := NewProgram([]byte{byte(PUSH2), 0xbe, 0xef, byte(ADD)})
CompileProgram(program)
if program.mapping[3] != 1 {
t.Error("expected mapping PC 4 to me instr no. 2, got", program.mapping[4])
}
}

View file

@ -16,23 +16,28 @@
package vm package vm
type parsedOp struct {
op OpCode
pc uint64
}
// Parse parses all opcodes from the given code byte slice. This function // Parse parses all opcodes from the given code byte slice. This function
// performs no error checking and may return non-existing opcodes. // performs no error checking and may return non-existing opcodes.
func Parse(code []byte) (opcodes []OpCode) { func Parse(code []byte) (opcodes []parsedOp) {
for pc := uint64(0); pc < uint64(len(code)); pc++ { for pc := uint64(0); pc < uint64(len(code)); pc++ {
op := OpCode(code[pc]) op := OpCode(code[pc])
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:
a := uint64(op) - uint64(PUSH1) + 1 a := uint64(op) - uint64(PUSH1) + 1
opcodes = append(opcodes, parsedOp{PUSH, pc})
pc += a pc += a
opcodes = append(opcodes, PUSH)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
opcodes = append(opcodes, DUP) opcodes = append(opcodes, parsedOp{DUP, pc})
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
opcodes = append(opcodes, SWAP) opcodes = append(opcodes, parsedOp{SWAP, pc})
default: default:
opcodes = append(opcodes, op) opcodes = append(opcodes, parsedOp{op, pc})
} }
} }
@ -43,7 +48,7 @@ func Parse(code []byte) (opcodes []OpCode) {
// an appropriate match. matcherFn yields the starting position in the input. // an appropriate match. matcherFn yields the starting position in the input.
// MatchFn will continue to search for a match until it reaches the end of the // MatchFn will continue to search for a match until it reaches the end of the
// buffer or if matcherFn return false. // buffer or if matcherFn return false.
func MatchFn(input, match []OpCode, matcherFn func(int) bool) { func MatchFn(input []parsedOp, match []OpCode, matcherFn func(int) bool) {
// short circuit if either input or match is empty or if the match is // short circuit if either input or match is empty or if the match is
// greater than the input // greater than the input
if len(input) == 0 || len(match) == 0 || len(match) > len(input) { if len(input) == 0 || len(match) == 0 || len(match) > len(input) {
@ -51,11 +56,11 @@ func MatchFn(input, match []OpCode, matcherFn func(int) bool) {
} }
main: main:
for i, op := range input[:len(input)+1-len(match)] { for i, parsedOp := range input[:len(input)+1-len(match)] {
// match first opcode and continue search // match first opcode and continue search
if op == match[0] { if parsedOp.op == match[0] {
for j := 1; j < len(match); j++ { for j := 1; j < len(match); j++ {
if input[i+j] != match[j] { if input[i+j].op != match[j] {
continue main continue main
} }
} }

View file

@ -1,113 +0,0 @@
// Copyright 2015 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 runtime
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
)
// Env is a basic runtime environment required for running the EVM.
type Env struct {
ruleSet vm.RuleSet
depth int
state *state.StateDB
origin common.Address
coinbase common.Address
number *big.Int
time *big.Int
difficulty *big.Int
gasLimit *big.Int
getHashFn func(uint64) common.Hash
evm *vm.EVM
}
// NewEnv returns a new vm.Environment
func NewEnv(cfg *Config, state *state.StateDB) vm.Environment {
env := &Env{
ruleSet: cfg.RuleSet,
state: state,
origin: cfg.Origin,
coinbase: cfg.Coinbase,
number: cfg.BlockNumber,
time: cfg.Time,
difficulty: cfg.Difficulty,
gasLimit: cfg.GasLimit,
}
env.evm = vm.New(env, vm.Config{
Debug: cfg.Debug,
EnableJit: !cfg.DisableJit,
ForceJit: !cfg.DisableJit,
})
return env
}
func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet }
func (self *Env) Vm() vm.Vm { return self.evm }
func (self *Env) Origin() common.Address { return self.origin }
func (self *Env) BlockNumber() *big.Int { return self.number }
func (self *Env) Coinbase() common.Address { return self.coinbase }
func (self *Env) Time() *big.Int { return self.time }
func (self *Env) Difficulty() *big.Int { return self.difficulty }
func (self *Env) Db() vm.Database { return self.state }
func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) VmType() vm.Type { return vm.StdVmTy }
func (self *Env) GetHash(n uint64) common.Hash {
return self.getHashFn(n)
}
func (self *Env) AddLog(log *vm.Log) {
self.state.AddLog(log)
}
func (self *Env) Depth() int { return self.depth }
func (self *Env) SetDepth(i int) { self.depth = i }
func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
return self.state.GetBalance(from).Cmp(balance) >= 0
}
func (self *Env) SnapshotDatabase() int {
return self.state.Snapshot()
}
func (self *Env) RevertToSnapshot(snapshot int) {
self.state.RevertToSnapshot(snapshot)
}
func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {
core.Transfer(from, to, amount)
}
func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return core.Call(self, caller, addr, data, gas, price, value)
}
func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return core.CallCode(self, caller, addr, data, gas, price, value)
}
func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return core.DelegateCall(self, me, addr, data, gas, price)
}
func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return core.Create(self, caller, data, gas, price, value)
}

View file

@ -17,10 +17,12 @@
package runtime package runtime
import ( import (
"math"
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"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"
@ -41,7 +43,7 @@ type Config struct {
Coinbase common.Address Coinbase common.Address
BlockNumber *big.Int BlockNumber *big.Int
Time *big.Int Time *big.Int
GasLimit *big.Int GasLimit uint64
GasPrice *big.Int GasPrice *big.Int
Value *big.Int Value *big.Int
DisableJit bool // "disable" so it's enabled by default DisableJit bool // "disable" so it's enabled by default
@ -63,8 +65,8 @@ func setDefaults(cfg *Config) {
if cfg.Time == nil { if cfg.Time == nil {
cfg.Time = big.NewInt(time.Now().Unix()) cfg.Time = big.NewInt(time.Now().Unix())
} }
if cfg.GasLimit == nil { if cfg.GasLimit == 0 {
cfg.GasLimit = new(big.Int).Set(common.MaxBig) cfg.GasLimit = math.MaxUint64
} }
if cfg.GasPrice == nil { if cfg.GasPrice == nil {
cfg.GasPrice = new(big.Int) cfg.GasPrice = new(big.Int)
@ -98,8 +100,26 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
cfg.State, _ = state.New(common.Hash{}, db) cfg.State, _ = state.New(common.Hash{}, db)
} }
var chainConfig = &core.ChainConfig{}
backend := &core.EVMBackend{
GetHashFn: cfg.GetHashFn,
State: cfg.State,
}
context := vm.Context{
CallContext: core.EVMCallContext{core.CanTransfer, core.Transfer},
Origin: cfg.Origin,
Coinbase: cfg.Coinbase,
BlockNumber: cfg.BlockNumber,
Time: cfg.Time,
Difficulty: cfg.Difficulty,
GasLimit: new(big.Int).SetUint64(cfg.GasLimit),
GasPrice: cfg.GasPrice,
}
vmenv := vm.NewEnvironment(context, backend, chainConfig, chainConfig.VmConfig)
var ( var (
vmenv = NewEnv(cfg, cfg.State)
sender = cfg.State.CreateAccount(cfg.Origin) sender = cfg.State.CreateAccount(cfg.Origin)
receiver = cfg.State.CreateAccount(common.StringToAddress("contract")) receiver = cfg.State.CreateAccount(common.StringToAddress("contract"))
) )
@ -111,8 +131,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
sender, sender,
receiver.Address(), receiver.Address(),
input, input,
cfg.GasLimit, new(big.Int).SetUint64(cfg.GasLimit),
cfg.GasPrice,
cfg.Value, cfg.Value,
) )
@ -127,7 +146,23 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) {
setDefaults(cfg) setDefaults(cfg)
vmenv := NewEnv(cfg, cfg.State) var chainConfig = &core.ChainConfig{}
backend := &core.EVMBackend{
GetHashFn: cfg.GetHashFn,
State: cfg.State,
}
context := vm.Context{
CallContext: core.EVMCallContext{core.CanTransfer, core.Transfer},
Origin: cfg.Origin,
Coinbase: cfg.Coinbase,
BlockNumber: cfg.BlockNumber,
Time: cfg.Time,
Difficulty: cfg.Difficulty,
GasLimit: new(big.Int).SetUint64(cfg.GasLimit),
GasPrice: cfg.GasPrice,
}
vmenv := vm.NewEnvironment(context, backend, chainConfig, chainConfig.VmConfig)
sender := cfg.State.GetOrNewStateObject(cfg.Origin) sender := cfg.State.GetOrNewStateObject(cfg.Origin)
// Call the code with the given configuration. // Call the code with the given configuration.
@ -135,8 +170,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) {
sender, sender,
address, address,
input, input,
cfg.GasLimit, new(big.Int).SetUint64(cfg.GasLimit),
cfg.GasPrice,
cfg.Value, cfg.Value,
) )

View file

@ -39,8 +39,8 @@ func TestDefaults(t *testing.T) {
if cfg.Time == nil { if cfg.Time == nil {
t.Error("expected time to be non nil") t.Error("expected time to be non nil")
} }
if cfg.GasLimit == nil { if cfg.GasLimit == 0 {
t.Error("expected time to be non nil") t.Error("expected time to be non 0")
} }
if cfg.GasPrice == nil { if cfg.GasPrice == nil {
t.Error("expected time to be non nil") t.Error("expected time to be non nil")

View file

@ -16,15 +16,22 @@
package vm package vm
import "math/big" import (
"fmt"
"math/big"
)
type jumpSeg struct { type jumpSeg struct {
pos uint64 pos uint64
err error err error
gas *big.Int gas uint64
} }
func (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func (js jumpSeg) String() string {
return fmt.Sprintf("[JUMP SEG: %d]", js.pos)
}
func (j jumpSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
if !contract.UseGas(j.gas) { if !contract.UseGas(j.gas) {
return nil, OutOfGasError return nil, OutOfGasError
} }
@ -39,10 +46,18 @@ func (s jumpSeg) Op() OpCode { return 0 }
type pushSeg struct { type pushSeg struct {
data []*big.Int data []*big.Int
gas *big.Int gas uint64
} }
func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func (ps pushSeg) String() string {
var ret string
for _, num := range ps.data {
ret += fmt.Sprintf("PUSH %v\n", num)
}
return ret
}
func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// Use the calculated gas. When insufficient gas is present, use all gas and return an // Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error // Out Of Gas error
if !contract.UseGas(s.gas) { if !contract.UseGas(s.gas) {
@ -58,3 +73,26 @@ func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Con
func (s pushSeg) halts() bool { return false } func (s pushSeg) halts() bool { return false }
func (s pushSeg) Op() OpCode { return 0 } func (s pushSeg) Op() OpCode { return 0 }
//CALLDATASIZE, ISZERO, PUSH2, JUMPI
//if len(calldata) > 0 {
// *pc = T.pos
//}
//PUSH 224, PUSH 2, EXP, PUSH 0, CALLDATALOAD, DIV
//calldata[:4]
//PUSH4, DUP2, EQ, PUSH2, JUMP
/*
if calldata[:4] == (PUSH4)
else if calldata[:4] == (PUSH2) ????
else if calldata[:4] == (PUSH2) ????
type programJumpTable map[funcId]dest
if len(calldata) > 0 {
if ppc, exist := programJumpTable[string(calldata[:4])]; exit {
*pc = ppc
}
}
*/

View file

@ -18,21 +18,22 @@ package vm
import ( import (
"fmt" "fmt"
"math/big" "os"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
) )
// Config are the configuration options for the EVM // Config are the configuration options for the EVM
type Config struct { type Config struct {
Test bool // boolean to indicate this is a VM Test
ForceCompile bool
NoOptimise bool // skip optimising the program if it hasn't been compiled
Debug bool Debug bool
EnableJit bool
ForceJit bool
Tracer Tracer Tracer Tracer
} }
@ -41,28 +42,28 @@ type Config struct {
// The EVM will run the byte code VM or JIT VM based on the passed // The EVM will run the byte code VM or JIT VM based on the passed
// configuration. // configuration.
type EVM struct { type EVM struct {
env Environment env *Environment
jumpTable vmJumpTable jumpTable vmJumpTable
cfg Config cfg Config
} }
// New returns a new instance of the EVM. // New returns a new instance of the EVM.
func New(env Environment, cfg Config) *EVM { func New(env *Environment, cfg Config) *EVM {
return &EVM{ return &EVM{
env: env, env: env,
jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber()), jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber),
cfg: cfg, cfg: cfg,
} }
} }
// Run loops and evaluates the contract's code with the given input data // Run loops and evaluates the contract's code with the given input data
func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { func (evm *EVM) Run(contract *Contract, input []byte) ([]byte, error) {
evm.env.SetDepth(evm.env.Depth() + 1) evm.env.Depth++
defer evm.env.SetDepth(evm.env.Depth() - 1) defer func() { evm.env.Depth-- }()
if contract.CodeAddr != nil { if contract.CodeAddr != nil {
if p := Precompiled[contract.CodeAddr.Str()]; p != nil { if p, exist := PrecompiledContracts[*contract.CodeAddr]; exist {
return evm.RunPrecompiled(p, input, contract) return RunPrecompiled(p, input, contract)
} }
} }
@ -71,310 +72,88 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
return nil, nil return nil, nil
} }
codehash := contract.CodeHash // codehash is used when doing jump dest caching codehash := contract.CodeHash // codehash is used as an identifier for the programs
if codehash == (common.Hash{}) { if codehash == (common.Hash{}) {
codehash = crypto.Keccak256Hash(contract.Code) codehash = crypto.Keccak256Hash(contract.Code)
} }
var program *Program
if evm.cfg.EnableJit {
// If the JIT is enabled check the status of the JIT program, // If the JIT is enabled check the status of the JIT program,
// if it doesn't exist compile a new program in a separate // if it doesn't exist compile a new program in a separate
// goroutine or wait for compilation to finish if the JIT is // goroutine or wait for compilation to finish if the JIT is
// forced. // forced.
switch GetProgramStatus(codehash) { switch GetProgramStatus(codehash) {
case progReady: case progReady:
return RunProgram(GetProgram(codehash), evm.env, contract, input) return evm.runProgram(GetProgram(codehash), contract, input)
case progUnknown: case progUnknown:
if evm.cfg.ForceJit {
// Create and compile program // Create and compile program
program = NewProgram(contract.Code) program := NewProgram(contract.Code)
perr := CompileProgram(program) CompileProgram(program)
if perr == nil { if !evm.cfg.NoOptimise {
return RunProgram(program, evm.env, contract, input) OptimiseProgram(program)
}
glog.V(logger.Info).Infoln("error compiling program", err)
} else {
// create and compile the program. Compilation
// is done in a separate goroutine
program = NewProgram(contract.Code)
go func() {
err := CompileProgram(program)
if err != nil {
glog.V(logger.Info).Infoln("error compiling program", err)
return
}
}()
}
}
} }
var ( return evm.runProgram(program, contract, input)
caller = contract.caller case progCompile:
code = contract.Code // if the program is already compling wait for the compilation to finish
instrCount = 0 // and use the program instead of defaulting to the regular byte code vm.
<-WaitCompile(codehash)
op OpCode // current opcode return evm.runProgram(GetProgram(codehash), contract, input)
mem = NewMemory() // bound memory
stack = newstack() // local stack
statedb = evm.env.Db() // current state
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
// jump evaluates and checks whether the given jump destination is a valid one
// if valid move the `pc` otherwise return an error.
jump = func(from uint64, to *big.Int) error {
if !contract.jumpdests.has(codehash, code, to) {
nop := contract.GetOp(to.Uint64())
return fmt.Errorf("invalid jump destination (%v) %v", nop, to)
} }
return nil, fmt.Errorf("Unexpected return using program %x", codehash)
}
pc = to.Uint64() func (evm *EVM) runProgram(program *Program, contract *Contract, input []byte) ([]byte, error) {
return nil
}
newMemSize *big.Int
cost *big.Int
)
contract.Input = input contract.Input = input
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return. var (
defer func() { pc uint64 = 0 //program.mapping[pcstart]
if err != nil && evm.cfg.Debug { instrCount uint64 = 0
evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), err) mem = NewMemory()
} stack = newstack()
}() env = evm.env
)
if glog.V(logger.Debug) { if glog.V(logger.Debug) {
glog.Infof("running byte VM %x\n", codehash[:4]) glog.Infof("running JIT program %x\n", program.Id[:4])
tstart := time.Now() tstart := time.Now()
defer func() { defer func() {
glog.Infof("byte VM %x done. time: %v instrc: %v\n", codehash[:4], time.Since(tstart), instrCount) glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount)
}() }()
} }
for ; ; instrCount++ { var ops []OpCode
/* defer func() {
if EnableJit && it%100 == 0 { if len(ops) > 0 {
if program != nil && progStatus(atomic.LoadInt32(&program.status)) == progReady { fmt.Printf("%x\n", input)
// move execution fmt.Printf("%d\n", ops)
fmt.Println("moved", it) os.Exit(1)
glog.V(logger.Info).Infoln("Moved execution to JIT")
return runProgram(program, pc, mem, stack, evm.env, contract, input)
} }
} }()
*/ homestead := env.RuleSet().IsHomestead(env.BlockNumber)
for pc < uint64(len(program.instructions)) {
instrCount++
// Get the memory location of pc instr := program.instructions[pc]
op = contract.GetOp(pc) //fmt.Println(instr)
// calculate the new memory size and gas price for the current executing opcode ops = append(ops, instr.Op())
newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack) if instr.Op() == DELEGATECALL && !homestead {
return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op())
}
ret, err := instr.do(program, &pc, env, contract, mem, stack)
if err != nil { if err != nil {
//gas := new(big.Int).SetUint64(contract.gas64)
//evm.cfg.Tracer.CaptureState(evm.env, pc, instr.Op(), gas, cost, mem, stack, contract, evm.env.Depth(), err)
return nil, err return nil, err
} }
// Use the calculated gas. When insufficient gas is present, use all gas and return an if instr.halts() {
// Out Of Gas error
if !contract.UseGas(cost) {
return nil, OutOfGasError
}
// Resize the memory calculated previously
mem.Resize(newMemSize.Uint64())
// Add a log message
if evm.cfg.Debug {
evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), nil)
}
if opPtr := evm.jumpTable[op]; opPtr.valid {
if opPtr.fn != nil {
opPtr.fn(instruction{}, &pc, evm.env, contract, mem, stack)
} else {
switch op {
case PC:
opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, evm.env, contract, mem, stack)
case JUMP:
if err := jump(pc, stack.pop()); err != nil {
return nil, err
}
continue
case JUMPI:
pos, cond := stack.pop(), stack.pop()
if cond.Cmp(common.BigTrue) >= 0 {
if err := jump(pc, pos); err != nil {
return nil, err
}
continue
}
case RETURN:
offset, size := stack.pop(), stack.pop()
ret := mem.GetPtr(offset.Int64(), size.Int64())
return ret, nil return ret, nil
case SUICIDE: }
opSuicide(instruction{}, nil, evm.env, contract, mem, stack) }
contract.Input = nil
fallthrough
case STOP: // Stop the contract
return nil, nil return nil, nil
}
}
} else {
return nil, fmt.Errorf("Invalid opcode %x", op)
}
pc++
}
}
// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory.
func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
)
err := baseCheck(op, stack, gas)
if err != nil {
return nil, nil, err
}
// stack Check, memory resize & gas phase
switch op {
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
n := int(op - SWAP1 + 2)
err := stack.require(n)
if err != nil {
return nil, nil, err
}
gas.Set(GasFastestStep)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1)
err := stack.require(n)
if err != nil {
return nil, nil, err
}
gas.Set(GasFastestStep)
case LOG0, LOG1, LOG2, LOG3, LOG4:
n := int(op - LOG0)
err := stack.require(n + 2)
if err != nil {
return nil, nil, err
}
mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1]
gas.Add(gas, params.LogGas)
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(n)), params.LogTopicGas))
gas.Add(gas, new(big.Int).Mul(mSize, params.LogDataGas))
newMemSize = calcMemSize(mStart, mSize)
case EXP:
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas))
case SSTORE:
err := stack.require(2)
if err != nil {
return nil, nil, err
}
var g *big.Int
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
val := statedb.GetState(contract.Address(), common.BigToHash(x))
// This checks for 3 scenario's and calculates gas accordingly
// 1. From a zero-value address to a non-zero value (NEW VALUE)
// 2. From a non-zero value address to a zero-value address (DELETE)
// 3. From a non-zero to a non-zero (CHANGE)
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) {
// 0 => non 0
g = params.SstoreSetGas
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
statedb.AddRefund(params.SstoreRefundGas)
g = params.SstoreClearGas
} else {
// non 0 => non 0 (or 0 => 0)
g = params.SstoreResetGas
}
gas.Set(g)
case SUICIDE:
if !statedb.HasSuicided(contract.Address()) {
statedb.AddRefund(params.SuicideRefundGas)
}
case MLOAD:
newMemSize = calcMemSize(stack.peek(), u256(32))
case MSTORE8:
newMemSize = calcMemSize(stack.peek(), u256(1))
case MSTORE:
newMemSize = calcMemSize(stack.peek(), u256(32))
case RETURN:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2])
case SHA3:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2])
words := toWordSize(stack.data[stack.len()-2])
gas.Add(gas, words.Mul(words, params.Sha3WordGas))
case CALLDATACOPY:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3])
words := toWordSize(stack.data[stack.len()-3])
gas.Add(gas, words.Mul(words, params.CopyGas))
case CODECOPY:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3])
words := toWordSize(stack.data[stack.len()-3])
gas.Add(gas, words.Mul(words, params.CopyGas))
case EXTCODECOPY:
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4])
words := toWordSize(stack.data[stack.len()-4])
gas.Add(gas, words.Mul(words, params.CopyGas))
case CREATE:
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3])
case CALL, CALLCODE:
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
}
}
if len(stack.data[stack.len()-3].Bytes()) > 0 {
gas.Add(gas, params.CallValueTransferGas)
}
x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7])
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5])
newMemSize = common.BigMax(x, y)
case DELEGATECALL:
gas.Add(gas, stack.data[stack.len()-1])
x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6])
y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4])
newMemSize = common.BigMax(x, y)
}
quadMemGas(mem, newMemSize, gas)
return newMemSize, gas, nil
}
// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go
func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.Gas(len(input))
if contract.UseGas(gas) {
ret = p.Call(input)
return ret, nil
} else {
return nil, OutOfGasError
}
} }

View file

@ -20,7 +20,7 @@ package vm
import "fmt" import "fmt"
func NewJitVm(env Environment) VirtualMachine { func NewJitVm(env *Environment) VirtualMachine {
fmt.Printf("Warning! EVM JIT not enabled.\n") fmt.Printf("Warning! EVM JIT not enabled.\n")
return New(env, Config{}) return New(env, Config{})
} }

View file

@ -1,117 +0,0 @@
// Copyright 2014 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 core
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)
// GetHashFn returns a function for which the VM env can query block hashes through
// up to the limit defined by the Yellow Paper and uses the given block chain
// to query for information.
func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash {
return func(n uint64) common.Hash {
for block := chain.GetBlockByHash(ref); block != nil; block = chain.GetBlock(block.ParentHash(), block.NumberU64()-1) {
if block.NumberU64() == n {
return block.Hash()
}
}
return common.Hash{}
}
}
type VMEnv struct {
chainConfig *ChainConfig // Chain configuration
state *state.StateDB // State to use for executing
evm *vm.EVM // The Ethereum Virtual Machine
depth int // Current execution depth
msg Message // Message appliod
header *types.Header // Header information
chain *BlockChain // Blockchain handle
getHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes
}
func NewEnv(state *state.StateDB, chainConfig *ChainConfig, chain *BlockChain, msg Message, header *types.Header, cfg vm.Config) *VMEnv {
env := &VMEnv{
chainConfig: chainConfig,
chain: chain,
state: state,
header: header,
msg: msg,
getHashFn: GetHashFn(header.ParentHash, chain),
}
env.evm = vm.New(env, cfg)
return env
}
func (self *VMEnv) RuleSet() vm.RuleSet { return self.chainConfig }
func (self *VMEnv) Vm() vm.Vm { return self.evm }
func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f }
func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number }
func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase }
func (self *VMEnv) Time() *big.Int { return self.header.Time }
func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty }
func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit }
func (self *VMEnv) Value() *big.Int { return self.msg.Value() }
func (self *VMEnv) Db() vm.Database { return self.state }
func (self *VMEnv) Depth() int { return self.depth }
func (self *VMEnv) SetDepth(i int) { self.depth = i }
func (self *VMEnv) GetHash(n uint64) common.Hash {
return self.getHashFn(n)
}
func (self *VMEnv) AddLog(log *vm.Log) {
self.state.AddLog(log)
}
func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
return self.state.GetBalance(from).Cmp(balance) >= 0
}
func (self *VMEnv) SnapshotDatabase() int {
return self.state.Snapshot()
}
func (self *VMEnv) RevertToSnapshot(snapshot int) {
self.state.RevertToSnapshot(snapshot)
}
func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
Transfer(from, to, amount)
}
func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return Call(self, me, addr, data, gas, price, value)
}
func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return CallCode(self, me, addr, data, gas, price, value)
}
func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return DelegateCall(self, me, addr, data, gas, price)
}
func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return Create(self, me, data, gas, price, value)
}

View file

@ -520,9 +520,17 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
value: tx.Value(), value: tx.Value(),
data: tx.Data(), data: tx.Data(),
} }
backend := &core.EVMBackend{
GetHashFn: core.GetHashFn(block.ParentHash(), api.eth.BlockChain()),
State: stateDb,
}
context := core.ToEVMContext(api.config, msg, block.Header())
// Mutate the state if we haven't reached the tracing transaction yet // Mutate the state if we haven't reached the tracing transaction yet
if uint64(idx) < txIndex { if uint64(idx) < txIndex {
vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{}) vmenv := vm.NewEnvironment(context, backend, api.config, vm.Config{})
//vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{})
_, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil { if err != nil {
return nil, fmt.Errorf("mutation failed: %v", err) return nil, fmt.Errorf("mutation failed: %v", err)
@ -531,7 +539,8 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
continue continue
} }
// Otherwise trace the transaction and return // Otherwise trace the transaction and return
vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Tracer: tracer}) vmenv := vm.NewEnvironment(context, backend, api.config, vm.Config{Debug: true, Tracer: tracer})
//vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Tracer: tracer})
ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil { if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err) return nil, fmt.Errorf("tracing failed: %v", err)

View file

@ -97,13 +97,20 @@ func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash) return b.eth.blockchain.GetTdByHash(blockHash)
} }
func (b *EthApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (vm.Environment, func() error, error) { func (b *EthApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (*vm.Environment, func() error, error) {
statedb := state.(EthApiState).state statedb := state.(EthApiState).state
addr, _ := msg.From() addr, _ := msg.From()
from := statedb.GetOrNewStateObject(addr) from := statedb.GetOrNewStateObject(addr)
from.SetBalance(common.MaxBig) from.SetBalance(common.MaxBig)
vmError := func() error { return nil } vmError := func() error { return nil }
return core.NewEnv(statedb, b.eth.chainConfig, b.eth.blockchain, msg, header, b.eth.chainConfig.VmConfig), vmError, nil
backend := &core.EVMBackend{
GetHashFn: core.GetHashFn(header.ParentHash, b.eth.blockchain),
State: statedb,
}
context := core.ToEVMContext(b.eth.chainConfig, msg, header)
return vm.NewEnvironment(context, backend, b.eth.chainConfig, b.eth.chainConfig.VmConfig), vmError, nil
} }
func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/common/registrar/ethreg" "github.com/ethereum/go-ethereum/common/registrar/ethreg"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
@ -202,10 +201,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
core.WriteChainConfig(chainDb, genesis.Hash(), config.ChainConfig) core.WriteChainConfig(chainDb, genesis.Hash(), config.ChainConfig)
eth.chainConfig = config.ChainConfig eth.chainConfig = config.ChainConfig
eth.chainConfig.VmConfig = vm.Config{
EnableJit: config.EnableJit,
ForceJit: config.ForceJit,
}
eth.blockchain, err = core.NewBlockChain(chainDb, eth.chainConfig, eth.pow, eth.EventMux()) eth.blockchain, err = core.NewBlockChain(chainDb, eth.chainConfig, eth.pow, eth.EventMux())
if err != nil { if err != nil {

11
fuzz/evm/main.go Normal file
View file

@ -0,0 +1,11 @@
package evm
import "github.com/ethereum/go-ethereum/core/vm/runtime"
func Fuzz(data []byte) int {
ret, _, err := runtime.Execute(data, data, &runtime.Config{GasLimit: 5000000})
if err != nil && ret != nil {
panic("ret != nil on error")
}
return 1
}

View file

@ -50,7 +50,7 @@ type Backend interface {
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
GetTd(blockHash common.Hash) *big.Int GetTd(blockHash common.Hash) *big.Int
GetVMEnv(ctx context.Context, msg core.Message, state State, header *types.Header) (vm.Environment, func() error, error) GetVMEnv(ctx context.Context, msg core.Message, state State, header *types.Header) (*vm.Environment, func() error, error)
// TxPool API // TxPool API
SendTx(ctx context.Context, signedTx *types.Transaction) error SendTx(ctx context.Context, signedTx *types.Transaction) error
RemoveTx(txHash common.Hash) RemoveTx(txHash common.Hash)

View file

@ -278,7 +278,7 @@ func wrapError(context string, err error) error {
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution // CaptureState implements the Tracer interface to trace a single step of VM execution
func (jst *JavascriptTracer) CaptureState(env vm.Environment, pc uint64, op vm.OpCode, gas, cost *big.Int, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) { func (jst *JavascriptTracer) CaptureState(env *vm.Environment, pc uint64, op vm.OpCode, gas, cost *big.Int, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) {
if jst.err == nil { if jst.err == nil {
jst.memory.memory = memory jst.memory.memory = memory
jst.stack.stack = stack jst.stack.stack = stack

View file

@ -24,8 +24,11 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"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"
) )
type ruleSet struct{} type ruleSet struct{}
@ -40,7 +43,7 @@ type Env struct {
func NewEnv(config *vm.Config) *Env { func NewEnv(config *vm.Config) *Env {
env := &Env{gasLimit: big.NewInt(10000), depth: 0} env := &Env{gasLimit: big.NewInt(10000), depth: 0}
env.evm = vm.New(env, *config) //env.evm = vm.New(env, *config)
return env return env
} }
@ -92,14 +95,20 @@ func (account) SetBalance(*big.Int) {}
func (account) SetNonce(uint64) {} func (account) SetNonce(uint64) {}
func (account) Balance() *big.Int { return nil } func (account) Balance() *big.Int { return nil }
func (account) Address() common.Address { return common.Address{} } func (account) Address() common.Address { return common.Address{} }
func (account) ReturnGas(*big.Int, *big.Int) {} func (account) ReturnGas(uint64) {}
func (account) SetCode(common.Hash, []byte) {} func (account) SetCode(common.Hash, []byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
func runTrace(tracer *JavascriptTracer) (interface{}, error) { func runTrace(tracer *JavascriptTracer) (interface{}, error) {
env := NewEnv(&vm.Config{Debug: true, Tracer: tracer}) db, _ := ethdb.NewMemDatabase()
st, _ := state.New(common.Hash{}, db)
backend := &core.EVMBackend{
GetHashFn: nil,
State: st,
}
env := vm.NewEnvironment(vm.Context{GasLimit: big.NewInt(1000000)}, backend, &ruleSet{}, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(account{}, account{}, big.NewInt(0), env.GasLimit(), big.NewInt(1)) contract := vm.NewContract(account{}, account{}, big.NewInt(0), env.GasLimit)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
_, err := env.Vm().Run(contract, []byte{}) _, err := env.Vm().Run(contract, []byte{})
@ -176,7 +185,7 @@ func TestHalt(t *testing.T) {
tracer.Stop(timeout) tracer.Stop(timeout)
}() }()
if _, err = runTrace(tracer); err.Error() != "stahp in server-side tracer function 'step'" { if _, err = runTrace(tracer); err == nil || (err != nil && err.Error() != "stahp in server-side tracer function 'step'") {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)
} }
} }
@ -187,8 +196,14 @@ func TestHaltBetweenSteps(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
env := NewEnv(&vm.Config{Debug: true, Tracer: tracer}) db, _ := ethdb.NewMemDatabase()
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), big.NewInt(0), big.NewInt(0)) st, _ := state.New(common.Hash{}, db)
backend := &core.EVMBackend{
GetHashFn: nil,
State: st,
}
env := vm.NewEnvironment(vm.Context{GasLimit: big.NewInt(1000000)}, backend, &ruleSet{}, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), big.NewInt(0))
tracer.CaptureState(env, 0, 0, big.NewInt(0), big.NewInt(0), nil, nil, contract, 0, nil) tracer.CaptureState(env, 0, 0, big.NewInt(0), big.NewInt(0), nil, nil, contract, 0, nil)
timeout := errors.New("stahp") timeout := errors.New("stahp")

View file

@ -621,13 +621,7 @@ func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, g
snap := env.state.Snapshot() snap := env.state.Snapshot()
// this is a bit of a hack to force jit for the miners // this is a bit of a hack to force jit for the miners
config := env.config.VmConfig receipt, logs, _, err := core.ApplyTransaction(env.config, bc, gp, env.state, env.header, tx, env.header.GasUsed, env.config.VmConfig)
if !(config.EnableJit && config.ForceJit) {
config.EnableJit = false
}
config.ForceJit = false // disable forcing jit
receipt, logs, _, err := core.ApplyTransaction(env.config, bc, gp, env.state, env.header, tx, env.header.GasUsed, config)
if err != nil { if err != nil {
env.state.RevertToSnapshot(snap) env.state.RevertToSnapshot(snap)
return err, nil return err, nil

View file

@ -21,6 +21,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"math"
"math/big" "math/big"
"strconv" "strconv"
"strings" "strings"
@ -103,13 +104,16 @@ func benchStateTest(ruleSet RuleSet, test VmTest, env map[string]string, b *test
} }
func runStateTests(ruleSet RuleSet, tests map[string]VmTest, skipTests []string) error { func runStateTests(ruleSet RuleSet, tests map[string]VmTest, skipTests []string) error {
//glog.SetToStderr(true)
//glog.SetV(6)
skipTest := make(map[string]bool, len(skipTests)) skipTest := make(map[string]bool, len(skipTests))
for _, name := range skipTests { for _, name := range skipTests {
skipTest[name] = true skipTest[name] = true
} }
for name, test := range tests { for name, test := range tests {
if skipTest[name] /*|| name != "callcodecallcode_11" */ { if skipTest[name] /*|| name != "gasPrice0"*/ {
glog.Infoln("Skipping state test", name) glog.Infoln("Skipping state test", name)
continue continue
} }
@ -149,6 +153,10 @@ func runStateTest(ruleSet RuleSet, test VmTest) error {
// err error // err error
logs vm.Logs logs vm.Logs
) )
if common.Big(test.Transaction["gasLimit"]).Cmp(new(big.Int).SetUint64(math.MaxUint64)) > 0 {
fmt.Println("skipping a test because of unsupported high gas")
return nil
}
ret, logs, _, _ = RunState(ruleSet, statedb, env, test.Transaction) ret, logs, _, _ = RunState(ruleSet, statedb, env, test.Transaction)
@ -219,20 +227,18 @@ func RunState(ruleSet RuleSet, statedb *state.StateDB, env, tx map[string]string
to = &t to = &t
} }
// Set pre compiled contracts // Set pre compiled contracts
vm.Precompiled = vm.PrecompiledContracts()
snapshot := statedb.Snapshot() snapshot := statedb.Snapshot()
gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"])) gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"]))
key, _ := hex.DecodeString(tx["secretKey"]) key, _ := hex.DecodeString(tx["secretKey"])
addr := crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey) addr := crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey)
message := NewMessage(addr, to, data, value, gas, price, nonce) message := NewMessage(addr, to, data, value, gas, price, nonce)
vmenv := NewEnvFromMap(ruleSet, statedb, env, tx) vmenv := NewEVMEnvironment(false, ruleSet, statedb, env, tx)
vmenv.origin = addr
ret, _, err := core.ApplyMessage(vmenv, message, gaspool) ret, _, err := core.ApplyMessage(vmenv, message, gaspool)
if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || core.IsGasLimitErr(err) { if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || core.IsGasLimitErr(err) {
statedb.RevertToSnapshot(snapshot) statedb.RevertToSnapshot(snapshot)
} }
statedb.Commit() statedb.Commit()
return ret, vmenv.state.Logs(), vmenv.Gas, err return ret, statedb.Logs(), gas, err
} }

View file

@ -18,6 +18,7 @@ package tests
import ( import (
"bytes" "bytes"
"encoding/hex"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
@ -157,139 +158,50 @@ func (r RuleSet) IsHomestead(n *big.Int) bool {
return n.Cmp(r.HomesteadBlock) >= 0 return n.Cmp(r.HomesteadBlock) >= 0
} }
type Env struct { func NewEVMEnvironment(vmTest bool, ruleSet RuleSet, state *state.StateDB, envValues map[string]string, exeValues map[string]string) *vm.Environment {
ruleSet RuleSet origin := common.HexToAddress(exeValues["caller"])
depth int if len(exeValues["secretKey"]) > 0 {
state *state.StateDB key, _ := hex.DecodeString(exeValues["secretKey"])
skipTransfer bool origin = crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey)
initial bool
Gas *big.Int
origin common.Address
parent common.Hash
coinbase common.Address
number *big.Int
time *big.Int
difficulty *big.Int
gasLimit *big.Int
vmTest bool
evm *vm.EVM
}
func NewEnv(ruleSet RuleSet, state *state.StateDB) *Env {
env := &Env{
ruleSet: ruleSet,
state: state,
} }
return env
}
func NewEnvFromMap(ruleSet RuleSet, state *state.StateDB, envValues map[string]string, exeValues map[string]string) *Env { context := vm.Context{
env := NewEnv(ruleSet, state) Origin: origin,
Coinbase: common.HexToAddress(envValues["currentCoinbase"]),
BlockNumber: common.Big(envValues["currentNumber"]),
Time: common.Big(envValues["currentTimestamp"]),
Difficulty: common.Big(envValues["currentDifficulty"]),
GasLimit: common.Big(envValues["currentGasLimit"]),
GasPrice: common.Big(exeValues["gasPrice"]),
}
env.origin = common.HexToAddress(exeValues["caller"]) //var initialCall bool
env.parent = common.HexToHash(envValues["previousHash"]) backend := &core.EVMBackend{
env.coinbase = common.HexToAddress(envValues["currentCoinbase"]) GetHashFn: func(n uint64) common.Hash {
env.number = common.Big(envValues["currentNumber"])
env.time = common.Big(envValues["currentTimestamp"])
env.difficulty = common.Big(envValues["currentDifficulty"])
env.gasLimit = common.Big(envValues["currentGasLimit"])
env.Gas = new(big.Int)
env.evm = vm.New(env, vm.Config{
EnableJit: EnableJit,
ForceJit: ForceJit,
})
return env
}
func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet }
func (self *Env) Vm() vm.Vm { return self.evm }
func (self *Env) Origin() common.Address { return self.origin }
func (self *Env) BlockNumber() *big.Int { return self.number }
func (self *Env) Coinbase() common.Address { return self.coinbase }
func (self *Env) Time() *big.Int { return self.time }
func (self *Env) Difficulty() *big.Int { return self.difficulty }
func (self *Env) Db() vm.Database { return self.state }
func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) VmType() vm.Type { return vm.StdVmTy }
func (self *Env) GetHash(n uint64) common.Hash {
return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String())))
} },
func (self *Env) AddLog(log *vm.Log) { State: state,
self.state.AddLog(log) }
} initialCall := true
func (self *Env) Depth() int { return self.depth } canTransfer := func(db vm.Database, address common.Address, amount *big.Int) bool {
func (self *Env) SetDepth(i int) { self.depth = i } if vmTest {
func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool { if initialCall {
if self.skipTransfer { initialCall = false
if self.initial {
self.initial = false
return true return true
} }
} }
return db.GetBalance(address).Cmp(amount) >= 0
return self.state.GetBalance(from).Cmp(balance) >= 0 }
} transfer := func(db vm.Database, sender, recipient common.Address, amount *big.Int) {
func (self *Env) SnapshotDatabase() int { if vmTest {
return self.state.Snapshot()
}
func (self *Env) RevertToSnapshot(snapshot int) {
self.state.RevertToSnapshot(snapshot)
}
func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {
if self.skipTransfer {
return return
} }
core.Transfer(from, to, amount) core.Transfer(db, sender, recipient, amount)
}
func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
if self.vmTest && self.depth > 0 {
caller.ReturnGas(gas, price)
return nil, nil
} }
ret, err := core.Call(self, caller, addr, data, gas, price, value) context.CallContext = core.EVMCallContext{canTransfer, transfer}
self.Gas = gas
return ret, err env := vm.NewEnvironment(context, backend, ruleSet, vm.Config{Test: vmTest})
return env
}
func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
if self.vmTest && self.depth > 0 {
caller.ReturnGas(gas, price)
return nil, nil
}
return core.CallCode(self, caller, addr, data, gas, price, value)
}
func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
if self.vmTest && self.depth > 0 {
caller.ReturnGas(gas, price)
return nil, nil
}
return core.DelegateCall(self, caller, addr, data, gas, price)
}
func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
if self.vmTest {
caller.ReturnGas(gas, price)
nonce := self.state.GetNonce(caller.Address())
obj := self.state.GetOrNewStateObject(crypto.CreateAddress(caller.Address(), nonce))
return nil, obj.Address(), nil
} else {
return core.Create(self, caller, data, gas, price, value)
}
} }
type Message struct { type Message struct {

View file

@ -128,9 +128,9 @@ func runVmTests(tests map[string]VmTest, skipTests []string) error {
} }
for name, test := range tests { for name, test := range tests {
if skipTest[name] { if skipTest[name] /*|| name != "57a9a2fbfe21a848e8d83379562a8513417c73d127495cf4abc5a44a5fe08e92"*/ {
glog.Infoln("Skipping VM test", name) glog.Infoln("Skipping VM test", name)
return nil continue
} }
if err := runVmTest(test); err != nil { if err := runVmTest(test); err != nil {
@ -211,25 +211,21 @@ func runVmTest(test VmTest) error {
return nil return nil
} }
func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, *big.Int, error) { func RunVm(statedb *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, *big.Int, error) {
var ( var (
to = common.HexToAddress(exec["address"]) to = common.HexToAddress(exec["address"])
from = common.HexToAddress(exec["caller"]) from = common.HexToAddress(exec["caller"])
data = common.FromHex(exec["data"]) data = common.FromHex(exec["data"])
gas = common.Big(exec["gas"]) gas = common.Big(exec["gas"])
price = common.Big(exec["gasPrice"])
value = common.Big(exec["value"]) value = common.Big(exec["value"])
) )
// Reset the pre-compiled contracts for VM tests. // Reset the pre-compiled contracts for VM tests.
vm.Precompiled = make(map[string]*vm.PrecompiledAccount) vm.PrecompiledContracts = make(map[common.Address]vm.PrecompiledContract)
caller := state.GetOrNewStateObject(from) caller := statedb.GetOrNewStateObject(from)
vmenv := NewEnvFromMap(RuleSet{params.MainNetHomesteadBlock, params.MainNetDAOForkBlock, true}, state, env, exec) vmenv := NewEVMEnvironment(true, RuleSet{params.MainNetHomesteadBlock, params.MainNetDAOForkBlock, true}, statedb, env, exec)
vmenv.vmTest = true ret, err := vmenv.Call(caller, to, data, gas, value)
vmenv.skipTransfer = true
vmenv.initial = true
ret, err := vmenv.Call(caller, to, data, gas, price, value)
return ret, vmenv.state.Logs(), vmenv.Gas, err return ret, statedb.Logs(), gas, err
} }