mirror of https://github.com/ethereum/go-ethereum
core/vm: improved EVM run loop & instruction calling (#3378)
The run loop, which previously contained custom opcode executes have been removed and has been simplified to a few checks. Each operation consists of 4 elements: execution function, gas cost function, stack validation function and memory size function. The execution function implements the operation's runtime behaviour, the gas cost function implements the operation gas costs function and greatly depends on the memory and stack, the stack validation function validates the stack and makes sure that enough items can be popped off and pushed on and the memory size function calculates the memory required for the operation and returns it. This commit also allows the EVM to go unmetered. This is helpful for offline operations such as contract calls.pull/3516/head
parent
2126d81488
commit
bbc4ea4ae8
@ -0,0 +1,246 @@ |
|||||||
|
package vm |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
) |
||||||
|
|
||||||
|
func memoryGasCost(mem *Memory, newMemSize *big.Int) *big.Int { |
||||||
|
gas := new(big.Int) |
||||||
|
if newMemSize.Cmp(common.Big0) > 0 { |
||||||
|
newMemSizeWords := toWordSize(newMemSize) |
||||||
|
|
||||||
|
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) |
||||||
|
} |
||||||
|
} |
||||||
|
return gas |
||||||
|
} |
||||||
|
|
||||||
|
func constGasFunc(gas *big.Int) gasFunc { |
||||||
|
return func(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return gas |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func gasCalldataCopy(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := memoryGasCost(mem, memorySize) |
||||||
|
gas.Add(gas, GasFastestStep) |
||||||
|
words := toWordSize(stack.Back(2)) |
||||||
|
|
||||||
|
return gas.Add(gas, words.Mul(words, params.CopyGas)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasSStore(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
var ( |
||||||
|
y, x = stack.Back(1), stack.Back(0) |
||||||
|
val = env.StateDB.GetState(contract.Address(), common.BigToHash(x)) |
||||||
|
) |
||||||
|
// This checks for 3 scenario's and calculates gas accordingly
|
||||||
|
// 1. From a zero-value address to a non-zero value (NEW VALUE)
|
||||||
|
// 2. From a non-zero value address to a zero-value address (DELETE)
|
||||||
|
// 3. From a non-zero to a non-zero (CHANGE)
|
||||||
|
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { |
||||||
|
// 0 => non 0
|
||||||
|
return new(big.Int).Set(params.SstoreSetGas) |
||||||
|
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { |
||||||
|
env.StateDB.AddRefund(params.SstoreRefundGas) |
||||||
|
|
||||||
|
return new(big.Int).Set(params.SstoreClearGas) |
||||||
|
} else { |
||||||
|
// non 0 => non 0 (or 0 => 0)
|
||||||
|
return new(big.Int).Set(params.SstoreResetGas) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func makeGasLog(n uint) gasFunc { |
||||||
|
return func(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
mSize := stack.Back(1) |
||||||
|
|
||||||
|
gas := new(big.Int).Add(memoryGasCost(mem, memorySize), params.LogGas) |
||||||
|
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(n)), params.LogTopicGas)) |
||||||
|
gas.Add(gas, new(big.Int).Mul(mSize, params.LogDataGas)) |
||||||
|
return gas |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func gasSha3(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := memoryGasCost(mem, memorySize) |
||||||
|
gas.Add(gas, params.Sha3Gas) |
||||||
|
words := toWordSize(stack.Back(1)) |
||||||
|
return gas.Add(gas, words.Mul(words, params.Sha3WordGas)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasCodeCopy(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := memoryGasCost(mem, memorySize) |
||||||
|
gas.Add(gas, GasFastestStep) |
||||||
|
words := toWordSize(stack.Back(2)) |
||||||
|
|
||||||
|
return gas.Add(gas, words.Mul(words, params.CopyGas)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasExtCodeCopy(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := memoryGasCost(mem, memorySize) |
||||||
|
gas.Add(gas, gt.ExtcodeCopy) |
||||||
|
words := toWordSize(stack.Back(3)) |
||||||
|
|
||||||
|
return gas.Add(gas, words.Mul(words, params.CopyGas)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasMLoad(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return new(big.Int).Add(GasFastestStep, memoryGasCost(mem, memorySize)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasMStore8(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return new(big.Int).Add(GasFastestStep, memoryGasCost(mem, memorySize)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasMStore(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return new(big.Int).Add(GasFastestStep, memoryGasCost(mem, memorySize)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasCreate(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return new(big.Int).Add(params.CreateGas, memoryGasCost(mem, memorySize)) |
||||||
|
} |
||||||
|
|
||||||
|
func gasBalance(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return gt.Balance |
||||||
|
} |
||||||
|
|
||||||
|
func gasExtCodeSize(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return gt.ExtcodeSize |
||||||
|
} |
||||||
|
|
||||||
|
func gasSLoad(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return gt.SLoad |
||||||
|
} |
||||||
|
|
||||||
|
func gasExp(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
expByteLen := int64((stack.data[stack.len()-2].BitLen() + 7) / 8) |
||||||
|
gas := big.NewInt(expByteLen) |
||||||
|
gas.Mul(gas, gt.ExpByte) |
||||||
|
return gas.Add(gas, GasSlowStep) |
||||||
|
} |
||||||
|
|
||||||
|
func gasCall(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := new(big.Int).Set(gt.Calls) |
||||||
|
|
||||||
|
transfersValue := stack.Back(2).BitLen() > 0 |
||||||
|
var ( |
||||||
|
address = common.BigToAddress(stack.Back(1)) |
||||||
|
eip158 = env.ChainConfig().IsEIP158(env.BlockNumber) |
||||||
|
) |
||||||
|
if eip158 { |
||||||
|
if env.StateDB.Empty(address) && transfersValue { |
||||||
|
gas.Add(gas, params.CallNewAccountGas) |
||||||
|
} |
||||||
|
} else if !env.StateDB.Exist(address) { |
||||||
|
gas.Add(gas, params.CallNewAccountGas) |
||||||
|
} |
||||||
|
if transfersValue { |
||||||
|
gas.Add(gas, params.CallValueTransferGas) |
||||||
|
} |
||||||
|
gas.Add(gas, memoryGasCost(mem, memorySize)) |
||||||
|
|
||||||
|
cg := callGas(gt, contract.Gas, gas, stack.data[stack.len()-1]) |
||||||
|
// 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
|
||||||
|
// We replace the stack item so that it's available when the opCall instruction is
|
||||||
|
// called. This information is otherwise lost due to the dependency on *current*
|
||||||
|
// available gas.
|
||||||
|
stack.data[stack.len()-1] = cg |
||||||
|
|
||||||
|
return gas.Add(gas, cg) |
||||||
|
} |
||||||
|
|
||||||
|
func gasCallCode(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := new(big.Int).Set(gt.Calls) |
||||||
|
if stack.Back(2).BitLen() > 0 { |
||||||
|
gas.Add(gas, params.CallValueTransferGas) |
||||||
|
} |
||||||
|
gas.Add(gas, memoryGasCost(mem, memorySize)) |
||||||
|
|
||||||
|
cg := callGas(gt, contract.Gas, gas, stack.data[stack.len()-1]) |
||||||
|
// 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
|
||||||
|
// We replace the stack item so that it's available when the opCall instruction is
|
||||||
|
// called. This information is otherwise lost due to the dependency on *current*
|
||||||
|
// available gas.
|
||||||
|
stack.data[stack.len()-1] = cg |
||||||
|
|
||||||
|
return gas.Add(gas, cg) |
||||||
|
} |
||||||
|
|
||||||
|
func gasReturn(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return memoryGasCost(mem, memorySize) |
||||||
|
} |
||||||
|
|
||||||
|
func gasSuicide(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := new(big.Int) |
||||||
|
// EIP150 homestead gas reprice fork:
|
||||||
|
if env.ChainConfig().IsEIP150(env.BlockNumber) { |
||||||
|
gas.Set(gt.Suicide) |
||||||
|
var ( |
||||||
|
address = common.BigToAddress(stack.Back(0)) |
||||||
|
eip158 = env.ChainConfig().IsEIP158(env.BlockNumber) |
||||||
|
) |
||||||
|
|
||||||
|
if eip158 { |
||||||
|
// if empty and transfers value
|
||||||
|
if env.StateDB.Empty(address) && env.StateDB.GetBalance(contract.Address()).BitLen() > 0 { |
||||||
|
gas.Add(gas, gt.CreateBySuicide) |
||||||
|
} |
||||||
|
} else if !env.StateDB.Exist(address) { |
||||||
|
gas.Add(gas, gt.CreateBySuicide) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if !env.StateDB.HasSuicided(contract.Address()) { |
||||||
|
env.StateDB.AddRefund(params.SuicideRefundGas) |
||||||
|
} |
||||||
|
return gas |
||||||
|
} |
||||||
|
|
||||||
|
func gasDelegateCall(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
gas := new(big.Int).Add(gt.Calls, memoryGasCost(mem, memorySize)) |
||||||
|
|
||||||
|
cg := callGas(gt, contract.Gas, gas, stack.data[stack.len()-1]) |
||||||
|
// 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
|
||||||
|
// We replace the stack item so that it's available when the opCall instruction is
|
||||||
|
// called.
|
||||||
|
stack.data[stack.len()-1] = cg |
||||||
|
|
||||||
|
return gas.Add(gas, cg) |
||||||
|
} |
||||||
|
|
||||||
|
func gasPush(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return GasFastestStep |
||||||
|
} |
||||||
|
|
||||||
|
func gasSwap(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return GasFastestStep |
||||||
|
} |
||||||
|
|
||||||
|
func gasDup(gt params.GasTable, env *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize *big.Int) *big.Int { |
||||||
|
return GasFastestStep |
||||||
|
} |
@ -1,512 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"math/big" |
|
||||||
"sync/atomic" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/logger" |
|
||||||
"github.com/ethereum/go-ethereum/logger/glog" |
|
||||||
"github.com/ethereum/go-ethereum/params" |
|
||||||
"github.com/hashicorp/golang-lru" |
|
||||||
) |
|
||||||
|
|
||||||
// progStatus is the type for the JIT program status.
|
|
||||||
type progStatus int32 |
|
||||||
|
|
||||||
const ( |
|
||||||
progUnknown progStatus = iota // unknown status
|
|
||||||
progCompile // compile status
|
|
||||||
progReady // ready for use status
|
|
||||||
progError // error status (usually caused during compilation)
|
|
||||||
|
|
||||||
defaultJitMaxCache int = 64 // maximum amount of jit cached programs
|
|
||||||
) |
|
||||||
|
|
||||||
var MaxProgSize int // Max cache size for JIT programs
|
|
||||||
|
|
||||||
var programs *lru.Cache // lru cache for the JIT programs.
|
|
||||||
|
|
||||||
func init() { |
|
||||||
SetJITCacheSize(defaultJitMaxCache) |
|
||||||
} |
|
||||||
|
|
||||||
// SetJITCacheSize recreates the program cache with the max given size. Setting
|
|
||||||
// a new cache is **not** thread safe. Use with caution.
|
|
||||||
func SetJITCacheSize(size int) { |
|
||||||
programs, _ = lru.New(size) |
|
||||||
} |
|
||||||
|
|
||||||
// GetProgram returns the program by id or nil when non-existent
|
|
||||||
func GetProgram(id common.Hash) *Program { |
|
||||||
if p, ok := programs.Get(id); ok { |
|
||||||
return p.(*Program) |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// GenProgramStatus returns the status of the given program id
|
|
||||||
func GetProgramStatus(id common.Hash) progStatus { |
|
||||||
program := GetProgram(id) |
|
||||||
if program != nil { |
|
||||||
return progStatus(atomic.LoadInt32(&program.status)) |
|
||||||
} |
|
||||||
|
|
||||||
return progUnknown |
|
||||||
} |
|
||||||
|
|
||||||
// Program is a compiled program for the JIT VM and holds all required for
|
|
||||||
// running a compiled JIT program.
|
|
||||||
type Program struct { |
|
||||||
Id common.Hash // Id of the program
|
|
||||||
status int32 // status should be accessed atomically
|
|
||||||
|
|
||||||
contract *Contract |
|
||||||
|
|
||||||
instructions []programInstruction // instruction set
|
|
||||||
mapping map[uint64]uint64 // real PC mapping to array indices
|
|
||||||
destinations map[uint64]struct{} // cached jump destinations
|
|
||||||
|
|
||||||
code []byte |
|
||||||
} |
|
||||||
|
|
||||||
// NewProgram returns a new JIT program
|
|
||||||
func NewProgram(code []byte) *Program { |
|
||||||
program := &Program{ |
|
||||||
Id: crypto.Keccak256Hash(code), |
|
||||||
mapping: make(map[uint64]uint64), |
|
||||||
destinations: make(map[uint64]struct{}), |
|
||||||
code: code, |
|
||||||
} |
|
||||||
|
|
||||||
programs.Add(program.Id, program) |
|
||||||
return program |
|
||||||
} |
|
||||||
|
|
||||||
func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) { |
|
||||||
// PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit
|
|
||||||
// PUSH is also allowed to calculate the same price for all PUSHes
|
|
||||||
// DUP requirements are handled elsewhere (except for the stack limit check)
|
|
||||||
baseOp := op |
|
||||||
if op >= PUSH1 && op <= PUSH32 { |
|
||||||
baseOp = PUSH1 |
|
||||||
} |
|
||||||
if op >= DUP1 && op <= DUP16 { |
|
||||||
baseOp = DUP1 |
|
||||||
} |
|
||||||
base := _baseCheck[baseOp] |
|
||||||
|
|
||||||
returns := op == RETURN || op == SUICIDE || op == STOP |
|
||||||
instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns} |
|
||||||
|
|
||||||
p.instructions = append(p.instructions, instr) |
|
||||||
p.mapping[pc] = uint64(len(p.instructions) - 1) |
|
||||||
} |
|
||||||
|
|
||||||
// CompileProgram compiles the given program and return an error when it fails
|
|
||||||
func CompileProgram(program *Program) (err error) { |
|
||||||
if progStatus(atomic.LoadInt32(&program.status)) == progCompile { |
|
||||||
return nil |
|
||||||
} |
|
||||||
atomic.StoreInt32(&program.status, int32(progCompile)) |
|
||||||
defer func() { |
|
||||||
if err != nil { |
|
||||||
atomic.StoreInt32(&program.status, int32(progError)) |
|
||||||
} else { |
|
||||||
atomic.StoreInt32(&program.status, int32(progReady)) |
|
||||||
} |
|
||||||
}() |
|
||||||
if glog.V(logger.Debug) { |
|
||||||
glog.Infof("compiling %x\n", program.Id[:4]) |
|
||||||
tstart := time.Now() |
|
||||||
defer func() { |
|
||||||
glog.Infof("compiled %x instrc: %d time: %v\n", program.Id[:4], len(program.instructions), time.Since(tstart)) |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
// loop thru the opcodes and "compile" in to instructions
|
|
||||||
for pc := uint64(0); pc < uint64(len(program.code)); pc++ { |
|
||||||
switch op := OpCode(program.code[pc]); op { |
|
||||||
case ADD: |
|
||||||
program.addInstr(op, pc, opAdd, nil) |
|
||||||
case SUB: |
|
||||||
program.addInstr(op, pc, opSub, nil) |
|
||||||
case MUL: |
|
||||||
program.addInstr(op, pc, opMul, nil) |
|
||||||
case DIV: |
|
||||||
program.addInstr(op, pc, opDiv, nil) |
|
||||||
case SDIV: |
|
||||||
program.addInstr(op, pc, opSdiv, nil) |
|
||||||
case MOD: |
|
||||||
program.addInstr(op, pc, opMod, nil) |
|
||||||
case SMOD: |
|
||||||
program.addInstr(op, pc, opSmod, nil) |
|
||||||
case EXP: |
|
||||||
program.addInstr(op, pc, opExp, nil) |
|
||||||
case SIGNEXTEND: |
|
||||||
program.addInstr(op, pc, opSignExtend, nil) |
|
||||||
case NOT: |
|
||||||
program.addInstr(op, pc, opNot, nil) |
|
||||||
case LT: |
|
||||||
program.addInstr(op, pc, opLt, nil) |
|
||||||
case GT: |
|
||||||
program.addInstr(op, pc, opGt, nil) |
|
||||||
case SLT: |
|
||||||
program.addInstr(op, pc, opSlt, nil) |
|
||||||
case SGT: |
|
||||||
program.addInstr(op, pc, opSgt, nil) |
|
||||||
case EQ: |
|
||||||
program.addInstr(op, pc, opEq, nil) |
|
||||||
case ISZERO: |
|
||||||
program.addInstr(op, pc, opIszero, nil) |
|
||||||
case AND: |
|
||||||
program.addInstr(op, pc, opAnd, nil) |
|
||||||
case OR: |
|
||||||
program.addInstr(op, pc, opOr, nil) |
|
||||||
case XOR: |
|
||||||
program.addInstr(op, pc, opXor, nil) |
|
||||||
case BYTE: |
|
||||||
program.addInstr(op, pc, opByte, nil) |
|
||||||
case ADDMOD: |
|
||||||
program.addInstr(op, pc, opAddmod, nil) |
|
||||||
case MULMOD: |
|
||||||
program.addInstr(op, pc, opMulmod, nil) |
|
||||||
case SHA3: |
|
||||||
program.addInstr(op, pc, opSha3, nil) |
|
||||||
case ADDRESS: |
|
||||||
program.addInstr(op, pc, opAddress, nil) |
|
||||||
case BALANCE: |
|
||||||
program.addInstr(op, pc, opBalance, nil) |
|
||||||
case ORIGIN: |
|
||||||
program.addInstr(op, pc, opOrigin, nil) |
|
||||||
case CALLER: |
|
||||||
program.addInstr(op, pc, opCaller, nil) |
|
||||||
case CALLVALUE: |
|
||||||
program.addInstr(op, pc, opCallValue, nil) |
|
||||||
case CALLDATALOAD: |
|
||||||
program.addInstr(op, pc, opCalldataLoad, nil) |
|
||||||
case CALLDATASIZE: |
|
||||||
program.addInstr(op, pc, opCalldataSize, nil) |
|
||||||
case CALLDATACOPY: |
|
||||||
program.addInstr(op, pc, opCalldataCopy, nil) |
|
||||||
case CODESIZE: |
|
||||||
program.addInstr(op, pc, opCodeSize, nil) |
|
||||||
case EXTCODESIZE: |
|
||||||
program.addInstr(op, pc, opExtCodeSize, nil) |
|
||||||
case CODECOPY: |
|
||||||
program.addInstr(op, pc, opCodeCopy, nil) |
|
||||||
case EXTCODECOPY: |
|
||||||
program.addInstr(op, pc, opExtCodeCopy, nil) |
|
||||||
case GASPRICE: |
|
||||||
program.addInstr(op, pc, opGasprice, nil) |
|
||||||
case BLOCKHASH: |
|
||||||
program.addInstr(op, pc, opBlockhash, nil) |
|
||||||
case COINBASE: |
|
||||||
program.addInstr(op, pc, opCoinbase, nil) |
|
||||||
case TIMESTAMP: |
|
||||||
program.addInstr(op, pc, opTimestamp, nil) |
|
||||||
case NUMBER: |
|
||||||
program.addInstr(op, pc, opNumber, nil) |
|
||||||
case DIFFICULTY: |
|
||||||
program.addInstr(op, pc, opDifficulty, nil) |
|
||||||
case GASLIMIT: |
|
||||||
program.addInstr(op, pc, opGasLimit, nil) |
|
||||||
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: |
|
||||||
size := uint64(op - PUSH1 + 1) |
|
||||||
bytes := getData([]byte(program.code), new(big.Int).SetUint64(pc+1), new(big.Int).SetUint64(size)) |
|
||||||
|
|
||||||
program.addInstr(op, pc, opPush, common.Bytes2Big(bytes)) |
|
||||||
|
|
||||||
pc += size |
|
||||||
|
|
||||||
case POP: |
|
||||||
program.addInstr(op, pc, opPop, nil) |
|
||||||
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: |
|
||||||
program.addInstr(op, pc, opDup, big.NewInt(int64(op-DUP1+1))) |
|
||||||
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: |
|
||||||
program.addInstr(op, pc, opSwap, big.NewInt(int64(op-SWAP1+2))) |
|
||||||
case LOG0, LOG1, LOG2, LOG3, LOG4: |
|
||||||
program.addInstr(op, pc, opLog, big.NewInt(int64(op-LOG0))) |
|
||||||
case MLOAD: |
|
||||||
program.addInstr(op, pc, opMload, nil) |
|
||||||
case MSTORE: |
|
||||||
program.addInstr(op, pc, opMstore, nil) |
|
||||||
case MSTORE8: |
|
||||||
program.addInstr(op, pc, opMstore8, nil) |
|
||||||
case SLOAD: |
|
||||||
program.addInstr(op, pc, opSload, nil) |
|
||||||
case SSTORE: |
|
||||||
program.addInstr(op, pc, opSstore, nil) |
|
||||||
case JUMP: |
|
||||||
program.addInstr(op, pc, opJump, nil) |
|
||||||
case JUMPI: |
|
||||||
program.addInstr(op, pc, opJumpi, nil) |
|
||||||
case JUMPDEST: |
|
||||||
program.addInstr(op, pc, opJumpdest, nil) |
|
||||||
program.destinations[pc] = struct{}{} |
|
||||||
case PC: |
|
||||||
program.addInstr(op, pc, opPc, big.NewInt(int64(pc))) |
|
||||||
case MSIZE: |
|
||||||
program.addInstr(op, pc, opMsize, nil) |
|
||||||
case GAS: |
|
||||||
program.addInstr(op, pc, opGas, nil) |
|
||||||
case CREATE: |
|
||||||
program.addInstr(op, pc, opCreate, nil) |
|
||||||
case DELEGATECALL: |
|
||||||
// Instruction added regardless of homestead phase.
|
|
||||||
// Homestead (and execution of the opcode) is checked during
|
|
||||||
// runtime.
|
|
||||||
program.addInstr(op, pc, opDelegateCall, nil) |
|
||||||
case CALL: |
|
||||||
program.addInstr(op, pc, opCall, nil) |
|
||||||
case CALLCODE: |
|
||||||
program.addInstr(op, pc, opCallCode, nil) |
|
||||||
case RETURN: |
|
||||||
program.addInstr(op, pc, opReturn, nil) |
|
||||||
case SUICIDE: |
|
||||||
program.addInstr(op, pc, opSuicide, nil) |
|
||||||
case STOP: // Stop the contract
|
|
||||||
program.addInstr(op, pc, opStop, nil) |
|
||||||
default: |
|
||||||
program.addInstr(op, pc, nil, nil) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
optimiseProgram(program) |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// RunProgram runs the program given the environment and contract and returns an
|
|
||||||
// error if the execution failed (non-consensus)
|
|
||||||
func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) { |
|
||||||
return runProgram(program, 0, NewMemory(), newstack(), env, contract, input) |
|
||||||
} |
|
||||||
|
|
||||||
func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env *Environment, contract *Contract, input []byte) ([]byte, error) { |
|
||||||
contract.Input = input |
|
||||||
|
|
||||||
var ( |
|
||||||
pc uint64 = program.mapping[pcstart] |
|
||||||
instrCount = 0 |
|
||||||
) |
|
||||||
|
|
||||||
if glog.V(logger.Debug) { |
|
||||||
glog.Infof("running JIT program %x\n", program.Id[:4]) |
|
||||||
tstart := time.Now() |
|
||||||
defer func() { |
|
||||||
glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount) |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
homestead := env.ChainConfig().IsHomestead(env.BlockNumber) |
|
||||||
for pc < uint64(len(program.instructions)) { |
|
||||||
instrCount++ |
|
||||||
|
|
||||||
instr := program.instructions[pc] |
|
||||||
if instr.Op() == DELEGATECALL && !homestead { |
|
||||||
return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) |
|
||||||
} |
|
||||||
|
|
||||||
ret, err := instr.do(program, &pc, env, contract, mem, stack) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
if instr.halts() { |
|
||||||
return ret, nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
contract.Input = nil |
|
||||||
|
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
// validDest checks if the given destination is a valid one given the
|
|
||||||
// destination table of the program
|
|
||||||
func validDest(dests map[uint64]struct{}, dest *big.Int) bool { |
|
||||||
// PC cannot go beyond len(code) and certainly can't be bigger than 64bits.
|
|
||||||
// Don't bother checking for JUMPDEST in that case.
|
|
||||||
if dest.Cmp(bigMaxUint64) > 0 { |
|
||||||
return false |
|
||||||
} |
|
||||||
_, ok := dests[dest.Uint64()] |
|
||||||
return ok |
|
||||||
} |
|
||||||
|
|
||||||
// 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, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) { |
|
||||||
var ( |
|
||||||
gas = new(big.Int) |
|
||||||
newMemSize *big.Int = new(big.Int) |
|
||||||
) |
|
||||||
err := jitBaseCheck(instr, stack, gas) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
|
|
||||||
// stack Check, memory resize & gas phase
|
|
||||||
switch op := instr.op; op { |
|
||||||
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: |
|
||||||
n := int(op - SWAP1 + 2) |
|
||||||
err := stack.require(n) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
gas.Set(GasFastestStep) |
|
||||||
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: |
|
||||||
n := int(op - DUP1 + 1) |
|
||||||
err := stack.require(n) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
gas.Set(GasFastestStep) |
|
||||||
case LOG0, LOG1, LOG2, LOG3, LOG4: |
|
||||||
n := int(op - LOG0) |
|
||||||
err := stack.require(n + 2) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
|
|
||||||
mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1] |
|
||||||
|
|
||||||
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)) |
|
||||||
|
|
||||||
newMemSize = calcMemSize(mStart, mSize) |
|
||||||
case EXP: |
|
||||||
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas)) |
|
||||||
case SSTORE: |
|
||||||
err := stack.require(2) |
|
||||||
if err != nil { |
|
||||||
return nil, nil, err |
|
||||||
} |
|
||||||
|
|
||||||
var g *big.Int |
|
||||||
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1] |
|
||||||
val := env.StateDB.GetState(contract.Address(), common.BigToHash(x)) |
|
||||||
|
|
||||||
// This checks for 3 scenario's and calculates gas accordingly
|
|
||||||
// 1. From a zero-value address to a non-zero value (NEW VALUE)
|
|
||||||
// 2. From a non-zero value address to a zero-value address (DELETE)
|
|
||||||
// 3. From a non-zero to a non-zero (CHANGE)
|
|
||||||
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { |
|
||||||
g = params.SstoreSetGas |
|
||||||
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { |
|
||||||
env.StateDB.AddRefund(params.SstoreRefundGas) |
|
||||||
|
|
||||||
g = params.SstoreClearGas |
|
||||||
} else { |
|
||||||
g = params.SstoreResetGas |
|
||||||
} |
|
||||||
gas.Set(g) |
|
||||||
case SUICIDE: |
|
||||||
if !env.StateDB.HasSuicided(contract.Address()) { |
|
||||||
env.StateDB.AddRefund(params.SuicideRefundGas) |
|
||||||
} |
|
||||||
case MLOAD: |
|
||||||
newMemSize = calcMemSize(stack.peek(), u256(32)) |
|
||||||
case MSTORE8: |
|
||||||
newMemSize = calcMemSize(stack.peek(), u256(1)) |
|
||||||
case MSTORE: |
|
||||||
newMemSize = calcMemSize(stack.peek(), u256(32)) |
|
||||||
case RETURN: |
|
||||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) |
|
||||||
case SHA3: |
|
||||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) |
|
||||||
|
|
||||||
words := toWordSize(stack.data[stack.len()-2]) |
|
||||||
gas.Add(gas, words.Mul(words, params.Sha3WordGas)) |
|
||||||
case CALLDATACOPY: |
|
||||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) |
|
||||||
|
|
||||||
words := toWordSize(stack.data[stack.len()-3]) |
|
||||||
gas.Add(gas, words.Mul(words, params.CopyGas)) |
|
||||||
case CODECOPY: |
|
||||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) |
|
||||||
|
|
||||||
words := toWordSize(stack.data[stack.len()-3]) |
|
||||||
gas.Add(gas, words.Mul(words, params.CopyGas)) |
|
||||||
case EXTCODECOPY: |
|
||||||
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4]) |
|
||||||
|
|
||||||
words := toWordSize(stack.data[stack.len()-4]) |
|
||||||
gas.Add(gas, words.Mul(words, params.CopyGas)) |
|
||||||
|
|
||||||
case CREATE: |
|
||||||
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3]) |
|
||||||
case CALL, CALLCODE: |
|
||||||
gas.Add(gas, stack.data[stack.len()-1]) |
|
||||||
|
|
||||||
if op == CALL { |
|
||||||
if !env.StateDB.Exist(common.BigToAddress(stack.data[stack.len()-2])) { |
|
||||||
gas.Add(gas, params.CallNewAccountGas) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if len(stack.data[stack.len()-3].Bytes()) > 0 { |
|
||||||
gas.Add(gas, params.CallValueTransferGas) |
|
||||||
} |
|
||||||
|
|
||||||
x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) |
|
||||||
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) |
|
||||||
|
|
||||||
newMemSize = common.BigMax(x, y) |
|
||||||
case DELEGATECALL: |
|
||||||
gas.Add(gas, stack.data[stack.len()-1]) |
|
||||||
|
|
||||||
x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) |
|
||||||
y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) |
|
||||||
|
|
||||||
newMemSize = common.BigMax(x, y) |
|
||||||
} |
|
||||||
quadMemGas(mem, newMemSize, gas) |
|
||||||
|
|
||||||
return newMemSize, gas, nil |
|
||||||
} |
|
||||||
|
|
||||||
// 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 |
|
||||||
} |
|
@ -1,123 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/big" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/logger" |
|
||||||
"github.com/ethereum/go-ethereum/logger/glog" |
|
||||||
) |
|
||||||
|
|
||||||
// optimeProgram optimises a JIT program creating segments out of program
|
|
||||||
// instructions. Currently covered are multi-pushes and static jumps
|
|
||||||
func optimiseProgram(program *Program) { |
|
||||||
var load []instruction |
|
||||||
|
|
||||||
var ( |
|
||||||
statsJump = 0 |
|
||||||
statsPush = 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) |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
code := Parse(program.code) |
|
||||||
for _, test := range [][]OpCode{ |
|
||||||
[]OpCode{PUSH, PUSH, ADD}, |
|
||||||
[]OpCode{PUSH, PUSH, SUB}, |
|
||||||
[]OpCode{PUSH, PUSH, MUL}, |
|
||||||
[]OpCode{PUSH, PUSH, DIV}, |
|
||||||
} { |
|
||||||
matchCount := 0 |
|
||||||
MatchFn(code, test, func(i int) bool { |
|
||||||
matchCount++ |
|
||||||
return true |
|
||||||
}) |
|
||||||
fmt.Printf("found %d match count on: %v\n", matchCount, test) |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
for i := 0; i < len(program.instructions); i++ { |
|
||||||
instr := program.instructions[i].(instruction) |
|
||||||
|
|
||||||
switch { |
|
||||||
case instr.op.IsPush(): |
|
||||||
load = append(load, instr) |
|
||||||
case instr.op.IsStaticJump(): |
|
||||||
if len(load) == 0 { |
|
||||||
continue |
|
||||||
} |
|
||||||
// if the push load is greater than 1, finalise that
|
|
||||||
// segment first
|
|
||||||
if len(load) > 2 { |
|
||||||
seg, size := makePushSeg(load[:len(load)-1]) |
|
||||||
program.instructions[i-size-1] = seg |
|
||||||
statsPush++ |
|
||||||
} |
|
||||||
// create a segment consisting of a pre determined
|
|
||||||
// jump, destination and validity.
|
|
||||||
seg := makeStaticJumpSeg(load[len(load)-1].data, program) |
|
||||||
program.instructions[i-1] = seg |
|
||||||
statsJump++ |
|
||||||
|
|
||||||
load = nil |
|
||||||
default: |
|
||||||
// create a new N pushes segment
|
|
||||||
if len(load) > 1 { |
|
||||||
seg, size := makePushSeg(load) |
|
||||||
program.instructions[i-size] = seg |
|
||||||
statsPush++ |
|
||||||
} |
|
||||||
load = nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// makePushSeg creates a new push segment from N amount of push instructions
|
|
||||||
func makePushSeg(instrs []instruction) (pushSeg, int) { |
|
||||||
var ( |
|
||||||
data []*big.Int |
|
||||||
gas = new(big.Int) |
|
||||||
) |
|
||||||
|
|
||||||
for _, instr := range instrs { |
|
||||||
data = append(data, instr.data) |
|
||||||
gas.Add(gas, instr.gas) |
|
||||||
} |
|
||||||
|
|
||||||
return pushSeg{data, gas}, len(instrs) |
|
||||||
} |
|
||||||
|
|
||||||
// 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) |
|
||||||
|
|
||||||
contract := &Contract{Code: program.code} |
|
||||||
pos, err := jump(program.mapping, program.destinations, contract, to) |
|
||||||
return jumpSeg{pos, err, gas} |
|
||||||
} |
|
@ -1,160 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/big" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/params" |
|
||||||
) |
|
||||||
|
|
||||||
const maxRun = 1000 |
|
||||||
|
|
||||||
func TestSegmenting(t *testing.T) { |
|
||||||
prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0}) |
|
||||||
err := CompileProgram(prog) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
if instr, ok := prog.instructions[0].(pushSeg); ok { |
|
||||||
if len(instr.data) != 2 { |
|
||||||
t.Error("expected 2 element width pushSegment, got", len(instr.data)) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0]) |
|
||||||
} |
|
||||||
|
|
||||||
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) |
|
||||||
err = CompileProgram(prog) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if _, ok := prog.instructions[1].(jumpSeg); ok { |
|
||||||
} else { |
|
||||||
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1]) |
|
||||||
} |
|
||||||
|
|
||||||
prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) |
|
||||||
err = CompileProgram(prog) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
if instr, ok := prog.instructions[0].(pushSeg); ok { |
|
||||||
if len(instr.data) != 2 { |
|
||||||
t.Error("expected 2 element width pushSegment, got", len(instr.data)) |
|
||||||
} |
|
||||||
} else { |
|
||||||
t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0]) |
|
||||||
} |
|
||||||
if _, ok := prog.instructions[2].(jumpSeg); ok { |
|
||||||
} else { |
|
||||||
t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestCompiling(t *testing.T) { |
|
||||||
prog := NewProgram([]byte{0x60, 0x10}) |
|
||||||
err := CompileProgram(prog) |
|
||||||
if err != nil { |
|
||||||
t.Error("didn't expect compile error") |
|
||||||
} |
|
||||||
|
|
||||||
if len(prog.instructions) != 1 { |
|
||||||
t.Error("expected 1 compiled instruction, got", len(prog.instructions)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestResetInput(t *testing.T) { |
|
||||||
var sender account |
|
||||||
|
|
||||||
env := NewEnvironment(Context{}, nil, params.TestChainConfig, Config{}) |
|
||||||
contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) |
|
||||||
contract.CodeAddr = &common.Address{} |
|
||||||
|
|
||||||
program := NewProgram([]byte{}) |
|
||||||
RunProgram(program, env, contract, []byte{0xbe, 0xef}) |
|
||||||
if contract.Input != nil { |
|
||||||
t.Errorf("expected input to be nil, got %x", contract.Input) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestPcMappingToInstruction(t *testing.T) { |
|
||||||
program := NewProgram([]byte{byte(PUSH2), 0xbe, 0xef, byte(ADD)}) |
|
||||||
CompileProgram(program) |
|
||||||
if program.mapping[3] != 1 { |
|
||||||
t.Error("expected mapping PC 4 to me instr no. 2, got", program.mapping[4]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var benchmarks = map[string]vmBench{ |
|
||||||
"pushes": vmBench{ |
|
||||||
false, false, false, |
|
||||||
common.Hex2Bytes("600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01"), nil, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkPushes(b *testing.B) { |
|
||||||
runVmBench(benchmarks["pushes"], b) |
|
||||||
} |
|
||||||
|
|
||||||
type vmBench struct { |
|
||||||
precompile bool // compile prior to executing
|
|
||||||
nojit bool // ignore jit (sets DisbaleJit = true
|
|
||||||
forcejit bool // forces the jit, precompile is ignored
|
|
||||||
|
|
||||||
code []byte |
|
||||||
input []byte |
|
||||||
} |
|
||||||
|
|
||||||
type account struct{} |
|
||||||
|
|
||||||
func (account) SubBalance(amount *big.Int) {} |
|
||||||
func (account) AddBalance(amount *big.Int) {} |
|
||||||
func (account) SetAddress(common.Address) {} |
|
||||||
func (account) Value() *big.Int { return nil } |
|
||||||
func (account) SetBalance(*big.Int) {} |
|
||||||
func (account) SetNonce(uint64) {} |
|
||||||
func (account) Balance() *big.Int { return nil } |
|
||||||
func (account) Address() common.Address { return common.Address{} } |
|
||||||
func (account) ReturnGas(*big.Int) {} |
|
||||||
func (account) SetCode(common.Hash, []byte) {} |
|
||||||
func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} |
|
||||||
|
|
||||||
func runVmBench(test vmBench, b *testing.B) { |
|
||||||
var sender account |
|
||||||
|
|
||||||
if test.precompile && !test.forcejit { |
|
||||||
NewProgram(test.code) |
|
||||||
} |
|
||||||
env := NewEnvironment(Context{}, nil, params.TestChainConfig, Config{EnableJit: !test.nojit, ForceJit: test.forcejit}) |
|
||||||
|
|
||||||
b.ResetTimer() |
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) |
|
||||||
context.Code = test.code |
|
||||||
context.CodeAddr = &common.Address{} |
|
||||||
_, err := env.EVM().Run(context, test.input) |
|
||||||
if err != nil { |
|
||||||
b.Error(err) |
|
||||||
b.FailNow() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,68 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
// Parse parses all opcodes from the given code byte slice. This function
|
|
||||||
// performs no error checking and may return non-existing opcodes.
|
|
||||||
func Parse(code []byte) (opcodes []OpCode) { |
|
||||||
for pc := uint64(0); pc < uint64(len(code)); pc++ { |
|
||||||
op := OpCode(code[pc]) |
|
||||||
|
|
||||||
switch op { |
|
||||||
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: |
|
||||||
a := uint64(op) - uint64(PUSH1) + 1 |
|
||||||
pc += a |
|
||||||
opcodes = append(opcodes, PUSH) |
|
||||||
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: |
|
||||||
opcodes = append(opcodes, DUP) |
|
||||||
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: |
|
||||||
opcodes = append(opcodes, SWAP) |
|
||||||
default: |
|
||||||
opcodes = append(opcodes, op) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return opcodes |
|
||||||
} |
|
||||||
|
|
||||||
// MatchFn searcher for match in the given input and calls matcheFn if it finds
|
|
||||||
// an appropriate match. matcherFn yields the starting position in the input.
|
|
||||||
// MatchFn will continue to search for a match until it reaches the end of the
|
|
||||||
// buffer or if matcherFn return false.
|
|
||||||
func MatchFn(input, match []OpCode, matcherFn func(int) bool) { |
|
||||||
// short circuit if either input or match is empty or if the match is
|
|
||||||
// greater than the input
|
|
||||||
if len(input) == 0 || len(match) == 0 || len(match) > len(input) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
main: |
|
||||||
for i, op := range input[:len(input)+1-len(match)] { |
|
||||||
// match first opcode and continue search
|
|
||||||
if op == match[0] { |
|
||||||
for j := 1; j < len(match); j++ { |
|
||||||
if input[i+j] != match[j] { |
|
||||||
continue main |
|
||||||
} |
|
||||||
} |
|
||||||
// check for abort instruction
|
|
||||||
if !matcherFn(i) { |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,84 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
type matchTest struct { |
|
||||||
input []OpCode |
|
||||||
match []OpCode |
|
||||||
matches int |
|
||||||
} |
|
||||||
|
|
||||||
func TestMatchFn(t *testing.T) { |
|
||||||
tests := []matchTest{ |
|
||||||
matchTest{ |
|
||||||
[]OpCode{PUSH1, PUSH1, MSTORE, JUMP}, |
|
||||||
[]OpCode{PUSH1, MSTORE}, |
|
||||||
1, |
|
||||||
}, |
|
||||||
matchTest{ |
|
||||||
[]OpCode{PUSH1, PUSH1, MSTORE, JUMP}, |
|
||||||
[]OpCode{PUSH1, MSTORE, PUSH1}, |
|
||||||
0, |
|
||||||
}, |
|
||||||
matchTest{ |
|
||||||
[]OpCode{}, |
|
||||||
[]OpCode{PUSH1}, |
|
||||||
0, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
for i, test := range tests { |
|
||||||
var matchCount int |
|
||||||
MatchFn(test.input, test.match, func(i int) bool { |
|
||||||
matchCount++ |
|
||||||
return true |
|
||||||
}) |
|
||||||
if matchCount != test.matches { |
|
||||||
t.Errorf("match count failed on test[%d]: expected %d matches, got %d", i, test.matches, matchCount) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
type parseTest struct { |
|
||||||
base OpCode |
|
||||||
size int |
|
||||||
output OpCode |
|
||||||
} |
|
||||||
|
|
||||||
func TestParser(t *testing.T) { |
|
||||||
tests := []parseTest{ |
|
||||||
parseTest{PUSH1, 32, PUSH}, |
|
||||||
parseTest{DUP1, 16, DUP}, |
|
||||||
parseTest{SWAP1, 16, SWAP}, |
|
||||||
parseTest{MSTORE, 1, MSTORE}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, test := range tests { |
|
||||||
for i := 0; i < test.size; i++ { |
|
||||||
code := append([]byte{byte(byte(test.base) + byte(i))}, make([]byte, i+1)...) |
|
||||||
output := Parse(code) |
|
||||||
if len(output) == 0 { |
|
||||||
t.Fatal("empty output") |
|
||||||
} |
|
||||||
if output[0] != test.output { |
|
||||||
t.Errorf("%v failed: expected %v but got %v", test.base+OpCode(i), test.output, output[0]) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/big" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params" |
|
||||||
) |
|
||||||
|
|
||||||
func TestInit(t *testing.T) { |
|
||||||
jumpTable := newJumpTable(¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}, big.NewInt(0)) |
|
||||||
if jumpTable[DELEGATECALL].valid { |
|
||||||
t.Error("Expected DELEGATECALL not to be present") |
|
||||||
} |
|
||||||
|
|
||||||
for _, n := range []int64{1, 2, 100} { |
|
||||||
jumpTable := newJumpTable(¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}, big.NewInt(n)) |
|
||||||
if !jumpTable[DELEGATECALL].valid { |
|
||||||
t.Error("Expected DELEGATECALL to be present for block", n) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,68 @@ |
|||||||
|
package vm |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
func memorySha3(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), stack.Back(1)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryCalldataCopy(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), stack.Back(2)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryCodeCopy(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), stack.Back(2)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryExtCodeCopy(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(1), stack.Back(3)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryMLoad(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), big.NewInt(32)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryMStore8(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), big.NewInt(1)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryMStore(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), big.NewInt(32)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryCreate(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(1), stack.Back(2)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryCall(stack *Stack) *big.Int { |
||||||
|
x := calcMemSize(stack.Back(5), stack.Back(6)) |
||||||
|
y := calcMemSize(stack.Back(3), stack.Back(4)) |
||||||
|
|
||||||
|
return common.BigMax(x, y) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryCallCode(stack *Stack) *big.Int { |
||||||
|
x := calcMemSize(stack.Back(5), stack.Back(6)) |
||||||
|
y := calcMemSize(stack.Back(3), stack.Back(4)) |
||||||
|
|
||||||
|
return common.BigMax(x, y) |
||||||
|
} |
||||||
|
func memoryDelegateCall(stack *Stack) *big.Int { |
||||||
|
x := calcMemSize(stack.Back(4), stack.Back(5)) |
||||||
|
y := calcMemSize(stack.Back(2), stack.Back(3)) |
||||||
|
|
||||||
|
return common.BigMax(x, y) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryReturn(stack *Stack) *big.Int { |
||||||
|
return calcMemSize(stack.Back(0), stack.Back(1)) |
||||||
|
} |
||||||
|
|
||||||
|
func memoryLog(stack *Stack) *big.Int { |
||||||
|
mSize, mStart := stack.Back(1), stack.Back(0) |
||||||
|
return calcMemSize(mStart, mSize) |
||||||
|
} |
@ -1,60 +0,0 @@ |
|||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm |
|
||||||
|
|
||||||
import "math/big" |
|
||||||
|
|
||||||
type jumpSeg struct { |
|
||||||
pos uint64 |
|
||||||
err error |
|
||||||
gas *big.Int |
|
||||||
} |
|
||||||
|
|
||||||
func (j jumpSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { |
|
||||||
if !contract.UseGas(j.gas) { |
|
||||||
return nil, OutOfGasError |
|
||||||
} |
|
||||||
if j.err != nil { |
|
||||||
return nil, j.err |
|
||||||
} |
|
||||||
*pc = j.pos |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
func (s jumpSeg) halts() bool { return false } |
|
||||||
func (s jumpSeg) Op() OpCode { return 0 } |
|
||||||
|
|
||||||
type pushSeg struct { |
|
||||||
data []*big.Int |
|
||||||
gas *big.Int |
|
||||||
} |
|
||||||
|
|
||||||
func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { |
|
||||||
// Use the calculated gas. When insufficient gas is present, use all gas and return an
|
|
||||||
// Out Of Gas error
|
|
||||||
if !contract.UseGas(s.gas) { |
|
||||||
return nil, OutOfGasError |
|
||||||
} |
|
||||||
|
|
||||||
for _, d := range s.data { |
|
||||||
stack.push(new(big.Int).Set(d)) |
|
||||||
} |
|
||||||
*pc += uint64(len(s.data)) |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (s pushSeg) halts() bool { return false } |
|
||||||
func (s pushSeg) Op() OpCode { return 0 } |
|
@ -0,0 +1,20 @@ |
|||||||
|
package vm |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
) |
||||||
|
|
||||||
|
func makeStackFunc(pop, push int) stackValidationFunc { |
||||||
|
return func(stack *Stack) error { |
||||||
|
if err := stack.require(pop); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if push > 0 && int64(stack.len()-pop+push) > params.StackLimit.Int64() { |
||||||
|
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
@ -1,17 +0,0 @@ |
|||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package core |
|
Loading…
Reference in new issue