|
|
@ -1,84 +1,96 @@ |
|
|
|
package vm |
|
|
|
|
|
|
|
import ( |
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"math" |
|
|
|
|
|
|
|
sm "github.com/tendermint/tendermint/state" |
|
|
|
. "github.com/tendermint/tendermint/common" |
|
|
|
"github.com/tendermint/tendermint/vm/sha3" |
|
|
|
) |
|
|
|
|
|
|
|
type Vm struct { |
|
|
|
VMEnvironment |
|
|
|
} |
|
|
|
var ( |
|
|
|
ErrInsufficientBalance = errors.New("Insufficient balance") |
|
|
|
ErrInvalidJumpDest = errors.New("Invalid jump dest") |
|
|
|
ErrInsufficientGas = errors.New("Insuffient gas") |
|
|
|
ErrMemoryOutOfBounds = errors.New("Memory out of bounds") |
|
|
|
ErrCodeOutOfBounds = errors.New("Code out of bounds") |
|
|
|
ErrInputOutOfBounds = errors.New("Input out of bounds") |
|
|
|
ErrCallStackOverflow = errors.New("Call stack overflow") |
|
|
|
ErrCallStackUnderflow = errors.New("Call stack underflow") |
|
|
|
ErrDataStackOverflow = errors.New("Data stack overflow") |
|
|
|
ErrDataStackUnderflow = errors.New("Data stack underflow") |
|
|
|
ErrInvalidContract = errors.New("Invalid contract") |
|
|
|
) |
|
|
|
|
|
|
|
func NewVM(appState AppState, params VMParams) *Vm { |
|
|
|
vmEnv := NewVMEnvironment(appState, params) |
|
|
|
return &VM{ |
|
|
|
VMEnvironment: vmEnv, |
|
|
|
} |
|
|
|
const ( |
|
|
|
dataStackCapacity = 1024 |
|
|
|
callStackCapacity = 100 // TODO ensure usage.
|
|
|
|
memoryCapacity = 1024 * 1024 // 1 MB
|
|
|
|
) |
|
|
|
|
|
|
|
type VM struct { |
|
|
|
appState AppState |
|
|
|
params VMParams |
|
|
|
|
|
|
|
callDepth int |
|
|
|
} |
|
|
|
|
|
|
|
// feeLimit: the maximum the caller is willing to pay for fees.
|
|
|
|
// gasLimit: the maximum gas that will be run.
|
|
|
|
func (vm *Vm) RunTransaction(caller, target *Account, feeLimit, gasLimit, value uint64, input []byte) (output []byte, err error) { |
|
|
|
type VMParams struct { |
|
|
|
BlockHeight uint64 |
|
|
|
BlockHash Word |
|
|
|
BlockTime int64 |
|
|
|
GasLimit uint64 |
|
|
|
GasPrice uint64 |
|
|
|
CallStackLimit uint64 |
|
|
|
Origin Word |
|
|
|
} |
|
|
|
|
|
|
|
if len(target.Code) == 0 { |
|
|
|
panic("RunTransaction() requires target with code") |
|
|
|
func NewVM(appState AppState, params VMParams) *VM { |
|
|
|
return &VM{ |
|
|
|
appState: appState, |
|
|
|
params: params, |
|
|
|
callDepth: 0, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Check the gasLimit vs feeLimit
|
|
|
|
// TODO
|
|
|
|
/* |
|
|
|
// When running a transaction, the caller the pays for the fees.
|
|
|
|
|
|
|
|
// Check caller's account balance vs feeLimit and value
|
|
|
|
if caller.Balance < (feeLimit + value) { |
|
|
|
return nil, ErrInsufficientAccountBalance |
|
|
|
return nil, ErrInsufficientBalance |
|
|
|
} |
|
|
|
|
|
|
|
// Deduct balance from caller.
|
|
|
|
caller.Balance -= (feeLimit + value) |
|
|
|
|
|
|
|
vm.SetupTransaction(caller, target, gasLimit, value, input) |
|
|
|
*/ |
|
|
|
|
|
|
|
fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.CallStackDepth(), caller.Address[:4], target.Address, len(target.Code), gasLimit, input) |
|
|
|
// gas: the maximum gas that will be run.
|
|
|
|
// When the function returns, *gas will be the amount of remaining gas.
|
|
|
|
func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { |
|
|
|
|
|
|
|
/* |
|
|
|
if p := Precompiled[string(me.Address())]; p != nil { |
|
|
|
return vm.RunPrecompiled(p, callData, context) |
|
|
|
} |
|
|
|
*/ |
|
|
|
if len(callee.Code) == 0 { |
|
|
|
panic("Call() requires callee with code") |
|
|
|
} |
|
|
|
|
|
|
|
//-----------------------------------
|
|
|
|
fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) |
|
|
|
|
|
|
|
// By the time we're here, the related VMCall context is already appended onto VMEnvironment.callStack
|
|
|
|
var ( |
|
|
|
code = target.Code |
|
|
|
pc uint64 = 0 |
|
|
|
gas uint64 = call.gasLimit |
|
|
|
err error = nil |
|
|
|
stack = NewStack(defaultDataStackCapacity, &gas, &err) |
|
|
|
memory = NewMemory(&gas, &err) |
|
|
|
|
|
|
|
// volatile, convenience
|
|
|
|
ok = false |
|
|
|
|
|
|
|
// TODO review this code.
|
|
|
|
jump = func(from, to uint64) error { |
|
|
|
dest := CodeGetOp(code, to) |
|
|
|
if dest != JUMPDEST { |
|
|
|
return ErrInvalidJumpDest |
|
|
|
} |
|
|
|
pc = to |
|
|
|
fmt.Printf(" ~> %v\n", to) |
|
|
|
return nil |
|
|
|
} |
|
|
|
stack = NewStack(dataStackCapacity, gas, &err) |
|
|
|
memory = make([]byte, memoryCapacity) |
|
|
|
ok = false // convenience
|
|
|
|
) |
|
|
|
|
|
|
|
for { |
|
|
|
var op = CodeGetOp(code, pc) |
|
|
|
fmt.Printf("(pc) %-3d -o- %-14s (m) %-4d (s) %-4d ", pc, op.String(), mem.Len(), stack.Len()) |
|
|
|
var op = codeGetOp(code, pc) |
|
|
|
fmt.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) |
|
|
|
|
|
|
|
switch op { |
|
|
|
|
|
|
|
case STOP: // 0x00
|
|
|
|
return nil, nil |
|
|
|
|
|
|
|
case ADD: // 0x01
|
|
|
|
x, y := stack.Pop64(), stack.Pop64() |
|
|
|
stack.Push64(x + y) |
|
|
@ -241,345 +253,418 @@ func (vm *Vm) RunTransaction(caller, target *Account, feeLimit, gasLimit, value |
|
|
|
|
|
|
|
case BYTE: // 0x1A
|
|
|
|
idx, val := stack.Pop64(), stack.Pop() |
|
|
|
res := 0 |
|
|
|
res := byte(0) |
|
|
|
if idx < 32 { |
|
|
|
res = Uint642Bytes(val[idx/8])[idx%8] |
|
|
|
res = val[idx] |
|
|
|
} |
|
|
|
stack.Push64(res) |
|
|
|
fmt.Printf(" => 0x%X", res) |
|
|
|
stack.Push64(uint64(res)) |
|
|
|
fmt.Printf(" => 0x%X\n", res) |
|
|
|
|
|
|
|
case SHA3: // 0x20
|
|
|
|
if gas, ok = useGas(gas, GasSha3); !ok { |
|
|
|
return ErrInsufficientGas |
|
|
|
if ok = useGas(gas, GasSha3); !ok { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} |
|
|
|
offset, size := stack.Pop64(), stack.Pop64() |
|
|
|
data := sha3.Sha3(memory.Get(offset, size)) |
|
|
|
data, ok := subslice(memory, offset, size) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
data = sha3.Sha3(data) |
|
|
|
stack.PushBytes(data) |
|
|
|
fmt.Printf(" => (%v) %X", size, data) |
|
|
|
fmt.Printf(" => (%v) %X\n", size, data) |
|
|
|
|
|
|
|
case ADDRESS: // 0x30
|
|
|
|
stack.PushBytes(RightPadBytes(context.Address(), 32)) |
|
|
|
fmt.Printf(" => %X", RightPadBytes(context.Address(), 32)) |
|
|
|
stack.Push(callee.Address) |
|
|
|
fmt.Printf(" => %X\n", callee.Address) |
|
|
|
|
|
|
|
case BALANCE: // 0x31
|
|
|
|
addr := stack.PopBytes() |
|
|
|
if gas, ok = useGas(gas, GasGetAccount); !ok { |
|
|
|
return ErrInsufficientGas |
|
|
|
addr := stack.Pop() |
|
|
|
if ok = useGas(gas, GasGetAccount); !ok { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} |
|
|
|
account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
|
|
|
|
if err = firstErr(err, err_); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
account := vm.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
|
|
|
|
balance := account.Balance |
|
|
|
stack.Push64(balance) |
|
|
|
fmt.Printf(" => %v (%X)", balance, addr) |
|
|
|
fmt.Printf(" => %v (%X)\n", balance, addr) |
|
|
|
|
|
|
|
case ORIGIN: // 0x32
|
|
|
|
origin := vm.Origin() |
|
|
|
stack.PushBytes(origin) |
|
|
|
fmt.Printf(" => %X", origin) |
|
|
|
origin := vm.params.Origin |
|
|
|
stack.Push(origin) |
|
|
|
fmt.Printf(" => %X\n", origin) |
|
|
|
|
|
|
|
case CALLER: // 0x33
|
|
|
|
caller := vm.lastCall.caller |
|
|
|
stack.PushBytes(caller.Address) |
|
|
|
fmt.Printf(" => %X", caller.Address) |
|
|
|
stack.Push(caller.Address) |
|
|
|
fmt.Printf(" => %X\n", caller.Address) |
|
|
|
|
|
|
|
case CALLVALUE: // 0x34
|
|
|
|
stack.Push64(value) |
|
|
|
fmt.Printf(" => %v", value) |
|
|
|
fmt.Printf(" => %v\n", value) |
|
|
|
|
|
|
|
case CALLDATALOAD: // 0x35
|
|
|
|
offset := stack.Pop64() |
|
|
|
data, _ := subslice(input, offset, 32) |
|
|
|
stack.PushBytes(RightPadBytes(data), 32) |
|
|
|
fmt.Printf(" => 0x%X", data) |
|
|
|
data, ok := subslice(input, offset, 32) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrInputOutOfBounds) |
|
|
|
} |
|
|
|
stack.Push(RightPadWord(data)) |
|
|
|
fmt.Printf(" => 0x%X\n", data) |
|
|
|
|
|
|
|
case CALLDATASIZE: // 0x36
|
|
|
|
stack.Push64(uint64(len(callData))) |
|
|
|
fmt.Printf(" => %d", len(callData)) |
|
|
|
stack.Push64(uint64(len(input))) |
|
|
|
fmt.Printf(" => %d\n", len(input)) |
|
|
|
|
|
|
|
case CALLDATACOPY: // 0x37
|
|
|
|
memOff := stack.Pop64() |
|
|
|
inputOff := stack.Pop64() |
|
|
|
length := stack.Pop64() |
|
|
|
data, ok := subslice(input, inputOff, length) |
|
|
|
if ok { |
|
|
|
memory.Set(memOff, length, data) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrInputOutOfBounds) |
|
|
|
} |
|
|
|
fmt.Printf(" => [%v, %v, %v] %X", memOff, inputOff, length, data) |
|
|
|
dest, ok := subslice(memory, memOff, length) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
copy(dest, data) |
|
|
|
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) |
|
|
|
|
|
|
|
case CODESIZE: // 0x38
|
|
|
|
l := uint64(len(code)) |
|
|
|
stack.Push64(l) |
|
|
|
fmt.Printf(" => %d", l) |
|
|
|
fmt.Printf(" => %d\n", l) |
|
|
|
|
|
|
|
case CODECOPY: // 0x39
|
|
|
|
memOff := stack.Pop64() |
|
|
|
codeOff := stack.Pop64() |
|
|
|
length := stack.Pop64() |
|
|
|
data, ok := subslice(code, codeOff, length) |
|
|
|
if ok { |
|
|
|
memory.Set(memOff, length, data) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrCodeOutOfBounds) |
|
|
|
} |
|
|
|
dest, ok := subslice(memory, memOff, length) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
fmt.Printf(" => [%v, %v, %v] %X", memOff, codeOff, length, data) |
|
|
|
copy(dest, data) |
|
|
|
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) |
|
|
|
|
|
|
|
case GASPRICE: // 0x3A
|
|
|
|
stack.Push64(vm.params.GasPrice) |
|
|
|
fmt.Printf(" => %X", vm.params.GasPrice) |
|
|
|
fmt.Printf(" => %X\n", vm.params.GasPrice) |
|
|
|
|
|
|
|
case EXTCODESIZE: // 0x3B
|
|
|
|
addr := stack.PopBytes()[:20] |
|
|
|
account := vm.GetAccount(addr) |
|
|
|
addr := stack.Pop() |
|
|
|
if ok = useGas(gas, GasGetAccount); !ok { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} |
|
|
|
account, err_ := vm.appState.GetAccount(addr) |
|
|
|
if err = firstErr(err, err_); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
code := account.Code |
|
|
|
l := uint64(len(code)) |
|
|
|
stack.Push64(l) |
|
|
|
fmt.Printf(" => %d", l) |
|
|
|
fmt.Printf(" => %d\n", l) |
|
|
|
|
|
|
|
case EXTCODECOPY: // 0x3C
|
|
|
|
addr := stack.PopBytes()[:20] |
|
|
|
account := vm.GetAccount(addr) |
|
|
|
addr := stack.Pop() |
|
|
|
if ok = useGas(gas, GasGetAccount); !ok { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} |
|
|
|
account, err_ := vm.appState.GetAccount(addr) |
|
|
|
if err = firstErr(err, err_); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
code := account.Code |
|
|
|
memOff := stack.Pop64() |
|
|
|
codeOff := stack.Pop64() |
|
|
|
length := stack.Pop64() |
|
|
|
data, ok := subslice(code, codeOff, length) |
|
|
|
if ok { |
|
|
|
memory.Set(memOff, length, data) |
|
|
|
if !ok { |
|
|
|
return nil, ErrCodeOutOfBounds |
|
|
|
} |
|
|
|
fmt.Printf(" => [%v, %v, %v] %X", memOff, codeOff, length, data) |
|
|
|
dest, ok := subslice(memory, memOff, length) |
|
|
|
if !ok { |
|
|
|
return nil, ErrMemoryOutOfBounds |
|
|
|
} |
|
|
|
copy(dest, data) |
|
|
|
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) |
|
|
|
|
|
|
|
case BLOCKHASH: // 0x40
|
|
|
|
/* |
|
|
|
num := stack.pop() |
|
|
|
n := new(big.Int).Sub(vm.env.BlockHeight(), Big257) |
|
|
|
if num.Cmp(n) > 0 && num.Cmp(vm.env.BlockHeight()) < 0 { |
|
|
|
stack.push(Bytes2Big(vm.env.GetBlockHash(num.Uint64()))) |
|
|
|
} else { |
|
|
|
stack.push(Big0) |
|
|
|
} |
|
|
|
*/ |
|
|
|
stack.Push([4]Word{0, 0, 0, 0}) |
|
|
|
fmt.Printf(" => 0x%X (NOT SUPPORTED)", stack.Peek().Bytes()) |
|
|
|
stack.Push(Zero) |
|
|
|
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) |
|
|
|
|
|
|
|
case COINBASE: // 0x41
|
|
|
|
stack.Push([4]Word{0, 0, 0, 0}) |
|
|
|
fmt.Printf(" => 0x%X (NOT SUPPORTED)", stack.Peek().Bytes()) |
|
|
|
stack.Push(Zero) |
|
|
|
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) |
|
|
|
|
|
|
|
case TIMESTAMP: // 0x42
|
|
|
|
time := vm.params.BlockTime |
|
|
|
stack.Push64(uint64(time)) |
|
|
|
fmt.Printf(" => 0x%X", time) |
|
|
|
fmt.Printf(" => 0x%X\n", time) |
|
|
|
|
|
|
|
case BLOCKHEIGHT: // 0x43
|
|
|
|
number := uint64(vm.params.BlockHeight) |
|
|
|
stack.Push64(number) |
|
|
|
fmt.Printf(" => 0x%X", number) |
|
|
|
fmt.Printf(" => 0x%X\n", number) |
|
|
|
|
|
|
|
case GASLIMIT: // 0x45
|
|
|
|
stack.Push64(vm.params.GasLimit) |
|
|
|
fmt.Printf(" => %v", vm.params.GasLimit) |
|
|
|
fmt.Printf(" => %v\n", vm.params.GasLimit) |
|
|
|
|
|
|
|
case POP: // 0x50
|
|
|
|
stack.Pop() |
|
|
|
fmt.Printf(" => %v", vm.params.GasLimit) |
|
|
|
fmt.Printf(" => %v\n", vm.params.GasLimit) |
|
|
|
|
|
|
|
case MLOAD: // 0x51
|
|
|
|
offset := stack.Pop64() |
|
|
|
data, _ := subslice(input, offset, 32) |
|
|
|
stack.PushBytes(RightPadBytes(data), 32) |
|
|
|
fmt.Printf(" => 0x%X", data) |
|
|
|
|
|
|
|
offset := stack.pop() |
|
|
|
val := Bytes2Big(mem.Get(offset.Int64(), 32)) |
|
|
|
stack.push(val) |
|
|
|
|
|
|
|
fmt.Printf(" => 0x%X", val.Bytes()) |
|
|
|
case MSTORE: // Store the value at stack top-1 in to memory at location stack top
|
|
|
|
// pop value of the stack
|
|
|
|
mStart, val := stack.pop(), stack.pop() |
|
|
|
mem.Set(mStart.Uint64(), 32, Big2Bytes(val, 256)) |
|
|
|
|
|
|
|
fmt.Printf(" => 0x%X", val) |
|
|
|
case MSTORE8: |
|
|
|
off, val := stack.pop(), stack.pop() |
|
|
|
|
|
|
|
mem.store[off.Int64()] = byte(val.Int64() & 0xff) |
|
|
|
|
|
|
|
fmt.Printf(" => [%v] 0x%X", off, val) |
|
|
|
case SLOAD: |
|
|
|
loc := stack.pop() |
|
|
|
val := Bytes2Big(state.GetState(context.Address(), loc.Bytes())) |
|
|
|
stack.push(val) |
|
|
|
|
|
|
|
fmt.Printf(" {0x%X : 0x%X}", loc.Bytes(), val.Bytes()) |
|
|
|
case SSTORE: |
|
|
|
loc, val := stack.pop(), stack.pop() |
|
|
|
state.SetState(context.Address(), loc.Bytes(), val) |
|
|
|
data, ok := subslice(memory, offset, 32) |
|
|
|
if !ok { |
|
|
|
return nil, ErrMemoryOutOfBounds |
|
|
|
} |
|
|
|
stack.Push(RightPadWord(data)) |
|
|
|
fmt.Printf(" => 0x%X\n", data) |
|
|
|
|
|
|
|
case MSTORE: // 0x52
|
|
|
|
offset, data := stack.Pop64(), stack.Pop() |
|
|
|
dest, ok := subslice(memory, offset, 32) |
|
|
|
if !ok { |
|
|
|
return nil, ErrMemoryOutOfBounds |
|
|
|
} |
|
|
|
copy(dest, data[:]) |
|
|
|
fmt.Printf(" => 0x%X\n", data) |
|
|
|
|
|
|
|
fmt.Printf(" {0x%X : 0x%X}", loc.Bytes(), val.Bytes()) |
|
|
|
case JUMP: |
|
|
|
jump(pc, stack.pop()) |
|
|
|
case MSTORE8: // 0x53
|
|
|
|
offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) |
|
|
|
if len(memory) <= int(offset) { |
|
|
|
return nil, ErrMemoryOutOfBounds |
|
|
|
} |
|
|
|
memory[offset] = val |
|
|
|
fmt.Printf(" => [%v] 0x%X\n", offset, val) |
|
|
|
|
|
|
|
case SLOAD: // 0x54
|
|
|
|
loc := stack.Pop() |
|
|
|
data, _ := vm.appState.GetStorage(callee.Address, loc) |
|
|
|
stack.Push(data) |
|
|
|
fmt.Printf(" {0x%X : 0x%X}\n", loc, data) |
|
|
|
|
|
|
|
case SSTORE: // 0x55
|
|
|
|
loc, data := stack.Pop(), stack.Pop() |
|
|
|
updated, err_ := vm.appState.SetStorage(callee.Address, loc, data) |
|
|
|
if err = firstErr(err, err_); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
if updated { |
|
|
|
useGas(gas, GasStorageUpdate) |
|
|
|
} else { |
|
|
|
useGas(gas, GasStorageCreate) |
|
|
|
} |
|
|
|
fmt.Printf(" {0x%X : 0x%X}\n", loc, data) |
|
|
|
|
|
|
|
case JUMP: // 0x56
|
|
|
|
err = jump(code, stack.Pop64()) |
|
|
|
continue |
|
|
|
case JUMPI: |
|
|
|
pos, cond := stack.pop(), stack.pop() |
|
|
|
|
|
|
|
if cond.Cmp(BigTrue) >= 0 { |
|
|
|
jump(pc, pos) |
|
|
|
|
|
|
|
case JUMPI: // 0x57
|
|
|
|
pos, cond := stack.Pop64(), stack.Pop64() |
|
|
|
if cond >= 1 { |
|
|
|
err = jump(code, pos) |
|
|
|
continue |
|
|
|
} |
|
|
|
fmt.Printf(" ~> false\n") |
|
|
|
|
|
|
|
case PC: // 0x58
|
|
|
|
stack.Push64(pc) |
|
|
|
|
|
|
|
fmt.Printf(" ~> false") |
|
|
|
case MSIZE: // 0x59
|
|
|
|
stack.Push64(uint64(len(memory))) |
|
|
|
|
|
|
|
case JUMPDEST: |
|
|
|
case PC: |
|
|
|
stack.push(Big(int64(pc))) |
|
|
|
case MSIZE: |
|
|
|
stack.push(Big(int64(mem.Len()))) |
|
|
|
case GAS: |
|
|
|
stack.push(context.Gas) |
|
|
|
case GAS: // 0x5A
|
|
|
|
stack.Push64(*gas) |
|
|
|
fmt.Printf(" => %X\n", *gas) |
|
|
|
|
|
|
|
fmt.Printf(" => %X", context.Gas) |
|
|
|
case JUMPDEST: // 0x5B
|
|
|
|
// Do nothing
|
|
|
|
|
|
|
|
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 - PUSH1 + 1) |
|
|
|
byts := context.GetRangeValue(pc+1, a) |
|
|
|
// push value to stack
|
|
|
|
stack.push(Bytes2Big(byts)) |
|
|
|
codeSegment, ok := subslice(code, pc+1, a) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrCodeOutOfBounds) |
|
|
|
} |
|
|
|
res := RightPadWord(codeSegment) |
|
|
|
stack.Push(res) |
|
|
|
pc += a |
|
|
|
|
|
|
|
fmt.Printf(" => 0x%X", byts) |
|
|
|
fmt.Printf(" => 0x%X\n", res) |
|
|
|
|
|
|
|
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: |
|
|
|
n := int(op - DUP1 + 1) |
|
|
|
stack.dup(n) |
|
|
|
stack.Dup(n) |
|
|
|
fmt.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) |
|
|
|
|
|
|
|
fmt.Printf(" => [%d] 0x%X", n, stack.peek().Bytes()) |
|
|
|
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: |
|
|
|
n := int(op - SWAP1 + 2) |
|
|
|
stack.swap(n) |
|
|
|
stack.Swap(n) |
|
|
|
fmt.Printf(" => [%d]\n", n) |
|
|
|
|
|
|
|
fmt.Printf(" => [%d]", n) |
|
|
|
case LOG0, LOG1, LOG2, LOG3, LOG4: |
|
|
|
n := int(op - LOG0) |
|
|
|
topics := make([][]byte, n) |
|
|
|
mStart, mSize := stack.pop(), stack.pop() |
|
|
|
topics := make([]Word, n) |
|
|
|
offset, size := stack.Pop64(), stack.Pop64() |
|
|
|
for i := 0; i < n; i++ { |
|
|
|
topics[i] = LeftPadBytes(stack.pop().Bytes(), 32) |
|
|
|
topics[i] = stack.Pop() |
|
|
|
} |
|
|
|
data, ok := subslice(memory, offset, size) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
log := &Log{ |
|
|
|
callee.Address, |
|
|
|
topics, |
|
|
|
data, |
|
|
|
vm.params.BlockHeight, |
|
|
|
} |
|
|
|
vm.appState.AddLog(log) |
|
|
|
fmt.Printf(" => %v\n", log) |
|
|
|
|
|
|
|
data := mem.Get(mStart.Int64(), mSize.Int64()) |
|
|
|
log := &Log{context.Address(), topics, data, vm.env.BlockHeight().Uint64()} |
|
|
|
vm.env.AddLog(log) |
|
|
|
|
|
|
|
fmt.Printf(" => %v", log) |
|
|
|
case CREATE: // 0xF0
|
|
|
|
value := stack.Pop64() |
|
|
|
offset, size := stack.Pop64(), stack.Pop64() |
|
|
|
input, ok := subslice(memory, offset, size) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
|
|
|
|
// 0x60 range
|
|
|
|
case CREATE: |
|
|
|
// Check balance
|
|
|
|
if caller.Balance < value { |
|
|
|
return nil, firstErr(err, ErrInsufficientBalance) |
|
|
|
} else { |
|
|
|
caller.Balance -= value |
|
|
|
} |
|
|
|
|
|
|
|
var ( |
|
|
|
value = stack.pop() |
|
|
|
offset, size = stack.pop(), stack.pop() |
|
|
|
input = mem.Get(offset.Int64(), size.Int64()) |
|
|
|
gas = new(big.Int).Set(context.Gas) |
|
|
|
addr []byte |
|
|
|
) |
|
|
|
vm.Endl() |
|
|
|
// Create a new address
|
|
|
|
nonce := caller.Nonce |
|
|
|
addr := createAddress(caller.Address, nonce) |
|
|
|
caller.Nonce += 1 |
|
|
|
|
|
|
|
context.UseGas(context.Gas) |
|
|
|
ret, suberr, ref := Create(vm, context, nil, input, gas, price, value) |
|
|
|
if suberr != nil { |
|
|
|
stack.push(BigFalse) |
|
|
|
// TODO charge for gas to create account _ the code length * GasCreateByte
|
|
|
|
|
|
|
|
fmt.Printf(" (*) 0x0 %v", suberr) |
|
|
|
newAccount, err := vm.appState.CreateAccount(addr, value) |
|
|
|
if err != nil { |
|
|
|
stack.Push64(0) |
|
|
|
fmt.Printf(" (*) 0x0 %v\n", err) |
|
|
|
} else { |
|
|
|
|
|
|
|
// gas < len(ret) * CreateDataGas == NO_CODE
|
|
|
|
dataGas := Big(int64(len(ret))) |
|
|
|
dataGas.Mul(dataGas, GasCreateByte) |
|
|
|
if context.UseGas(dataGas) { |
|
|
|
ref.SetCode(ret) |
|
|
|
// Run the input to get the contract code.
|
|
|
|
// The code as well as the input to the code are the same.
|
|
|
|
ret, err_ := vm.Call(callee, newAccount, input, input, value, gas) |
|
|
|
if err_ != nil { |
|
|
|
caller.Balance += value // Return the balance
|
|
|
|
stack.Push64(0) |
|
|
|
} else { |
|
|
|
newAccount.Code = ret // Set the code
|
|
|
|
stack.Push(newAccount.Address) |
|
|
|
} |
|
|
|
addr = ref.Address() |
|
|
|
|
|
|
|
stack.push(Bytes2Big(addr)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
case CALL, CALLCODE: |
|
|
|
gas := stack.pop() |
|
|
|
// pop gas and value of the stack.
|
|
|
|
addr, value := stack.pop(), stack.pop() |
|
|
|
value = U256(value) |
|
|
|
// pop input size and offset
|
|
|
|
inOffset, inSize := stack.pop(), stack.pop() |
|
|
|
// pop return size and offset
|
|
|
|
retOffset, retSize := stack.pop(), stack.pop() |
|
|
|
|
|
|
|
address := addr.Bytes() |
|
|
|
fmt.Printf(" => %X", address).Endl() |
|
|
|
case CALL, CALLCODE: // 0xF1, 0xF2
|
|
|
|
gasLimit := stack.Pop64() |
|
|
|
addr, value := stack.Pop(), stack.Pop64() |
|
|
|
inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs
|
|
|
|
retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs
|
|
|
|
fmt.Printf(" => %X\n", addr) |
|
|
|
|
|
|
|
// Get the arguments from the memory
|
|
|
|
args := mem.Get(inOffset.Int64(), inSize.Int64()) |
|
|
|
|
|
|
|
if len(value.Bytes()) > 0 { |
|
|
|
gas.Add(gas, GasStipend) |
|
|
|
args, ok := subslice(memory, inOffset, inSize) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
|
|
|
|
var ( |
|
|
|
ret []byte |
|
|
|
err error |
|
|
|
) |
|
|
|
if op == CALLCODE { |
|
|
|
ret, err = CallCode(env, context, address, args, gas, price, value) |
|
|
|
// Ensure that gasLimit is reasonable
|
|
|
|
if *gas < gasLimit { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} else { |
|
|
|
ret, err = Call(env, context, address, args, gas, price, value) |
|
|
|
*gas -= gasLimit |
|
|
|
// NOTE: we will return any used gas later.
|
|
|
|
} |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
stack.push(BigFalse) |
|
|
|
// Begin execution
|
|
|
|
var ret []byte |
|
|
|
var err error |
|
|
|
|
|
|
|
fmt.Printf("%v").Endl() |
|
|
|
// If addr is in nativeContracts
|
|
|
|
if nativeContract := nativeContracts[addr]; nativeContract != nil { |
|
|
|
ret, err = nativeContract(args, &gasLimit) |
|
|
|
} else { |
|
|
|
stack.push(BigTrue) |
|
|
|
|
|
|
|
mem.Set(retOffset.Uint64(), retSize.Uint64(), ret) |
|
|
|
if ok = useGas(gas, GasGetAccount); !ok { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} |
|
|
|
account, err_ := vm.appState.GetAccount(addr) |
|
|
|
if err = firstErr(err, err_); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
if op == CALLCODE { |
|
|
|
ret, err = vm.Call(callee, callee, account.Code, args, value, gas) |
|
|
|
} else { |
|
|
|
ret, err = vm.Call(callee, account, account.Code, args, value, gas) |
|
|
|
} |
|
|
|
} |
|
|
|
fmt.Printf("resume %X (%v)", context.Address(), context.Gas) |
|
|
|
|
|
|
|
case RETURN: |
|
|
|
offset, size := stack.pop(), stack.pop() |
|
|
|
ret := mem.Get(offset.Int64(), size.Int64()) |
|
|
|
fmt.Printf(" => [%v, %v] (%d) 0x%X", offset, size, len(ret), ret).Endl() |
|
|
|
return context.Return(ret), nil |
|
|
|
|
|
|
|
case SUICIDE: |
|
|
|
receiver := state.GetOrNewStateObject(stack.pop().Bytes()) |
|
|
|
balance := state.GetBalance(context.Address()) |
|
|
|
// Push result
|
|
|
|
if err != nil { |
|
|
|
stack.Push(Zero) |
|
|
|
} else { |
|
|
|
stack.Push(One) |
|
|
|
dest, ok := subslice(memory, retOffset, retSize) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
copy(dest, ret) |
|
|
|
} |
|
|
|
|
|
|
|
fmt.Printf(" => (%X) %v", receiver.Address()[:4], balance) |
|
|
|
// Handle remaining gas.
|
|
|
|
*gas += gasLimit |
|
|
|
|
|
|
|
receiver.AddBalance(balance) |
|
|
|
fmt.Printf("resume %X (%v)\n", callee.Address, gas) |
|
|
|
|
|
|
|
state.Delete(context.Address()) |
|
|
|
case RETURN: // 0xF3
|
|
|
|
offset, size := stack.Pop64(), stack.Pop64() |
|
|
|
ret, ok := subslice(memory, offset, size) |
|
|
|
if !ok { |
|
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds) |
|
|
|
} |
|
|
|
fmt.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) |
|
|
|
return ret, nil |
|
|
|
|
|
|
|
case SUICIDE: // 0xFF
|
|
|
|
addr := stack.Pop() |
|
|
|
if ok = useGas(gas, GasGetAccount); !ok { |
|
|
|
return nil, firstErr(err, ErrInsufficientGas) |
|
|
|
} |
|
|
|
receiver, err_ := vm.appState.GetAccount(addr) |
|
|
|
if err = firstErr(err, err_); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
balance := callee.Balance |
|
|
|
receiver.Balance += balance |
|
|
|
vm.appState.UpdateAccount(receiver) |
|
|
|
vm.appState.DeleteAccount(callee) |
|
|
|
fmt.Printf(" => (%X) %v\n", addr[:4], balance) |
|
|
|
fallthrough |
|
|
|
case STOP: // Stop the context
|
|
|
|
vm.Endl() |
|
|
|
|
|
|
|
return context.Return(nil), nil |
|
|
|
default: |
|
|
|
fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op).Endl() |
|
|
|
|
|
|
|
fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) |
|
|
|
panic(fmt.Errorf("Invalid opcode %X", op)) |
|
|
|
} |
|
|
|
|
|
|
|
pc++ |
|
|
|
|
|
|
|
vm.Endl() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
func (vm *Vm) RunPrecompiled(p *PrecompiledAccount, callData []byte, context *Context) (ret []byte, err error) { |
|
|
|
func (vm *VM) CallPrecompiled(p *PrecompiledAccount, callData []byte, context *Context) (ret []byte, err error) { |
|
|
|
gas := p.Gas(len(callData)) |
|
|
|
if context.UseGas(gas) { |
|
|
|
ret = p.Call(callData) |
|
|
@ -608,10 +693,44 @@ func subslice(data []byte, offset, length uint64) ([]byte, bool) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func useGas(gasLeft, gasToUse uint64) (uint64, bool) { |
|
|
|
if gasLeft > gasToUse { |
|
|
|
return gasLeft - gasToUse, true |
|
|
|
func codeGetOp(code []byte, n uint64) OpCode { |
|
|
|
if uint64(len(code)) <= n { |
|
|
|
return OpCode(0) // stop
|
|
|
|
} else { |
|
|
|
return gasLeft, false |
|
|
|
return OpCode(code[n]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func jump(code []byte, to uint64) (err error) { |
|
|
|
dest := codeGetOp(code, to) |
|
|
|
if dest != JUMPDEST { |
|
|
|
return ErrInvalidJumpDest |
|
|
|
} |
|
|
|
fmt.Printf(" ~> %v\n", to) |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
func firstErr(errA, errB error) error { |
|
|
|
if errA != nil { |
|
|
|
return errA |
|
|
|
} else { |
|
|
|
return errB |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func useGas(gas *uint64, gasToUse uint64) bool { |
|
|
|
if *gas > gasToUse { |
|
|
|
*gas -= gasToUse |
|
|
|
return true |
|
|
|
} else { |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Creates a 20 byte address from the creatorAddr and nonce.
|
|
|
|
func createAddress(creatorAddr Word, nonce uint64) Word { |
|
|
|
temp := make([]byte, 32+8) |
|
|
|
copy(temp, creatorAddr[:]) |
|
|
|
PutUint64(temp[32:], nonce) |
|
|
|
return RightPadWord(sha3.Sha3(temp)[:20]) |
|
|
|
} |