common/math, core/vm, core/state: EVM gas calculations to 64bit

This changes the internals of the EVM-JIT by dropping support for
blocks (and therefor also transactions) that can exceed a 64^2-1
gas-limit. This means that the EVM-JIT can drop support for abritrary
precision (*big.Int) and move to 64bit based gas calculations.

For obvious reasons integer overflows have added to make sure that
gas calculations such as gas for memory doesn't overflow 64bit. If the
overflow check fails it throws an out-of-gas error and will halt the
execution of the code.
This commit is contained in:
Jeffrey Wilcke 2016-05-14 17:08:38 +02:00
parent 3778f1bf77
commit 32b5ce9a32
26 changed files with 604 additions and 284 deletions

View file

@ -120,8 +120,7 @@ func run(ctx *cli.Context) error {
sender := statedb.CreateAccount(common.StringToAddress("sender")) 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)), common.Big(ctx.GlobalString(PriceFlag.Name)), vm.Config{
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), vm.Config{
Debug: ctx.GlobalBool(DebugFlag.Name), Debug: ctx.GlobalBool(DebugFlag.Name),
ForceJit: ctx.GlobalBool(ForceJitFlag.Name), ForceJit: ctx.GlobalBool(ForceJitFlag.Name),
EnableJit: !ctx.GlobalBool(DisableJitFlag.Name), EnableJit: !ctx.GlobalBool(DisableJitFlag.Name),
@ -141,7 +140,6 @@ 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 {
@ -152,7 +150,6 @@ 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)),
) )
} }
@ -200,18 +197,19 @@ type VMEnv struct {
value *big.Int value *big.Int
depth int depth int
Gas *big.Int Gas, price *big.Int
time *big.Int time *big.Int
logs []vm.StructLog logs []vm.StructLog
evm *vm.EVM evm *vm.EVM
} }
func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int, cfg vm.Config) *VMEnv { func NewEnv(state *state.StateDB, transactor common.Address, value, price *big.Int, cfg vm.Config) *VMEnv {
env := &VMEnv{ env := &VMEnv{
state: state, state: state,
transactor: &transactor, transactor: &transactor,
value: value, value: value,
price: price,
time: big.NewInt(time.Now().Unix()), time: big.NewInt(time.Now().Unix()),
} }
@ -237,6 +235,7 @@ func (self *VMEnv) Difficulty() *big.Int { return common.Big1 }
func (self *VMEnv) BlockHash() []byte { return make([]byte, 32) } func (self *VMEnv) BlockHash() []byte { return make([]byte, 32) }
func (self *VMEnv) Value() *big.Int { return self.value } func (self *VMEnv) Value() *big.Int { return self.value }
func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) } func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) }
func (self *VMEnv) GasPrice() *big.Int { return big.NewInt(1000000000) }
func (self *VMEnv) VmType() vm.Type { return vm.StdVmTy } func (self *VMEnv) VmType() vm.Type { return vm.StdVmTy }
func (self *VMEnv) Depth() int { return 0 } func (self *VMEnv) Depth() int { return 0 }
func (self *VMEnv) SetDepth(i int) { self.depth = i } func (self *VMEnv) SetDepth(i int) { self.depth = i }
@ -256,19 +255,19 @@ func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
core.Transfer(from, to, amount) core.Transfer(from, to, amount)
} }
func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
self.Gas = gas self.Gas = gas
return core.Call(self, caller, addr, data, gas, price, value) return core.Call(self, caller, addr, data, gas, value)
} }
func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return core.CallCode(self, caller, addr, data, gas, price, value) return core.CallCode(self, caller, addr, data, gas, value)
} }
func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
return core.DelegateCall(self, caller, addr, data, gas, price) return core.DelegateCall(self, caller, addr, data, gas)
} }
func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
return core.Create(self, caller, data, gas, price, value) return core.Create(self, caller, data, gas, value)
} }

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

