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"))
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),
@ -141,7 +140,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 {
@ -152,7 +150,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)),
)
}
@ -199,19 +196,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()),
}
@ -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) 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 }
@ -256,19 +255,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)
}

View file

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

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
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(env, caller, &addr, &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(env, caller, &addr, &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(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
}
// 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, 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
}
// 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(env, caller, nil, nil, 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(env, caller, nil, nil, 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(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()
// 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()))
}
@ -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
// 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, code)
defer contract.Finalise()
@ -116,7 +116,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
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
@ -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
// when we're in homestead this also counts for code storage gas errors.
if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) {
contract.UseGas(contract.Gas)
contract.UseGas(contract.Gas())
env.SetSnapshot(snapshotPreTransfer)
}
@ -135,12 +135,12 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
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()
// 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
}
@ -154,13 +154,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, code)
defer contract.Finalise()
ret, err = evm.Run(contract, input)
if err != nil {
contract.UseGas(contract.Gas)
contract.UseGas(contract.Gas())
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
func (c *StateObject) ReturnGas(gas, price *big.Int) {}
func (c *StateObject) ReturnGas(gas uint64) {}
func (self *StateObject) Copy() *StateObject {
stateObject := NewStateObject(self.Address(), self.db)

View file

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

View file

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

View file

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

View file

@ -53,6 +53,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
@ -66,13 +68,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.
@ -116,7 +118,7 @@ type Account interface {
SetNonce(uint64)
Balance() *big.Int
Address() common.Address
ReturnGas(*big.Int, *big.Int)
ReturnGas(uint64)
SetCode([]byte)
ForEachStorage(cb func(key, value common.Hash) bool)
Value() *big.Int

View file

@ -20,6 +20,7 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
@ -72,6 +73,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

View file

@ -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,10 +512,10 @@ 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)
)
contract.UseGas(contract.Gas)
_, addr, suberr := env.Create(contract, input, gas, contract.Price, value)
contract.UseGas(contract.gas64)
_, addr, suberr := env.Create(contract, input, 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
@ -548,7 +548,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))
@ -579,7 +579,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))
@ -596,7 +596,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 {

View file

@ -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.IsDeleted(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
}

View file

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

View file

@ -29,10 +29,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 {
@ -43,20 +40,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))
@ -72,10 +65,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))
@ -85,8 +75,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{})
@ -134,7 +124,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([]byte) {}
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
@ -149,7 +139,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)
@ -174,6 +164,7 @@ func NewEnv(config *Config) *Env {
func (self *Env) RuleSet() RuleSet { return ruleSet{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) }
@ -197,15 +188,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
}

View file

@ -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([]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([]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()

View file

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

View file

@ -38,6 +38,7 @@ type Env struct {
time *big.Int
difficulty *big.Int
gasLimit *big.Int
gasPrice *big.Int
getHashFn func(uint64) common.Hash
@ -55,6 +56,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,
@ -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) 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 }
@ -97,17 +100,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)
}

View file

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

View file

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

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.
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)
}
}()
@ -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])
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)
// 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
// Out Of Gas error
if !contract.UseGas(cost) {
if !contract.UseGas(cost.Uint64()) {
return nil, OutOfGasError
}
@ -183,7 +173,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 {
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 {
@ -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
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

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) 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 }
@ -101,17 +102,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)
}

View file

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

View file

@ -150,12 +150,12 @@ func (r RuleSet) IsHomestead(n *big.Int) bool {
}
type Env struct {
ruleSet RuleSet
depth int
state *state.StateDB
skipTransfer bool
initial bool
Gas *big.Int
ruleSet RuleSet
depth int
state *state.StateDB
skipTransfer bool
initial bool
Gas, gasPrice *big.Int
origin common.Address
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.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,
@ -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) 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())))
@ -241,46 +243,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)
}
}

View file

@ -135,9 +135,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 {
@ -233,7 +233,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.
@ -245,7 +244,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
}