package vm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
|
|
. "github.com/tendermint/tendermint/common"
|
|
"github.com/tendermint/tendermint/vm/sha3"
|
|
)
|
|
|
|
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")
|
|
)
|
|
|
|
const (
|
|
dataStackCapacity = 1024
|
|
callStackCapacity = 100 // TODO ensure usage.
|
|
memoryCapacity = 1024 * 1024 // 1 MB
|
|
)
|
|
|
|
type VM struct {
|
|
appState AppState
|
|
params VMParams
|
|
|
|
callDepth int
|
|
}
|
|
|
|
type VMParams struct {
|
|
BlockHeight uint64
|
|
BlockHash Word
|
|
BlockTime int64
|
|
GasLimit uint64
|
|
CallStackLimit uint64
|
|
Origin Word
|
|
}
|
|
|
|
func NewVM(appState AppState, params VMParams) *VM {
|
|
return &VM{
|
|
appState: appState,
|
|
params: params,
|
|
callDepth: 0,
|
|
}
|
|
}
|
|
|
|
/*
|
|
// 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, ErrInsufficientBalance
|
|
}
|
|
caller.Balance -= (feeLimit + value)
|
|
|
|
*/
|
|
|
|
// 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 len(code) == 0 {
|
|
panic("Call() requires 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)
|
|
|
|
var (
|
|
pc uint64 = 0
|
|
stack = NewStack(dataStackCapacity, gas, &err)
|
|
memory = make([]byte, memoryCapacity)
|
|
ok = false // convenience
|
|
)
|
|
|
|
for {
|
|
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)
|
|
fmt.Printf(" %v + %v = %v\n", x, y, x+y)
|
|
|
|
case MUL: // 0x02
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
stack.Push64(x * y)
|
|
fmt.Printf(" %v * %v = %v\n", x, y, x*y)
|
|
|
|
case SUB: // 0x03
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
stack.Push64(x - y)
|
|
fmt.Printf(" %v - %v = %v\n", x, y, x-y)
|
|
|
|
case DIV: // 0x04
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
if y == 0 { // TODO
|
|
stack.Push(Zero)
|
|
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
|
|
} else {
|
|
stack.Push64(x / y)
|
|
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
|
|
}
|
|
|
|
case SDIV: // 0x05
|
|
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
|
if y == 0 { // TODO
|
|
stack.Push(Zero)
|
|
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
|
|
} else {
|
|
stack.Push64(uint64(x / y))
|
|
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
|
|
}
|
|
|
|
case MOD: // 0x06
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
if y == 0 { // TODO
|
|
stack.Push(Zero)
|
|
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
|
|
} else {
|
|
stack.Push64(x % y)
|
|
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
|
|
}
|
|
|
|
case SMOD: // 0x07
|
|
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
|
if y == 0 { // TODO
|
|
stack.Push(Zero)
|
|
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
|
|
} else {
|
|
stack.Push64(uint64(x % y))
|
|
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
|
|
}
|
|
|
|
case ADDMOD: // 0x08
|
|
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
|
|
if z == 0 { // TODO
|
|
stack.Push(Zero)
|
|
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
|
|
} else {
|
|
stack.Push64(x % y)
|
|
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
|
|
}
|
|
|
|
case MULMOD: // 0x09
|
|
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
|
|
if z == 0 { // TODO
|
|
stack.Push(Zero)
|
|
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
|
|
} else {
|
|
stack.Push64(x % y)
|
|
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
|
|
}
|
|
|
|
case EXP: // 0x0A
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
stack.Push64(ExpUint64(x, y))
|
|
fmt.Printf(" %v ** %v = %v\n", x, y, uint64(math.Pow(float64(x), float64(y))))
|
|
|
|
case SIGNEXTEND: // 0x0B
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
res := (y << uint(x)) >> x
|
|
stack.Push64(res)
|
|
fmt.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res)
|
|
|
|
case LT: // 0x10
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
if x < y {
|
|
stack.Push64(1)
|
|
} else {
|
|
stack.Push(Zero)
|
|
}
|
|
fmt.Printf(" %v < %v = %v\n", x, y, x < y)
|
|
|
|
case GT: // 0x11
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
if x > y {
|
|
stack.Push64(1)
|
|
} else {
|
|
stack.Push(Zero)
|
|
}
|
|
fmt.Printf(" %v > %v = %v\n", x, y, x > y)
|
|
|
|
case SLT: // 0x12
|
|
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
|
if x < y {
|
|
stack.Push64(1)
|
|
} else {
|
|
stack.Push(Zero)
|
|
}
|
|
fmt.Printf(" %v < %v = %v\n", x, y, x < y)
|
|
|
|
case SGT: // 0x13
|
|
x, y := int64(stack.Pop64()), int64(stack.Pop64())
|
|
if x > y {
|
|
stack.Push64(1)
|
|
} else {
|
|
stack.Push(Zero)
|
|
}
|
|
fmt.Printf(" %v > %v = %v\n", x, y, x > y)
|
|
|
|
case EQ: // 0x14
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
if x > y {
|
|
stack.Push64(1)
|
|
} else {
|
|
stack.Push(Zero)
|
|
}
|
|
fmt.Printf(" %v == %v = %v\n", x, y, x == y)
|
|
|
|
case ISZERO: // 0x15
|
|
x := stack.Pop64()
|
|
if x == 0 {
|
|
stack.Push64(1)
|
|
} else {
|
|
stack.Push(Zero)
|
|
}
|
|
fmt.Printf(" %v == 0 = %v\n", x, x == 0)
|
|
|
|
case AND: // 0x16
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
stack.Push64(x & y)
|
|
fmt.Printf(" %v & %v = %v\n", x, y, x&y)
|
|
|
|
case OR: // 0x17
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
stack.Push64(x | y)
|
|
fmt.Printf(" %v | %v = %v\n", x, y, x|y)
|
|
|
|
case XOR: // 0x18
|
|
x, y := stack.Pop64(), stack.Pop64()
|
|
stack.Push64(x ^ y)
|
|
fmt.Printf(" %v ^ %v = %v\n", x, y, x^y)
|
|
|
|
case NOT: // 0x19
|
|
x := stack.Pop64()
|
|
stack.Push64(^x)
|
|
fmt.Printf(" !%v = %v\n", x, ^x)
|
|
|
|
case BYTE: // 0x1A
|
|
idx, val := stack.Pop64(), stack.Pop()
|
|
res := byte(0)
|
|
if idx < 32 {
|
|
res = val[idx]
|
|
}
|
|
stack.Push64(uint64(res))
|
|
fmt.Printf(" => 0x%X\n", res)
|
|
|
|
case SHA3: // 0x20
|
|
if ok = useGas(gas, GasSha3); !ok {
|
|
return nil, firstErr(err, ErrInsufficientGas)
|
|
}
|
|
offset, size := stack.Pop64(), stack.Pop64()
|
|
data, ok := subslice(memory, offset, size)
|
|
if !ok {
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds)
|
|
}
|
|
data = sha3.Sha3(data)
|
|
stack.PushBytes(data)
|
|
fmt.Printf(" => (%v) %X\n", size, data)
|
|
|
|
case ADDRESS: // 0x30
|
|
stack.Push(callee.Address)
|
|
fmt.Printf(" => %X\n", callee.Address)
|
|
|
|
case BALANCE: // 0x31
|
|
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
|
|
}
|
|
balance := account.Balance
|
|
stack.Push64(balance)
|
|
fmt.Printf(" => %v (%X)\n", balance, addr)
|
|
|
|
case ORIGIN: // 0x32
|
|
origin := vm.params.Origin
|
|
stack.Push(origin)
|
|
fmt.Printf(" => %X\n", origin)
|
|
|
|
case CALLER: // 0x33
|
|
stack.Push(caller.Address)
|
|
fmt.Printf(" => %X\n", caller.Address)
|
|
|
|
case CALLVALUE: // 0x34
|
|
stack.Push64(value)
|
|
fmt.Printf(" => %v\n", value)
|
|
|
|
case CALLDATALOAD: // 0x35
|
|
offset := stack.Pop64()
|
|
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(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 {
|
|
return nil, firstErr(err, ErrInputOutOfBounds)
|
|
}
|
|
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\n", l)
|
|
|
|
case CODECOPY: // 0x39
|
|
memOff := stack.Pop64()
|
|
codeOff := stack.Pop64()
|
|
length := stack.Pop64()
|
|
data, ok := subslice(code, codeOff, length)
|
|
if !ok {
|
|
return nil, firstErr(err, ErrCodeOutOfBounds)
|
|
}
|
|
dest, ok := subslice(memory, memOff, length)
|
|
if !ok {
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds)
|
|
}
|
|
copy(dest, data)
|
|
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
|
|
|
|
case GASPRICE_DEPRECATED: // 0x3A
|
|
stack.Push(Zero)
|
|
fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
|
|
|
|
case EXTCODESIZE: // 0x3B
|
|
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\n", l)
|
|
|
|
case EXTCODECOPY: // 0x3C
|
|
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 {
|
|
return nil, ErrCodeOutOfBounds
|
|
}
|
|
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
|
|
stack.Push(Zero)
|
|
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
|
|
|
|
case COINBASE: // 0x41
|
|
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\n", time)
|
|
|
|
case BLOCKHEIGHT: // 0x43
|
|
number := uint64(vm.params.BlockHeight)
|
|
stack.Push64(number)
|
|
fmt.Printf(" => 0x%X\n", number)
|
|
|
|
case GASLIMIT: // 0x45
|
|
stack.Push64(vm.params.GasLimit)
|
|
fmt.Printf(" => %v\n", vm.params.GasLimit)
|
|
|
|
case POP: // 0x50
|
|
stack.Pop()
|
|
fmt.Printf(" => %v\n", vm.params.GasLimit)
|
|
|
|
case MLOAD: // 0x51
|
|
offset := stack.Pop64()
|
|
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)
|
|
|
|
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: // 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)
|
|
|
|
case MSIZE: // 0x59
|
|
stack.Push64(uint64(len(memory)))
|
|
|
|
case GAS: // 0x5A
|
|
stack.Push64(*gas)
|
|
fmt.Printf(" => %X\n", *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)
|
|
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\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)
|
|
fmt.Printf(" => [%d] 0x%X\n", 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)
|
|
fmt.Printf(" => [%d]\n", n)
|
|
|
|
case LOG0, LOG1, LOG2, LOG3, LOG4:
|
|
n := int(op - LOG0)
|
|
topics := make([]Word, n)
|
|
offset, size := stack.Pop64(), stack.Pop64()
|
|
for i := 0; i < n; i++ {
|
|
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)
|
|
|
|
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)
|
|
}
|
|
|
|
// Check balance
|
|
if caller.Balance < value {
|
|
return nil, firstErr(err, ErrInsufficientBalance)
|
|
} else {
|
|
caller.Balance -= value
|
|
}
|
|
|
|
// Create a new address
|
|
nonce := caller.Nonce
|
|
addr := createAddress(caller.Address, nonce)
|
|
caller.Nonce += 1
|
|
|
|
// TODO charge for gas to create account _ the code length * GasCreateByte
|
|
|
|
newAccount, err := vm.appState.CreateAccount(addr, value)
|
|
if err != nil {
|
|
stack.Push(Zero)
|
|
fmt.Printf(" (*) 0x0 %v\n", err)
|
|
} else {
|
|
// 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.Push(Zero)
|
|
} else {
|
|
newAccount.Code = ret // Set the code
|
|
stack.Push(newAccount.Address)
|
|
}
|
|
}
|
|
|
|
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, ok := subslice(memory, inOffset, inSize)
|
|
if !ok {
|
|
return nil, firstErr(err, ErrMemoryOutOfBounds)
|
|
}
|
|
|
|
// Ensure that gasLimit is reasonable
|
|
if *gas < gasLimit {
|
|
return nil, firstErr(err, ErrInsufficientGas)
|
|
} else {
|
|
*gas -= gasLimit
|
|
// NOTE: we will return any used gas later.
|
|
}
|
|
|
|
// Begin execution
|
|
var ret []byte
|
|
var err error
|
|
|
|
// If addr is in nativeContracts
|
|
if nativeContract := nativeContracts[addr]; nativeContract != nil {
|
|
ret, err = nativeContract(args, &gasLimit)
|
|
} else {
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Handle remaining gas.
|
|
*gas += gasLimit
|
|
|
|
fmt.Printf("resume %X (%v)\n", callee.Address, gas)
|
|
|
|
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
|
|
|
|
default:
|
|
fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op)
|
|
panic(fmt.Errorf("Invalid opcode %X", op))
|
|
}
|
|
|
|
pc++
|
|
|
|
// If there is an error, return
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func subslice(data []byte, offset, length uint64) ([]byte, bool) {
|
|
size := uint64(len(data))
|
|
if size < offset {
|
|
return nil, false
|
|
} else if size < offset+length {
|
|
return data[offset:], false
|
|
} else {
|
|
return data[offset : offset+length], true
|
|
}
|
|
}
|
|
|
|
func codeGetOp(code []byte, n uint64) OpCode {
|
|
if uint64(len(code)) <= n {
|
|
return OpCode(0) // stop
|
|
} else {
|
|
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])
|
|
}
|