@ -26,30 +26,30 @@ import (
) )
// 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 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, input, env.Db().GetCode(addr), gas, gasPrice, value) ret, _, err = exec(env, caller, &addr, &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 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, input, env.Db().GetCode(addr), gas, gasPrice, value) ret, _, err = exec(env, caller, &callerAddr, &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 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, input, env.Db().GetCode(addr), gas, gasPrice, callerValue) ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &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 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, nil, code, gas, gasPrice, value) ret, address, err = exec(env, caller, nil, nil, 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 +59,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, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, 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 !env.CanTransfer(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()))
} }
@ -104,7 +104,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
// 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, code) contract.SetCallCode(codeAddr, code)
defer contract.Finalise() defer contract.Finalise()
@ -116,7 +116,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
@ -127,7 +127,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
// 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.SetSnapshot(snapshotPreTransfer) env.SetSnapshot(snapshotPreTransfer)
} }
@ -135,12 +135,12 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
return ret, addr, err return ret, addr, err
} }
func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, 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
} }
@ -154,13 +154,13 @@ 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, code) contract.SetCallCode(codeAddr, 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.SetSnapshot(snapshot) env.SetSnapshot(snapshot)
} }

View file

@ -177,7 +177,7 @@ func (c *StateObject) St() Storage {
} }
// 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) Copy() *StateObject { func (self *StateObject) Copy() *StateObject {
stateObject := NewStateObject(self.Address(), self.db) stateObject := NewStateObject(self.Address(), self.db)

View file

@ -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)
} }

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([]byte) SetCode([]byte)
@ -47,7 +47,8 @@ type Contract struct {
Input []byte Input []byte
CodeAddr *common.Address CodeAddr *common.Address
value, Gas, UsedGas, Price *big.Int value, gas *big.Int
gas64 uint64
Args []byte Args []byte
@ -55,7 +56,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 {
@ -67,12 +68,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
} }
@ -101,6 +99,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
@ -112,24 +115,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

