From f770d3643b364987d5f2a47947a0472d0d327c32 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sat, 14 May 2016 17:08:38 +0200 Subject: [PATCH] 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. --- cmd/evm/main.go | 33 +++--- common/big.go | 6 +- common/math/integer.go | 22 ++++ common/math/integer_test.go | 50 ++++++++ core/execution.go | 36 +++--- core/state/state_object.go | 2 +- core/state_transition.go | 4 +- core/vm/common.go | 36 ------ core/vm/contract.go | 37 +++--- core/vm/environment.go | 12 +- core/vm/gas.go | 52 +++++++++ core/vm/instructions.go | 26 ++--- core/vm/jit.go | 224 ++++++++++++++++++++++++------------ core/vm/jit_optimiser.go | 20 ++-- core/vm/jit_test.go | 39 +++---- core/vm/logger_test.go | 12 +- core/vm/memory.go | 3 +- core/vm/runtime/env.go | 20 ++-- core/vm/runtime/runtime.go | 2 - core/vm/segments.go | 4 +- core/vm/uint64.go | 175 ++++++++++++++++++++++++++++ core/vm/vm.go | 27 ++--- core/vm_env.go | 17 +-- tests/state_test_util.go | 5 + tests/util.go | 38 +++--- tests/vm_test_util.go | 7 +- 26 files changed, 623 insertions(+), 286 deletions(-) create mode 100644 common/math/integer.go create mode 100644 common/math/integer_test.go create mode 100644 core/vm/uint64.go diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 2c4329fa50..b1a3cd9e20 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -128,8 +128,7 @@ func run(ctx *cli.Context) error { sender := statedb.CreateAccount(common.StringToAddress("sender")) logger := vm.NewStructLogger(nil) - - vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), vm.Config{ + vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), common.Big(ctx.GlobalString(PriceFlag.Name)), vm.Config{ Debug: ctx.GlobalBool(DebugFlag.Name), ForceJit: ctx.GlobalBool(ForceJitFlag.Name), EnableJit: !ctx.GlobalBool(DisableJitFlag.Name), @@ -172,7 +171,6 @@ func run(ctx *cli.Context) error { sender, input, common.Big(ctx.GlobalString(GasFlag.Name)), - common.Big(ctx.GlobalString(PriceFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)), ) } else { @@ -184,7 +182,6 @@ func run(ctx *cli.Context) error { receiver.Address(), common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), common.Big(ctx.GlobalString(GasFlag.Name)), - common.Big(ctx.GlobalString(PriceFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)), ) } @@ -231,19 +228,20 @@ type VMEnv struct { transactor *common.Address value *big.Int - depth int - Gas *big.Int - time *big.Int - logs []vm.StructLog + depth int + Gas, price *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 { +func NewEnv(state *state.StateDB, transactor common.Address, value, price *big.Int, cfg vm.Config) *VMEnv { env := &VMEnv{ state: state, transactor: &transactor, value: value, + price: price, time: big.NewInt(time.Now().Unix()), } @@ -272,6 +270,7 @@ 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) GasPrice() *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 } @@ -291,19 +290,19 @@ 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) { +func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { 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) { - return core.CallCode(self, caller, addr, data, gas, price, value) +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, 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) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + 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) { - return core.Create(self, caller, data, gas, price, value) +func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + return core.Create(self, caller, data, gas, value) } diff --git a/common/big.go b/common/big.go index 4ce87ee0c6..0282db68cf 100644 --- a/common/big.go +++ b/common/big.go @@ -16,7 +16,10 @@ package common -import "math/big" +import ( + "math" + "math/big" +) // Common big integers often used var ( @@ -33,6 +36,7 @@ var ( Big256 = big.NewInt(0xff) Big257 = big.NewInt(257) MaxBig = String2Big("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + Max64Bit = new(big.Int).SetUint64(math.MaxUint64) ) // Big pow diff --git a/common/math/integer.go b/common/math/integer.go new file mode 100644 index 0000000000..041cf3c700 --- /dev/null +++ b/common/math/integer.go @@ -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 +} diff --git a/common/math/integer_test.go b/common/math/integer_test.go new file mode 100644 index 0000000000..4e61c8a140 --- /dev/null +++ b/common/math/integer_test.go @@ -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) + } + } +} diff --git a/core/execution.go b/core/execution.go index 0474c7381e..00a856d22c 100644 --- a/core/execution.go +++ b/core/execution.go @@ -26,30 +26,30 @@ import ( ) // 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) { - ret, _, err = exec(true, env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value) +func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { + ret, _, err = exec(true, env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) return ret, err } // 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() - ret, _, err = exec(false, env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value) + ret, _, err = exec(false, env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) return ret, err } // 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() originAddr := env.Origin() 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 = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, callerValue) return ret, err } // 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) { - ret, address, err = exec(true, env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, gasPrice, value) +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(true, env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, value) // Here we get an error if we run into maximum stack depth, // See: https://github.com/ethereum/yellowpaper/pull/131 // 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 } -func exec(transfers bool, 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 exec(transfers bool, 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() // Depth check execution. Fail if we're trying to execute above the // limit. if env.Depth() > int(params.CallCreateDepth.Int64()) { - caller.ReturnGas(gas, gasPrice) + caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, vm.DepthError } 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())) } @@ -109,7 +109,7 @@ func exec(transfers bool, env vm.Environment, caller vm.ContractRef, address, co // initialise a new contract and set the code that is to be used by the // EVM. The contract is a scoped environment for this execution context // only. - contract := vm.NewContract(caller, to, value, gas, gasPrice) + contract := vm.NewContract(caller, to, value, gas) contract.SetCallCode(codeAddr, codeHash, code) defer contract.Finalise() @@ -121,7 +121,7 @@ func exec(transfers bool, env vm.Environment, caller vm.ContractRef, address, co if err == nil && createAccount { dataGas := big.NewInt(int64(len(ret))) dataGas.Mul(dataGas, params.CreateDataGas) - if contract.UseGas(dataGas) { + if contract.UseGas(dataGas.Uint64()) { env.Db().SetCode(*address, ret) } else { err = vm.CodeStoreOutOfGasError @@ -132,7 +132,7 @@ func exec(transfers bool, env vm.Environment, caller vm.ContractRef, address, co // 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. if err != nil && (env.ChainConfig().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) { - contract.UseGas(contract.Gas) + contract.UseGas(contract.Gas()) env.RevertToSnapshot(snapshotPreTransfer) } @@ -140,12 +140,12 @@ func exec(transfers bool, env vm.Environment, caller vm.ContractRef, address, co 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 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() // Depth check execution. Fail if we're trying to execute above the // limit. if env.Depth() > int(params.CallCreateDepth.Int64()) { - caller.ReturnGas(gas, gasPrice) + caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, vm.DepthError } @@ -159,13 +159,13 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA } // 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) defer contract.Finalise() ret, err = evm.Run(contract, input) if err != nil { - contract.UseGas(contract.Gas) + contract.UseGas(contract.Gas()) env.RevertToSnapshot(snapshot) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 396cb305c7..544d214a2b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -269,7 +269,7 @@ func (self *StateObject) setBalance(amount *big.Int) { } // 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 { stateObject := newObject(db, self.address, self.data, onDirty) diff --git a/core/state_transition.go b/core/state_transition.go index 2b9d1c6d1d..0acc290475 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -244,7 +244,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b vmenv := self.env //var addr common.Address 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 { self.gas = Big0 } @@ -256,7 +256,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b } else { // Increment the nonce for the next transaction 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 { glog.V(logger.Core).Infoln("VM call err:", err) } diff --git a/core/vm/common.go b/core/vm/common.go index 2878b92d2a..a17a6496b5 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -21,7 +21,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" ) // Type is the VM type accepted by **NewVm** @@ -45,41 +44,6 @@ var ( 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 func u256(n int64) *big.Int { return big.NewInt(n) diff --git a/core/vm/contract.go b/core/vm/contract.go index 70455a4c23..34c713642d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -24,7 +24,7 @@ import ( // ContractRef is a reference to the contract's backing object type ContractRef interface { - ReturnGas(*big.Int, *big.Int) + ReturnGas(uint64) Address() common.Address Value() *big.Int SetCode(common.Hash, []byte) @@ -48,7 +48,8 @@ type Contract struct { CodeAddr *common.Address Input []byte - value, Gas, UsedGas, Price *big.Int + value, gas *big.Int + gas64 uint64 Args []byte @@ -56,7 +57,7 @@ type Contract struct { } // 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} 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 // 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) - // 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 } @@ -102,6 +100,11 @@ func (c *Contract) GetByte(n uint64) byte { return 0 } +// Gas returns the current gas left +func (c *Contract) Gas() uint64 { + return c.gas64 +} + // Caller returns the caller of the contract. // // 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 // caller. func (c *Contract) Finalise() { + c.gas.SetUint64(c.gas64) // 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 -func (c *Contract) UseGas(gas *big.Int) (ok bool) { - ok = useGas(c.Gas, gas) - if ok { - c.UsedGas.Add(c.UsedGas, gas) +func (c *Contract) UseGas(gas uint64) (ok bool) { + if c.gas64 < gas { + return false } - return + c.gas64 -= gas + return true } // 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 - c.Gas.Add(c.Gas, gas) - c.UsedGas.Sub(c.UsedGas, gas) + c.gas64 += gas } // Address returns the contracts address diff --git a/core/vm/environment.go b/core/vm/environment.go index 803d51f35e..bed3caa29c 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -48,6 +48,8 @@ type Environment interface { Difficulty() *big.Int // The gas limit of the block GasLimit() *big.Int + // The gas price of the current call + GasPrice() *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 @@ -61,13 +63,13 @@ type Environment interface { // 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) + 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 - 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 - 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(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. @@ -117,7 +119,7 @@ type Account interface { SetNonce(uint64) Balance() *big.Int Address() common.Address - ReturnGas(*big.Int, *big.Int) + ReturnGas(uint64) SetCode(common.Hash, []byte) ForEachStorage(cb func(key, value common.Hash) bool) Value() *big.Int diff --git a/core/vm/gas.go b/core/vm/gas.go index fdbc0df7f7..4c738981b9 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -20,6 +20,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" ) @@ -39,6 +40,21 @@ var ( n64 = big.NewInt(64) ) +// calcGas returns the actual gas cost of the call. +// +// The cost of gas was changed during the homestead price change HF. To allow for EIP150 +// to be implemented. The returned gas is gas - base * 63 / 64. +func callGas(gasTable params.GasTable, availableGas, base, callCost uint64) uint64 { + if gasTable.CreateBySuicide != nil { + gas := availableGas - (availableGas-base)/64 + if gas < callCost { + return gas + } + } + return callCost +} + +/* // calcGas returns the actual gas cost of the call. // // The cost of gas was changed during the homestead price change HF. To allow for EIP150 @@ -55,6 +71,7 @@ func callGas(gasTable params.GasTable, availableGas, base, callCost *big.Int) *b } return callCost } +*/ // baseCheck checks for any stack error underflows func baseCheck(op OpCode, stack *Stack, gas *big.Int) error { @@ -91,6 +108,41 @@ func toWordSize(size *big.Int) *big.Int { 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 { stackPop int gas *big.Int diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 6743151993..36eedfe495 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -42,7 +42,7 @@ type instruction struct { fn instrFn data *big.Int - gas *big.Int + gas uint64 spop int spush int @@ -71,7 +71,7 @@ func (instr instruction) do(program *Program, pc *uint64, env Environment, contr return nil, OutOfGasError } // Resize the memory calculated previously - memory.Resize(newMemSize.Uint64()) + memory.Resize(newMemSize) // These opcodes return an argument and are therefor handled // 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) { 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)) } @@ -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) { - 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) { @@ -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) { offset := stack.pop() - val := common.BigD(memory.Get(offset.Int64(), 32)) + val := common.BigD(memory.GetPtr(offset.Int64(), 32)) 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) { - 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) { @@ -512,15 +512,15 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract value = stack.pop() offset, size = stack.pop(), stack.pop() input = memory.Get(offset.Int64(), size.Int64()) - gas = new(big.Int).Set(contract.Gas) + //gas = new(big.Int).SetUint64(contract.gas64) + gas = contract.gas64 ) if env.ChainConfig().IsEIP150(env.BlockNumber()) { - gas.Div(gas, n64) - gas = gas.Sub(contract.Gas, gas) + gas = gas - gas/64 } contract.UseGas(gas) - _, addr, suberr := env.Create(contract, input, gas, contract.Price, value) + _, addr, suberr := env.Create(contract, input, new(big.Int).SetUint64(gas), value) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -553,7 +553,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, 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 { stack.push(new(big.Int)) @@ -584,7 +584,7 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra 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 { stack.push(new(big.Int)) @@ -601,7 +601,7 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co toAddr := common.BigToAddress(to) 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 { stack.push(new(big.Int)) } else { diff --git a/core/vm/jit.go b/core/vm/jit.go index b75558d398..d00a97ad06 100644 --- a/core/vm/jit.go +++ b/core/vm/jit.go @@ -18,11 +18,13 @@ package vm import ( "fmt" + gmath "math" "math/big" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" "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 { baseOp = DUP1 } - base := _baseCheck[baseOp] + base := _baseCheck64[baseOp] returns := op == RETURN || op == SUICIDE || op == STOP 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 // 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 ( - gas = new(big.Int) - newMemSize *big.Int = new(big.Int) + newMemSize uint64 + sizeFault bool ) - err := jitBaseCheck(instr, stack, gas) + + gas, err := jitBaseCalc(instr, stack) if err != nil { - return nil, nil, err + return 0, 0, err } // stack Check, memory resize & gas phase @@ -373,40 +376,57 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi n := int(op - SWAP1 + 2) err := stack.require(n) 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: n := int(op - DUP1 + 1) err := stack.require(n) if err != nil { - return nil, nil, err + return 0, 0, err } - gas.Set(GasFastestStep) + gas = GasFastestStep64 case LOG0, LOG1, LOG2, LOG3, LOG4: n := int(op - LOG0) err := stack.require(n + 2) if err != nil { - return nil, nil, err + return 0, 0, err } 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.Add(gas, params.LogGas) - gas.Add(gas, add.Mul(big.NewInt(int64(n)), params.LogTopicGas)) - gas.Add(gas, add.Mul(mSize, params.LogDataGas)) + gas = (gas + LogGas64) + (uint64(n) * LogTopicGas64) + if !math.IsMulSafe(msize64, LogDataGas64) { + return 0, 0, OutOfGasError + } + 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: - 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: err := stack.require(2) 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] 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) // 3. From a non-zero to a non-zero (CHANGE) if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { - g = params.SstoreSetGas + gas = SstoreSetGas64 } else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { statedb.AddRefund(params.SstoreRefundGas) - g = params.SstoreClearGas + gas = SstoreClearGas64 } else { - g = params.SstoreResetGas + gas = SstoreResetGas64 } - gas.Set(g) case SUICIDE: if !statedb.HasSuicided(contract.Address()) { statedb.AddRefund(params.SuicideRefundGas) } case MLOAD: - newMemSize = calcMemSize(stack.peek(), u256(32)) + newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32)) case MSTORE8: - newMemSize = calcMemSize(stack.peek(), u256(1)) + newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(1)) case MSTORE: - newMemSize = calcMemSize(stack.peek(), u256(32)) + newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32)) case RETURN: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) + newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-2]) 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]) - gas.Add(gas, words.Mul(words, params.Sha3WordGas)) + if stack.data[stack.len()-2].BitLen() > 64 { + 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: - 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]) - gas.Add(gas, words.Mul(words, params.CopyGas)) + words := toWordSize64(stack.data[stack.len()-3].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 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]) - gas.Add(gas, words.Mul(words, params.CopyGas)) + words := toWordSize64(stack.data[stack.len()-3].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 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)) + newMemSize, sizeFault = calcMemSize64(stack.data[stack.len()-2], stack.data[stack.len()-4]) + if sizeFault { + return 0, 0, OutOfGasError + } + 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: - 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: - 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 !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 { - 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]) - y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) + x, xSizeFault := calcMemSize64(stack.data[stack.len()-6], stack.data[stack.len()-7]) + 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: - 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() - x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) - y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) + x, xSizeFault := calcMemSize64(stack.data[stack.len()-5], stack.data[stack.len()-6]) + if xSizeFault { + return 0, 0, OutOfGasError + } + y, ySizeFault := calcMemSize64(stack.data[stack.len()-3], stack.data[stack.len()-4]) + if ySizeFault { + return 0, 0, OutOfGasError + } - newMemSize = common.BigMax(x, y) + newMemSize = uint64(gmath.Max(float64(x), float64(y))) + } + if sizeFault { + return 0, 0, OutOfGasError } - quadMemGas(mem, newMemSize, gas) - return newMemSize, gas, nil -} - -// jitBaseCheck is the same as baseCheck except it doesn't do the look up in the -// gas table. This is done during compilation instead. -func jitBaseCheck(instr instruction, stack *Stack, gas *big.Int) error { - err := stack.require(instr.spop) - if err != nil { - return err - } - - 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()) - } - - // nil on gas means no base calculation - if instr.gas == nil { - return nil - } - - gas.Add(gas, instr.gas) - - return nil + memGas, _ := calcQuadMemGas64(mem, newMemSize) + if !math.IsAddSafe(gas, memGas) { + return 0, 0, OutOfGasError + } + return toWordSize64(newMemSize) * 32, gas + memGas, nil } diff --git a/core/vm/jit_optimiser.go b/core/vm/jit_optimiser.go index 31ad0c2e2b..831af1ed10 100644 --- a/core/vm/jit_optimiser.go +++ b/core/vm/jit_optimiser.go @@ -30,15 +30,16 @@ func optimiseProgram(program *Program) { var load []instruction var ( - statsJump = 0 - statsPush = 0 + statsJump = 0 + statsOldPush = 0 + statsNewPush = 0 ) if glog.V(logger.Debug) { glog.Infof("optimising %x\n", program.Id[:4]) tstart := time.Now() 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 { case instr.op.IsPush(): load = append(load, instr) + statsOldPush++ case instr.op.IsStaticJump(): if len(load) == 0 { continue @@ -74,7 +76,7 @@ func optimiseProgram(program *Program) { if len(load) > 2 { seg, size := makePushSeg(load[:len(load)-1]) program.instructions[i-size-1] = seg - statsPush++ + statsNewPush++ } // create a segment consisting of a pre determined // jump, destination and validity. @@ -88,7 +90,7 @@ func optimiseProgram(program *Program) { if len(load) > 1 { seg, size := makePushSeg(load) program.instructions[i-size] = seg - statsPush++ + statsNewPush++ } load = nil } @@ -99,12 +101,12 @@ func optimiseProgram(program *Program) { func makePushSeg(instrs []instruction) (pushSeg, int) { var ( data []*big.Int - gas = new(big.Int) + gas uint64 ) for _, instr := range instrs { data = append(data, instr.data) - gas.Add(gas, instr.gas) + gas += instr.gas } 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 // destination (PUSH, JUMP). func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg { - gas := new(big.Int) - gas.Add(gas, _baseCheck[PUSH1].gas) - gas.Add(gas, _baseCheck[JUMP].gas) + gas := _baseCheck64[PUSH1].gas + _baseCheck64[JUMP].gas contract := &Contract{Code: program.code} pos, err := jump(program.mapping, program.destinations, contract, to) diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index f9c0e8fa2e..063f98a20a 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -30,10 +30,7 @@ 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) - } + CompileProgram(prog) if instr, ok := prog.instructions[0].(pushSeg); ok { if len(instr.data) != 2 { @@ -44,20 +41,16 @@ func TestSegmenting(t *testing.T) { } prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) - err = CompileProgram(prog) - if err != nil { - t.Fatal(err) - } + CompileProgram(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)}) - err = CompileProgram(prog) - if err != nil { - t.Fatal(err) - } + CompileProgram(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)) @@ -73,10 +66,7 @@ func TestSegmenting(t *testing.T) { func TestCompiling(t *testing.T) { prog := NewProgram([]byte{0x60, 0x10}) - err := CompileProgram(prog) - if err != nil { - t.Error("didn't expect compile error") - } + CompileProgram(prog) if len(prog.instructions) != 1 { t.Error("expected 1 compiled instruction, got", len(prog.instructions)) @@ -86,8 +76,8 @@ func TestCompiling(t *testing.T) { 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)) + env := NewEnv(true, true) + contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) contract.CodeAddr = &common.Address{} program := NewProgram([]byte{}) @@ -135,7 +125,7 @@ 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) ReturnGas(uint64) {} func (account) SetCode(common.Hash, []byte) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} @@ -150,7 +140,7 @@ func runVmBench(test vmBench, b *testing.B) { b.ResetTimer() 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.CodeAddr = &common.Address{} _, err := env.Vm().Run(context, test.input) @@ -177,6 +167,7 @@ func (self *Env) ChainConfig() *params.ChainConfig { return ¶ms.ChainConfig{new(big.Int), new(big.Int), true, new(big.Int), common.Hash{}, new(big.Int)} } 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) BlockNumber() *big.Int { return big.NewInt(0) } @@ -200,15 +191,15 @@ 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) { +func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, 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) { +func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, 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) { +func (self *Env) Create(caller ContractRef, data []byte, gas, price *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) { +func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { return nil, nil } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index d4d164eb6f..a7aa0cfa0e 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -27,10 +27,10 @@ type dummyContractRef struct { calledForEach bool } -func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {} -func (dummyContractRef) Address() common.Address { return common.Address{} } -func (dummyContractRef) Value() *big.Int { return new(big.Int) } -func (dummyContractRef) SetCode(common.Hash, []byte) {} +func (dummyContractRef) ReturnGas(uint64) {} +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode(common.Hash, []byte) {} func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { d.calledForEach = true } @@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) { logger = NewStructLogger(nil) mem = NewMemory() 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(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") var ( 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) logger = NewStructLogger(nil) mem = NewMemory() diff --git a/core/vm/memory.go b/core/vm/memory.go index d011884173..ddf3b605eb 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -21,10 +21,11 @@ import "fmt" // Memory implements a simple memory model for the ethereum virtual machine. type Memory struct { store []byte + cost uint64 } func NewMemory() *Memory { - return &Memory{nil} + return &Memory{} } // Set sets offset + size to value diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index f1a2b60d3e..7a0531edbd 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -39,6 +39,7 @@ type Env struct { time *big.Int difficulty *big.Int gasLimit *big.Int + gasPrice *big.Int getHashFn func(uint64) common.Hash @@ -56,6 +57,7 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { time: cfg.Time, difficulty: cfg.Difficulty, gasLimit: cfg.GasLimit, + gasPrice: cfg.GasPrice, } env.evm = vm.New(env, vm.Config{ Debug: cfg.Debug, @@ -67,6 +69,8 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { } func (self *Env) ChainConfig() *params.ChainConfig { return self.chainConfig } +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) Origin() common.Address { return self.origin } func (self *Env) BlockNumber() *big.Int { return self.number } @@ -98,17 +102,17 @@ 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) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + 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) { - return core.CallCode(self, caller, addr, data, gas, price, value) +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, 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) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + 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) { - return core.Create(self, caller, data, gas, price, value) +func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + return core.Create(self, caller, data, gas, value) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index f2d86a3247..6674d12088 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -115,7 +115,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { receiver.Address(), input, cfg.GasLimit, - cfg.GasPrice, cfg.Value, ) @@ -139,7 +138,6 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { address, input, cfg.GasLimit, - cfg.GasPrice, cfg.Value, ) diff --git a/core/vm/segments.go b/core/vm/segments.go index 648d8a04a1..61d87367c6 100644 --- a/core/vm/segments.go +++ b/core/vm/segments.go @@ -21,7 +21,7 @@ import "math/big" type jumpSeg struct { pos uint64 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) { @@ -39,7 +39,7 @@ func (s jumpSeg) Op() OpCode { return 0 } type pushSeg struct { 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) { diff --git a/core/vm/uint64.go b/core/vm/uint64.go new file mode 100644 index 0000000000..96d1e76364 --- /dev/null +++ b/core/vm/uint64.go @@ -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}, +} diff --git a/core/vm/vm.go b/core/vm/vm.go index 8e0aa09a88..e1f00841c1 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -146,7 +146,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. defer func() { 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) } }() @@ -154,22 +155,11 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { glog.Infof("running byte VM %x\n", codehash[:4]) tstart := time.Now() 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++ { - /* - 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 op = contract.GetOp(pc) //fmt.Printf("OP %d %v\n", op, op) @@ -181,7 +171,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 // Out Of Gas error - if !contract.UseGas(cost) { + if !contract.UseGas(cost.Uint64()) { return nil, OutOfGasError } @@ -189,7 +179,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { mem.Resize(newMemSize.Uint64()) // Add a log message if evm.cfg.Debug { - err = 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) + err = evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), nil) if err != nil { return nil, err } @@ -427,7 +418,7 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co quadMemGas(mem, newMemSize, gas) - cg := callGas(gasTable, contract.Gas, gas, stack.data[stack.len()-1]) + cg := new(big.Int).SetUint64(callGas(gasTable, contract.gas64, gas.Uint64(), stack.data[stack.len()-1].Uint64())) // Replace the stack item with the new gas calculation. This means that // either the original item is left on the stack or the item is replaced by: // (availableGas - gas) * 63 / 64 @@ -447,7 +438,7 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co quadMemGas(mem, newMemSize, gas) - cg := callGas(gasTable, contract.Gas, gas, stack.data[stack.len()-1]) + cg := new(big.Int).SetUint64(callGas(gasTable, contract.gas64, gas.Uint64(), stack.data[stack.len()-1].Uint64())) // Replace the stack item with the new gas calculation. This means that // either the original item is left on the stack or the item is replaced by: // (availableGas - gas) * 63 / 64 @@ -464,7 +455,7 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co // 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) { + if contract.UseGas(gas.Uint64()) { ret = p.Call(input) return ret, nil diff --git a/core/vm_env.go b/core/vm_env.go index 43e4d2fd6f..4bd42c0a20 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -75,6 +75,7 @@ func (self *VMEnv) Coinbase() common.Address { return self.header.Coinba 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) GasPrice() *big.Int { return self.msg.GasPrice() } 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 } @@ -102,17 +103,17 @@ 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) Call(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + 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) { - return CallCode(self, me, addr, data, gas, price, value) +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, 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) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + 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) { - return Create(self, me, data, gas, price, value) +func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + return Create(self, me, data, gas, value) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index c8899392ad..728ca903e5 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -21,6 +21,7 @@ import ( "encoding/hex" "fmt" "io" + "math" "math/big" "strconv" "strings" @@ -150,6 +151,10 @@ func runStateTest(chainConfig *params.ChainConfig, test VmTest) error { // err error 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(chainConfig, statedb, env, test.Transaction) diff --git a/tests/util.go b/tests/util.go index 5296bb54bc..df571eef36 100644 --- a/tests/util.go +++ b/tests/util.go @@ -149,12 +149,12 @@ type VmTest struct { } type Env struct { - chainConfig *params.ChainConfig - depth int - state *state.StateDB - skipTransfer bool - initial bool - Gas *big.Int + chainConfig *params.ChainConfig + depth int + state *state.StateDB + skipTransfer bool + initial bool + Gas, gasPrice *big.Int origin common.Address parent common.Hash @@ -189,6 +189,7 @@ func NewEnvFromMap(chainConfig *params.ChainConfig, state *state.StateDB, envVal env.difficulty = common.Big(envValues["currentDifficulty"]) env.gasLimit = common.Big(envValues["currentGasLimit"]) env.Gas = new(big.Int) + env.gasPrice = common.Big(exeValues["gasPrice"]) env.evm = vm.New(env, vm.Config{ EnableJit: EnableJit, @@ -207,6 +208,7 @@ 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) GasPrice() *big.Int { return self.gasPrice } 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()))) @@ -240,46 +242,46 @@ 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) { +func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) 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 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 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) 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 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) 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 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) 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) + return core.Create(self, caller, data, gas, value) } } diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index f00af87b9e..55945e2c37 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -128,9 +128,9 @@ func runVmTests(tests map[string]VmTest, skipTests []string) error { } for name, test := range tests { - if skipTest[name] { + if skipTest[name] /*|| name != "57a9a2fbfe21a848e8d83379562a8513417c73d127495cf4abc5a44a5fe08e92"*/ { glog.Infoln("Skipping VM test", name) - return nil + continue } if err := runVmTest(test); err != nil { @@ -217,7 +217,6 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, from = common.HexToAddress(exec["caller"]) data = common.FromHex(exec["data"]) gas = common.Big(exec["gas"]) - price = common.Big(exec["gasPrice"]) value = common.Big(exec["value"]) ) // Reset the pre-compiled contracts for VM tests. @@ -229,7 +228,7 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, vmenv.vmTest = true vmenv.skipTransfer = 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 }