go-ethereum/core/vm/vm.go

381 lines
11 KiB
Go
Raw Normal View History

2015-07-06 19:54:22 -05:00
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
2015-07-06 19:54:22 -05:00
//
// The go-ethereum library is free software: you can redistribute it and/or modify
2015-07-06 19:54:22 -05:00
// 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,
2015-07-06 19:54:22 -05:00
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2015-07-06 19:54:22 -05:00
// 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/>.
2015-07-06 19:54:22 -05:00
2014-10-18 06:31:20 -05:00
package vm
2015-01-19 04:18:34 -06:00
import (
"fmt"
"math/big"
"time"
2015-03-16 05:27:38 -05:00
"github.com/ethereum/go-ethereum/common"
2015-03-23 15:48:31 -05:00
"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"
2015-01-19 04:18:34 -06:00
)
2014-10-15 10:12:26 -05:00
// Config are the configuration options for the EVM
type Config struct {
Debug bool
EnableJit bool
ForceJit bool
Tracer Tracer
}
// EVM is used to run Ethereum based contracts and will utilise the
// passed environment to query external sources for state information.
// The EVM will run the byte code VM or JIT VM based on the passed
// configuration.
type EVM struct {
env Environment
jumpTable vmJumpTable
cfg Config
2015-01-19 04:18:34 -06:00
}
// New returns a new instance of the EVM.
func New(env Environment, cfg Config) *EVM {
return &EVM{
env: env,
jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber()),
cfg: cfg,
}
2015-01-19 04:18:34 -06:00
}
// Run loops and evaluates the contract's code with the given input data
func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
evm.env.SetDepth(evm.env.Depth() + 1)
defer evm.env.SetDepth(evm.env.Depth() - 1)
2015-03-23 15:48:31 -05:00
if contract.CodeAddr != nil {
if p := Precompiled[contract.CodeAddr.Str()]; p != nil {
return evm.RunPrecompiled(p, input, contract)
}
}
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
codehash := contract.CodeHash // codehash is used when doing jump dest caching
if codehash == (common.Hash{}) {
codehash = crypto.Keccak256Hash(contract.Code)
}
var program *Program
if evm.cfg.EnableJit {
// If the JIT is enabled check the status of the JIT program,
2016-03-15 13:08:18 -05:00
// if it doesn't exist compile a new program in a separate
// goroutine or wait for compilation to finish if the JIT is
// forced.
switch GetProgramStatus(codehash) {
case progReady:
return RunProgram(GetProgram(codehash), evm.env, contract, input)
case progUnknown:
if evm.cfg.ForceJit {
// Create and compile program
program = NewProgram(contract.Code)
perr := CompileProgram(program)
if perr == nil {
return RunProgram(program, evm.env, contract, input)
}
glog.V(logger.Info).Infoln("error compiling program", err)
} else {
// create and compile the program. Compilation
2016-03-15 13:08:18 -05:00
// is done in a separate goroutine
program = NewProgram(contract.Code)
go func() {
err := CompileProgram(program)
if err != nil {
glog.V(logger.Info).Infoln("error compiling program", err)
return
}
}()
}
}
}
var (
caller = contract.caller
code = contract.Code
instrCount = 0
op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
statedb = evm.env.Db() // current state
// For optimisation reason we're using uint64 as the program counter.
2016-03-15 13:08:18 -05:00
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
// jump evaluates and checks whether the given jump destination is a valid one
// if valid move the `pc` otherwise return an error.
jump = func(from uint64, to *big.Int) error {
if !contract.jumpdests.has(codehash, code, to) {
nop := contract.GetOp(to.Uint64())
return fmt.Errorf("invalid jump destination (%v) %v", nop, to)
}
pc = to.Uint64()
return nil
}
newMemSize *big.Int
cost *big.Int
)
contract.Input = input
2015-01-19 04:18:34 -06:00
2015-03-27 10:53:05 -05:00
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
2015-03-27 10:09:57 -05:00
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)
2015-03-27 10:09:57 -05:00
}
}()
2015-01-19 04:18:34 -06:00
if glog.V(logger.Debug) {
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)
}()
}
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)
}
}
*/
2015-01-19 04:18:34 -06:00
// Get the memory location of pc
op = contract.GetOp(pc)
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack)
2015-03-27 10:09:57 -05:00
if err != nil {
return nil, err
}
2015-01-19 04:18:34 -06:00
// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
if !contract.UseGas(cost) {
return nil, OutOfGasError
2015-01-19 04:18:34 -06:00
}
// Resize the memory calculated previously
2015-01-19 04:18:34 -06:00
mem.Resize(newMemSize.Uint64())
// Add a log message
if evm.cfg.Debug {
evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), nil)
}
if opPtr := evm.jumpTable[op]; opPtr.valid {
if opPtr.fn != nil {
opPtr.fn(instruction{}, &pc, evm.env, contract, mem, stack)
2015-01-19 04:18:34 -06:00
} else {
switch op {
case PC:
opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, evm.env, contract, mem, stack)
case JUMP:
if err := jump(pc, stack.pop()); err != nil {
return nil, err
}
2015-01-19 04:18:34 -06:00
continue
case JUMPI:
pos, cond := stack.pop(), stack.pop()
2015-03-16 12:42:18 -05:00
if cond.Cmp(common.BigTrue) >= 0 {
if err := jump(pc, pos); err != nil {
return nil, err
}
2015-01-19 04:18:34 -06:00
continue
}
case RETURN:
offset, size := stack.pop(), stack.pop()
ret := mem.GetPtr(offset.Int64(), size.Int64())
2015-01-19 04:18:34 -06:00
return ret, nil
case SUICIDE:
opSuicide(instruction{}, nil, evm.env, contract, mem, stack)
2015-01-19 04:18:34 -06:00
fallthrough
case STOP: // Stop the contract
return nil, nil
2015-03-27 10:09:57 -05:00
}
2015-01-19 04:18:34 -06:00
}
} else {
2015-03-27 10:09:57 -05:00
return nil, fmt.Errorf("Invalid opcode %x", op)
2015-01-19 04:18:34 -06:00
}
pc++
2015-01-19 04:18:34 -06:00
}
}
// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory.
func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
2015-03-02 09:32:02 -06:00
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
)
2015-03-27 10:09:57 -05:00
err := baseCheck(op, stack, gas)
if err != nil {
return nil, nil, err
}
2015-01-19 04:18:34 -06:00
2015-03-09 18:25:27 -05:00
// stack Check, memory resize & gas phase
2015-01-19 04:18:34 -06:00
switch op {
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
n := int(op - SWAP1 + 2)
2015-03-27 10:09:57 -05:00
err := stack.require(n)
if err != nil {
return nil, nil, err
}
2015-03-03 04:11:11 -06:00
gas.Set(GasFastestStep)
2015-01-19 04:18:34 -06:00
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1)
2015-03-27 10:09:57 -05:00
err := stack.require(n)
if err != nil {
return nil, nil, err
}
2015-03-03 04:11:11 -06:00
gas.Set(GasFastestStep)
2015-01-19 04:18:34 -06:00
case LOG0, LOG1, LOG2, LOG3, LOG4:
n := int(op - LOG0)
2015-03-27 10:09:57 -05:00
err := stack.require(n + 2)
if err != nil {
return nil, nil, err
}
2015-01-19 04:18:34 -06:00
2015-03-09 18:25:27 -05:00
mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1]
2015-03-02 09:32:02 -06:00
gas.Add(gas, params.LogGas)
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(n)), params.LogTopicGas))
gas.Add(gas, new(big.Int).Mul(mSize, params.LogDataGas))
2015-01-19 04:18:34 -06:00
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))
2015-01-19 04:18:34 -06:00
case SSTORE:
2015-03-27 10:09:57 -05:00
err := stack.require(2)
if err != nil {
return nil, nil, err
}
2015-01-19 04:18:34 -06:00
2015-03-02 09:32:02 -06:00
var g *big.Int
2015-03-09 18:25:27 -05:00
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
val := statedb.GetState(contract.Address(), common.BigToHash(x))
2015-06-18 05:25:02 -05:00
// 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)
2016-03-15 13:08:18 -05:00
// 3. From a non-zero to a non-zero (CHANGE)
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) {
2015-01-19 04:18:34 -06:00
// 0 => non 0
g = params.SstoreSetGas
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
statedb.AddRefund(params.SstoreRefundGas)
2015-01-19 04:18:34 -06:00
g = params.SstoreClearGas
2015-01-19 04:18:34 -06:00
} else {
// non 0 => non 0 (or 0 => 0)
g = params.SstoreResetGas
2015-01-19 04:18:34 -06:00
}
2015-03-02 09:32:02 -06:00
gas.Set(g)
2015-03-09 05:28:35 -05:00
case SUICIDE:
if !statedb.HasSuicided(contract.Address()) {
statedb.AddRefund(params.SuicideRefundGas)
2015-03-09 05:28:35 -05:00
}
2015-01-19 04:18:34 -06:00
case MLOAD:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), u256(32))
2015-01-19 04:18:34 -06:00
case MSTORE8:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), u256(1))
2015-03-02 10:55:45 -06:00
case MSTORE:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), u256(32))
2015-01-19 04:18:34 -06:00
case RETURN:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2])
2015-01-19 04:18:34 -06:00
case SHA3:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2])
2015-01-19 04:18:34 -06:00
2015-03-09 18:25:27 -05:00
words := toWordSize(stack.data[stack.len()-2])
gas.Add(gas, words.Mul(words, params.Sha3WordGas))
2015-03-02 09:32:02 -06:00
case CALLDATACOPY:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3])
2015-01-19 04:18:34 -06:00
2015-03-09 18:25:27 -05:00
words := toWordSize(stack.data[stack.len()-3])
gas.Add(gas, words.Mul(words, params.CopyGas))
2015-03-02 09:32:02 -06:00
case CODECOPY:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3])
2015-01-19 04:18:34 -06:00
2015-03-09 18:25:27 -05:00
words := toWordSize(stack.data[stack.len()-3])
gas.Add(gas, words.Mul(words, params.CopyGas))
2015-03-02 09:32:02 -06:00
case EXTCODECOPY:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4])
2015-03-02 09:32:02 -06:00
2015-03-09 18:25:27 -05:00
words := toWordSize(stack.data[stack.len()-4])
gas.Add(gas, words.Mul(words, params.CopyGas))
2015-03-03 06:29:52 -06:00
2015-03-02 09:32:02 -06:00
case CREATE:
2015-03-09 18:25:27 -05:00
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3])
2015-01-19 04:18:34 -06:00
case CALL, CALLCODE:
2015-03-09 18:25:27 -05:00
gas.Add(gas, stack.data[stack.len()-1])
2015-03-02 09:32:02 -06:00
if op == CALL {
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
2015-03-02 09:32:02 -06:00
}
2015-03-03 06:29:52 -06:00
}
2015-03-02 09:32:02 -06:00
2015-03-09 18:25:27 -05:00
if len(stack.data[stack.len()-3].Bytes()) > 0 {
gas.Add(gas, params.CallValueTransferGas)
2015-03-02 09:32:02 -06:00
}
2015-01-19 04:18:34 -06:00
2015-03-09 18:25:27 -05:00
x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7])
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5])
2015-01-19 04:18:34 -06:00
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])
2015-03-16 05:27:38 -05:00
newMemSize = common.BigMax(x, y)
2015-01-19 04:18:34 -06:00
}
quadMemGas(mem, newMemSize, gas)
2015-01-19 04:18:34 -06:00
2015-03-27 10:09:57 -05:00
return newMemSize, gas, nil
2014-09-14 18:11:01 -05:00
}
// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go
func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.Gas(len(input))
if contract.UseGas(gas) {
ret = p.Call(input)
2015-01-19 04:18:34 -06:00
return ret, nil
2015-01-19 04:18:34 -06:00
} else {
return nil, OutOfGasError
2015-01-19 04:18:34 -06:00
}
}