@ -53,6 +53,8 @@ type Environment interface {
Difficulty() *big.Int Difficulty() *big.Int
// The gas limit of the block // The gas limit of the block
GasLimit() *big.Int GasLimit() *big.Int
// The gas price of the current call
GasPrice() *big.Int
// Determines whether it's possible to transact // Determines whether it's possible to transact
CanTransfer(from common.Address, balance *big.Int) bool CanTransfer(from common.Address, balance *big.Int) bool
// Transfers amount from one account to the other // Transfers amount from one account to the other
@ -66,13 +68,13 @@ type Environment interface {
// Set the current calling depth // Set the current calling depth
SetDepth(i int) SetDepth(i int)
// Call another contract // Call another contract
Call(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) Call(me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error)
// Take another's contract code and execute within our own context // 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) CallCode(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 // 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) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error)
// Create a new contract // Create a new contract
Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) Create(me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error)
} }
// Vm is the basic interface for an implementation of the EVM. // Vm is the basic interface for an implementation of the EVM.
@ -116,7 +118,7 @@ type Account interface {
SetNonce(uint64) SetNonce(uint64)
Balance() *big.Int Balance() *big.Int
Address() common.Address Address() common.Address
ReturnGas(*big.Int, *big.Int) ReturnGas(uint64)
SetCode([]byte) SetCode([]byte)
ForEachStorage(cb func(key, value common.Hash) bool) ForEachStorage(cb func(key, value common.Hash) bool)
Value() *big.Int Value() *big.Int

View file

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -72,6 +73,41 @@ func toWordSize(size *big.Int) *big.Int {
return tmp return tmp
} }
// 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)
}
}
}
type req struct { type req struct {
stackPop int stackPop int
gas *big.Int gas *big.Int

View file

@ -42,7 +42,7 @@ 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
@ -71,7 +71,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
@ -316,7 +316,7 @@ 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))
} }
@ -396,7 +396,7 @@ func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Con
} }
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) {
@ -461,7 +461,7 @@ func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, m
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)
} }
@ -504,7 +504,7 @@ func opMsize(instr instruction, pc *uint64, env Environment, contract *Contract,
} }
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) {
@ -512,10 +512,10 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
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
@ -548,7 +548,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))
@ -579,7 +579,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))
@ -596,7 +596,7 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co
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 {

View file

@ -18,11 +18,13 @@ package vm
import ( import (
"fmt" "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"
@ -114,7 +116,7 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) {
if op >= DUP1 && op <= DUP16 { if op >= DUP1 && op <= DUP16 {
baseOp = DUP1 baseOp = DUP1
} }
base := _baseCheck[baseOp] base := _baseCheck64[baseOp]
returns := op == RETURN || op == SUICIDE || op == STOP returns := op == RETURN || op == SUICIDE || op == STOP
instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns} instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns}
@ -357,14 +359,15 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool {
// jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // jitCalculateGasAndSize 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 jitCalculateGasAndSize(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 := jitBaseCalc(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 +376,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 = calcMemSize64(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 +435,152 @@ 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.IsDeleted(contract.Address()) { if !statedb.IsDeleted(contract.Address()) {
statedb.AddRefund(params.SuicideRefundGas) statedb.AddRefund(params.SuicideRefundGas)
} }
case MLOAD: case MLOAD:
newMemSize = calcMemSize(stack.peek(), u256(32)) newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32))
case MSTORE8: case MSTORE8:
newMemSize = calcMemSize(stack.peek(), u256(1)) newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(1))
case MSTORE: case MSTORE:
newMemSize = calcMemSize(stack.peek(), u256(32)) newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32))
case RETURN: case RETURN:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-2])
case SHA3: case SHA3:
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) newMemSize, sizeFault = calcMemSize64(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 := toWordSize64(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 = calcMemSize64(stack.peek(), stack.data[stack.len()-3])
if sizeFault {
return 0, 0, OutOfGasError
}
words := toWordSize(stack.data[stack.len()-3]) words := toWordSize64(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 = calcMemSize64(stack.peek(), stack.data[stack.len()-3])
if sizeFault {
return 0, 0, OutOfGasError
}
words := toWordSize(stack.data[stack.len()-3]) words := toWordSize64(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 = calcMemSize64(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 := toWordSize64(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 = calcMemSize64(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 := calcMemSize64(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 := calcMemSize64(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 := calcMemSize64(stack.data[stack.len()-5], stack.data[stack.len()-6])
} if xSizeFault {
return 0, 0, OutOfGasError
// jitBaseCheck is the same as baseCheck except it doesn't do the look up in the }
// gas table. This is done during compilation instead. y, ySizeFault := calcMemSize64(stack.data[stack.len()-3], stack.data[stack.len()-4])
func jitBaseCheck(instr instruction, stack *Stack, gas *big.Int) error { if ySizeFault {
err := stack.require(instr.spop) return 0, 0, OutOfGasError
if err != nil { }
return err
} newMemSize = uint64(gmath.Max(float64(x), float64(y)))
}
if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(params.StackLimit.Int64()) { if sizeFault {
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) return 0, 0, OutOfGasError
} }
// nil on gas means no base calculation memGas, _ := calcQuadMemGas64(mem, newMemSize)
if instr.gas == nil { if !math.IsAddSafe(gas, memGas) {
return nil return 0, 0, OutOfGasError
} }
return toWordSize64(newMemSize) * 32, gas + memGas, nil
gas.Add(gas, instr.gas)
return nil
} }

View file

@ -31,14 +31,15 @@ func optimiseProgram(program *Program) {
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)
}() }()
} }
@ -65,6 +66,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 +76,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 +90,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 +101,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 +115,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 := _baseCheck64[PUSH1].gas + _baseCheck64[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)

View file

@ -29,10 +29,7 @@ const maxRun = 1000
func TestSegmenting(t *testing.T) { func TestSegmenting(t *testing.T) {
prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0}) prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0})
err := CompileProgram(prog) CompileProgram(prog)
if err != nil {
t.Fatal(err)
}
if instr, ok := prog.instructions[0].(pushSeg); ok { if instr, ok := prog.instructions[0].(pushSeg); ok {
if len(instr.data) != 2 { if len(instr.data) != 2 {
@ -43,20 +40,16 @@ func TestSegmenting(t *testing.T) {
} }
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
err = CompileProgram(prog) CompileProgram(prog)
if err != nil {
t.Fatal(err)
}
if _, ok := prog.instructions[1].(jumpSeg); ok { if _, ok := prog.instructions[1].(jumpSeg); ok {
} else { } else {
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1]) 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)}) prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
err = CompileProgram(prog) CompileProgram(prog)
if err != nil {
t.Fatal(err)
}
if instr, ok := prog.instructions[0].(pushSeg); ok { if instr, ok := prog.instructions[0].(pushSeg); ok {
if len(instr.data) != 2 { if len(instr.data) != 2 {
t.Error("expected 2 element width pushSegment, got", len(instr.data)) t.Error("expected 2 element width pushSegment, got", len(instr.data))
@ -72,10 +65,7 @@ func TestSegmenting(t *testing.T) {
func TestCompiling(t *testing.T) { func TestCompiling(t *testing.T) {
prog := NewProgram([]byte{0x60, 0x10}) prog := NewProgram([]byte{0x60, 0x10})
err := CompileProgram(prog) CompileProgram(prog)
if err != nil {
t.Error("didn't expect compile error")
}
if len(prog.instructions) != 1 { if len(prog.instructions) != 1 {
t.Error("expected 1 compiled instruction, got", len(prog.instructions)) t.Error("expected 1 compiled instruction, got", len(prog.instructions))
@ -85,8 +75,8 @@ func TestCompiling(t *testing.T) {
func TestResetInput(t *testing.T) { func TestResetInput(t *testing.T) {
var sender account var sender account
env := NewEnv(&Config{EnableJit: true, ForceJit: true}) env := NewEnv(true, true)
contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0)) contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000))
contract.CodeAddr = &common.Address{} contract.CodeAddr = &common.Address{}
program := NewProgram([]byte{}) program := NewProgram([]byte{})
@ -134,7 +124,7 @@ 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([]byte) {} func (account) SetCode([]byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
@ -149,7 +139,7 @@ func runVmBench(test vmBench, b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0)) context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000))
context.Code = test.code context.Code = test.code
context.CodeAddr = &common.Address{} context.CodeAddr = &common.Address{}
_, err := env.Vm().Run(context, test.input) _, err := env.Vm().Run(context, test.input)
@ -174,6 +164,7 @@ func NewEnv(config *Config) *Env {
func (self *Env) RuleSet() RuleSet { return ruleSet{new(big.Int)} } func (self *Env) RuleSet() RuleSet { return ruleSet{new(big.Int)} }
func (self *Env) Vm() Vm { return self.evm } func (self *Env) Vm() Vm { return self.evm }
func (self *Env) GasPrice() *big.Int { return new(big.Int) }
func (self *Env) Origin() common.Address { return common.Address{} } func (self *Env) Origin() common.Address { return common.Address{} }
func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) } func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) }
@ -197,15 +188,15 @@ func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
return true return true
} }
func (self *Env) Transfer(from, to Account, amount *big.Int) {} 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) { func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return nil, nil return nil, nil
} }
func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return nil, nil return nil, nil
} }
func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { func (self *Env) Create(caller ContractRef, data []byte, gas, price *big.Int) ([]byte, common.Address, error) {
return nil, common.Address{}, nil return nil, common.Address{}, nil
} }
func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
return nil, nil return nil, nil
} }

View file

@ -27,7 +27,7 @@ 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([]byte) {} func (dummyContractRef) SetCode([]byte) {}
@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) {
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,7 +83,7 @@ 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()

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

@ -38,6 +38,7 @@ type Env struct {
time *big.Int time *big.Int
difficulty *big.Int difficulty *big.Int
gasLimit *big.Int gasLimit *big.Int
gasPrice *big.Int
getHashFn func(uint64) common.Hash getHashFn func(uint64) common.Hash
@ -55,6 +56,7 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment {
time: cfg.Time, time: cfg.Time,
difficulty: cfg.Difficulty, difficulty: cfg.Difficulty,
gasLimit: cfg.GasLimit, gasLimit: cfg.GasLimit,
gasPrice: cfg.GasPrice,
} }
env.evm = vm.New(env, vm.Config{ env.evm = vm.New(env, vm.Config{
Debug: cfg.Debug, Debug: cfg.Debug,
@ -66,6 +68,7 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment {
} }
func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet } func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet }
func (self *Env) GasPrice() *big.Int { return self.gasPrice }
func (self *Env) Vm() vm.Vm { return self.evm } func (self *Env) Vm() vm.Vm { return self.evm }
func (self *Env) Origin() common.Address { return self.origin } func (self *Env) Origin() common.Address { return self.origin }
func (self *Env) BlockNumber() *big.Int { return self.number } func (self *Env) BlockNumber() *big.Int { return self.number }
@ -97,17 +100,17 @@ func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {
core.Transfer(from, to, amount) core.Transfer(from, to, amount)
} }
func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return core.Call(self, caller, addr, data, gas, price, value) return core.Call(self, caller, addr, data, gas, value)
} }
func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return core.CallCode(self, caller, addr, data, gas, price, value) return core.CallCode(self, caller, addr, data, gas, value)
} }
func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
return core.DelegateCall(self, me, addr, data, gas, price) return core.DelegateCall(self, me, addr, data, gas)
} }
func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
return core.Create(self, caller, data, gas, price, value) return core.Create(self, caller, data, gas, value)
} }

View file

@ -112,7 +112,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
receiver.Address(), receiver.Address(),
input, input,
cfg.GasLimit, cfg.GasLimit,
cfg.GasPrice,
cfg.Value, cfg.Value,
) )
@ -136,7 +135,6 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) {
address, address,
input, input,
cfg.GasLimit, cfg.GasLimit,
cfg.GasPrice,
cfg.Value, cfg.Value,
) )

View file

@ -21,7 +21,7 @@ import "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 (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
@ -39,7 +39,7 @@ 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 (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {

175
core/vm/uint64.go Normal file
View file

@ -0,0 +1,175 @@
package vm
import (
"fmt"
gmath "math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
var (
maxInt63 = new(big.Int).Exp(big.NewInt(2), big.NewInt(63), big.NewInt(0))
maxIntCap = new(big.Int).Sub(maxInt63, big.NewInt(1))
)
var (
StackLimit64 = params.StackLimit.Uint64()
GasQuickStep64 uint64 = 2
GasFastestStep64 uint64 = 3
GasFastStep64 uint64 = 5
GasMidStep64 uint64 = 8
GasSlowStep64 uint64 = 10
GasExtStep64 uint64 = 20
GasReturn64 uint64 = 0
GasStop64 uint64 = 0
GasContractByte64 uint64 = 200
LogGas64 = params.LogGas.Uint64()
LogTopicGas64 = params.LogTopicGas.Uint64()
LogDataGas64 = params.LogDataGas.Uint64()
ExpByteGas64 = params.ExpByteGas.Uint64()
SstoreSetGas64 = params.SstoreSetGas.Uint64()
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)
func toWordSize64(size uint64) uint64 {
return (size + 31) / 32
}
// calculates the memory size required for a step
func calcMemSize64(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 calcQuadMemGas64(mem *Memory, newMemSize uint64) (uint64, bool) {
oldTotalFee := mem.cost
if newMemSize > 0 {
newMemSizeWords := toWordSize64(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
}
// jitBaseCalc is the same as baseCheck except it doesn't do the look up in the
// gas table. This is done during compilation instead.
func jitBaseCalc(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 req64 struct {
stackPop int
gas uint64
stackPush int
}
var _baseCheck64 = map[OpCode]req64{
// opcode | stack pop | gas price | stack push
ADD: {2, GasFastestStep64, 1},
LT: {2, GasFastestStep64, 1},
GT: {2, GasFastestStep64, 1},
SLT: {2, GasFastestStep64, 1},
SGT: {2, GasFastestStep64, 1},
EQ: {2, GasFastestStep64, 1},
ISZERO: {1, GasFastestStep64, 1},
SUB: {2, GasFastestStep64, 1},
AND: {2, GasFastestStep64, 1},
OR: {2, GasFastestStep64, 1},
XOR: {2, GasFastestStep64, 1},
NOT: {1, GasFastestStep64, 1},
BYTE: {2, GasFastestStep64, 1},
CALLDATALOAD: {1, GasFastestStep64, 1},
CALLDATACOPY: {3, GasFastestStep64, 1},
MLOAD: {1, GasFastestStep64, 1},
MSTORE: {2, GasFastestStep64, 0},
MSTORE8: {2, GasFastestStep64, 0},
CODECOPY: {3, GasFastestStep64, 0},
MUL: {2, GasFastStep64, 1},
DIV: {2, GasFastStep64, 1},
SDIV: {2, GasFastStep64, 1},
MOD: {2, GasFastStep64, 1},
SMOD: {2, GasFastStep64, 1},
SIGNEXTEND: {2, GasFastStep64, 1},
ADDMOD: {3, GasMidStep64, 1},
MULMOD: {3, GasMidStep64, 1},
JUMP: {1, GasMidStep64, 0},
JUMPI: {2, GasSlowStep64, 0},
EXP: {2, GasSlowStep64, 1},
ADDRESS: {0, GasQuickStep64, 1},
ORIGIN: {0, GasQuickStep64, 1},
CALLER: {0, GasQuickStep64, 1},
CALLVALUE: {0, GasQuickStep64, 1},
CODESIZE: {0, GasQuickStep64, 1},
GASPRICE: {0, GasQuickStep64, 1},
COINBASE: {0, GasQuickStep64, 1},
TIMESTAMP: {0, GasQuickStep64, 1},
NUMBER: {0, GasQuickStep64, 1},
CALLDATASIZE: {0, GasQuickStep64, 1},
DIFFICULTY: {0, GasQuickStep64, 1},
GASLIMIT: {0, GasQuickStep64, 1},
POP: {1, GasQuickStep64, 0},
PC: {0, GasQuickStep64, 1},
MSIZE: {0, GasQuickStep64, 1},
GAS: {0, GasQuickStep64, 1},
BLOCKHASH: {1, GasExtStep64, 1},
BALANCE: {1, GasExtStep64, 1},
EXTCODESIZE: {1, GasExtStep64, 1},
EXTCODECOPY: {4, GasExtStep64, 0},
SLOAD: {1, params.SloadGas.Uint64(), 1},
SSTORE: {2, 0, 0},
SHA3: {2, params.Sha3Gas.Uint64(), 1},
CREATE: {3, params.CreateGas.Uint64(), 1},
CALL: {7, params.CallGas.Uint64(), 1},
CALLCODE: {7, params.CallGas.Uint64(), 1},
DELEGATECALL: {6, params.CallGas.Uint64(), 1},
JUMPDEST: {0, params.JumpdestGas.Uint64(), 0},
SUICIDE: {1, 0, 0},
RETURN: {2, 0, 0},
PUSH1: {0, GasFastestStep64, 1},
DUP1: {0, 0, 1},
}

View file

@ -141,7 +141,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return. // User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
defer func() { defer func() {
if err != nil && evm.cfg.Debug { if err != nil && evm.cfg.Debug {
evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), err) gas := new(big.Int).SetUint64(contract.gas64)
evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), err)
} }
}() }()
@ -149,22 +150,11 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
glog.Infof("running byte VM %x\n", codehash[:4]) glog.Infof("running byte VM %x\n", codehash[: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("VM %x done. time: %v instrc: %v\n", codehash[:4], time.Since(tstart), instrCount)
}() }()
} }
for ; ; instrCount++ { for ; ; instrCount++ {
/*
if EnableJit && it%100 == 0 {
if program != nil && progStatus(atomic.LoadInt32(&program.status)) == progReady {
// move execution
fmt.Println("moved", it)
glog.V(logger.Info).Infoln("Moved execution to JIT")
return runProgram(program, pc, mem, stack, evm.env, contract, input)
}
}
*/
// Get the memory location of pc // Get the memory location of pc
op = contract.GetOp(pc) op = contract.GetOp(pc)
// 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
@ -175,7 +165,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err 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(cost) { if !contract.UseGas(cost.Uint64()) {
return nil, OutOfGasError return nil, OutOfGasError
} }
@ -183,7 +173,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
mem.Resize(newMemSize.Uint64()) mem.Resize(newMemSize.Uint64())
// Add a log message // Add a log message
if evm.cfg.Debug { if evm.cfg.Debug {
evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), nil) gas := new(big.Int).SetUint64(contract.gas64)
evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), nil)
} }
if opPtr := evm.jumpTable[op]; opPtr.valid { if opPtr := evm.jumpTable[op]; opPtr.valid {
@ -369,7 +360,7 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go // 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) { func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.Gas(len(input)) gas := p.Gas(len(input))
if contract.UseGas(gas) { if contract.UseGas(gas.Uint64()) {
ret = p.Call(input) ret = p.Call(input)
return ret, nil return ret, nil

View file

@ -74,6 +74,7 @@ func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase }
func (self *VMEnv) Time() *big.Int { return self.header.Time } func (self *VMEnv) Time() *big.Int { return self.header.Time }
func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty } func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty }
func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit } func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit }
func (self *VMEnv) GasPrice() *big.Int { return self.msg.GasPrice() }
func (self *VMEnv) Value() *big.Int { return self.msg.Value() } func (self *VMEnv) Value() *big.Int { return self.msg.Value() }
func (self *VMEnv) Db() vm.Database { return self.state } func (self *VMEnv) Db() vm.Database { return self.state }
func (self *VMEnv) Depth() int { return self.depth } func (self *VMEnv) Depth() int { return self.depth }
@ -101,17 +102,17 @@ func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
Transfer(from, to, amount) Transfer(from, to, amount)
} }
func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return Call(self, me, addr, data, gas, price, value) return Call(self, me, addr, data, gas, value)
} }
func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
return CallCode(self, me, addr, data, gas, price, value) return CallCode(self, me, addr, data, gas, value)
} }
func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
return DelegateCall(self, me, addr, data, gas, price) return DelegateCall(self, me, addr, data, gas)
} }
func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
return Create(self, me, data, gas, price, value) return Create(self, me, data, gas, value)
} }

View file

@ -21,6 +21,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"math"
"math/big" "math/big"
"strconv" "strconv"
"testing" "testing"
@ -115,7 +116,7 @@ func runStateTests(ruleSet RuleSet, tests map[string]VmTest, skipTests []string)
} }
for name, test := range tests { for name, test := range tests {
if skipTest[name] /*|| name != "callcodecallcode_11" */ { if skipTest[name] /*|| name != "TestInputLimitsLight"*/ {
glog.Infoln("Skipping state test", name) glog.Infoln("Skipping state test", name)
continue continue
} }
@ -162,6 +163,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)

View file

@ -155,7 +155,7 @@ type Env struct {
state *state.StateDB state *state.StateDB
skipTransfer bool skipTransfer bool
initial bool initial bool
Gas *big.Int Gas, gasPrice *big.Int
origin common.Address origin common.Address
parent common.Hash parent common.Hash
@ -190,6 +190,7 @@ func NewEnvFromMap(ruleSet RuleSet, state *state.StateDB, envValues map[string]s
env.difficulty = common.Big(envValues["currentDifficulty"]) env.difficulty = common.Big(envValues["currentDifficulty"])
env.gasLimit = common.Big(envValues["currentGasLimit"]) env.gasLimit = common.Big(envValues["currentGasLimit"])
env.Gas = new(big.Int) env.Gas = new(big.Int)
env.gasPrice = common.Big(exeValues["gasPrice"])
env.evm = vm.New(env, vm.Config{ env.evm = vm.New(env, vm.Config{
EnableJit: EnableJit, EnableJit: EnableJit,
@ -208,6 +209,7 @@ func (self *Env) Time() *big.Int { return self.time }
func (self *Env) Difficulty() *big.Int { return self.difficulty } func (self *Env) Difficulty() *big.Int { return self.difficulty }
func (self *Env) Db() vm.Database { return self.state } func (self *Env) Db() vm.Database { return self.state }
func (self *Env) GasLimit() *big.Int { return self.gasLimit } func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) GasPrice() *big.Int { return self.gasPrice }
func (self *Env) VmType() vm.Type { return vm.StdVmTy } func (self *Env) VmType() vm.Type { return vm.StdVmTy }
func (self *Env) GetHash(n uint64) common.Hash { 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())))
@ -241,46 +243,46 @@ func (self *Env) Transfer(from, to vm.Account, amount *big.Int) {
core.Transfer(from, to, amount) core.Transfer(from, to, amount)
} }
func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
if self.vmTest && self.depth > 0 { if self.vmTest && self.depth > 0 {
caller.ReturnGas(gas, price) caller.ReturnGas(gas.Uint64())
return nil, nil return nil, nil
} }
ret, err := core.Call(self, caller, addr, data, gas, price, value) ret, err := core.Call(self, caller, addr, data, gas, value)
self.Gas = gas self.Gas = gas
return ret, err return ret, err
} }
func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) {
if self.vmTest && self.depth > 0 { if self.vmTest && self.depth > 0 {
caller.ReturnGas(gas, price) caller.ReturnGas(gas.Uint64())
return nil, nil return nil, nil
} }
return core.CallCode(self, caller, addr, data, gas, price, value) return core.CallCode(self, caller, addr, data, gas, value)
} }
func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) {
if self.vmTest && self.depth > 0 { if self.vmTest && self.depth > 0 {
caller.ReturnGas(gas, price) caller.ReturnGas(gas.Uint64())
return nil, nil return nil, nil
} }
return core.DelegateCall(self, caller, addr, data, gas, price) return core.DelegateCall(self, caller, addr, data, gas)
} }
func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) {
if self.vmTest { if self.vmTest {
caller.ReturnGas(gas, price) caller.ReturnGas(gas.Uint64())
nonce := self.state.GetNonce(caller.Address()) nonce := self.state.GetNonce(caller.Address())
obj := self.state.GetOrNewStateObject(crypto.CreateAddress(caller.Address(), nonce)) obj := self.state.GetOrNewStateObject(crypto.CreateAddress(caller.Address(), nonce))
return nil, obj.Address(), nil return nil, obj.Address(), nil
} else { } else {
return core.Create(self, caller, data, gas, price, value) return core.Create(self, caller, data, gas, value)
} }
} }

View file

@ -135,9 +135,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 {
@ -233,7 +233,6 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs,
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.
@ -245,7 +244,7 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs,
vmenv.vmTest = true vmenv.vmTest = true
vmenv.skipTransfer = true vmenv.skipTransfer = true
vmenv.initial = true vmenv.initial = true
ret, err := vmenv.Call(caller, to, data, gas, price, value) ret, err := vmenv.Call(caller, to, data, gas, value)
return ret, vmenv.state.Logs(), vmenv.Gas, err return ret, vmenv.state.Logs(), vmenv.Gas, err
} }