neoneo-go/pkg/vm/stack/builder.go

178 lines
4 KiB
Go
Raw Normal View History

VM: Add basic vm (#166) * VM:Add abstract stack item * VM: Add stackItems; Array, Boolean, Int and ByteArray * VM: Add tests for stack item * VM: first pass at Random Access Stack object * VM: Add Sub, Mul, Mod LSH, RSH * VM: moved test helper functions into separate file * VM: removed helper functions from stack_test.go * Add conversions for bytearray and Int stack items * Add instructions file for vm * - Add guide to stack readme - Add testReadInt64 * Add Builder * Refactor Int, Boolean, ByteArray conversion * Add Context stack Item * Add Invocation stack - convenience RAS * rename testhelper to test_helper * Move opcode file * - Add `Add` OpCode - Add Opcode Function map * - Add test for math `Add` opcode - basic opcode execution * Add popTwoIntegers convenience func * Add `SUB` Opcode * Export Context Read methods - Return errors where failable * - Add `Op` to handleOP func signature - Add PushNBytes OPcode * remove error on NewBoolean - Expose underlying with Getter on Boolean StackItem - Add Equals method for ByteArray * Make Next() method on Context failable, refactor peekContext and Peek * Add ExecuteOp, Step and Run methods on the VM * Add Equal Opcode * Add THROWIFNOT Opcode * Add RET Opcode * Refactor PushNBytes Opcode * refactor Add, Sub to return VMSTATE add popTwoByteArrays helper function * Add basic tests for vm * clarify vm states * Add astack * [VM] Pass ResultStack to the opcode handlers * [VM] refactor handlers to have rstack as argument * [Stack] - Change RemoveCurrentContext for PopCurrentContext - Add CopTo method to stack * [VM] Add Result stack Len check in simple run test * [VM] fix typo * [Peer/Stall] Change seconds to milliseconds in test
2019-03-18 21:40:21 +00:00
package stack
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math/big"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
// Builder follows the builder pattern and will be used to build scripts
type Builder struct {
w *bytes.Buffer
err error
}
// NewBuilder returns a new builder object
func NewBuilder() *Builder {
return &Builder{
w: &bytes.Buffer{},
err: nil,
}
}
// Bytes returns the byte representation of the built buffer
func (br *Builder) Bytes() []byte {
return br.w.Bytes()
}
// Emit a VM Opcode with data to the given buffer.
func (br *Builder) Emit(op Instruction, b []byte) *Builder {
if br.err != nil {
return br
}
br.err = br.w.WriteByte(byte(op))
_, br.err = br.w.Write(b)
return br
}
// EmitOpcode emits a single VM Opcode the given buffer.
func (br *Builder) EmitOpcode(op Instruction) *Builder {
if br.err != nil {
return br
}
br.err = br.w.WriteByte(byte(op))
return br
}
// EmitBool emits a bool type the given buffer.
func (br *Builder) EmitBool(ok bool) *Builder {
if br.err != nil {
return br
}
op := PUSHT
if !ok {
op = PUSHF
}
return br.EmitOpcode(op)
}
// EmitInt emits a int type to the given buffer.
func (br *Builder) EmitInt(i int64) *Builder {
if br.err != nil {
return br
}
if i == -1 {
return br.EmitOpcode(PUSHM1)
}
if i == 0 {
return br.EmitOpcode(PUSHF)
}
if i > 0 && i < 16 {
val := Instruction(int(PUSH1) - 1 + int(i))
return br.EmitOpcode(val)
}
bInt := big.NewInt(i)
val := reverse(bInt.Bytes())
return br.EmitBytes(val)
}
// EmitString emits a string to the given buffer.
func (br *Builder) EmitString(s string) *Builder {
if br.err != nil {
return br
}
return br.EmitBytes([]byte(s))
}
// EmitBytes emits a byte array to the given buffer.
func (br *Builder) EmitBytes(b []byte) *Builder {
if br.err != nil {
return br
}
var (
n = len(b)
)
if n <= int(PUSHBYTES75) {
return br.Emit(Instruction(n), b)
} else if n < 0x100 {
br.Emit(PUSHDATA1, []byte{byte(n)})
} else if n < 0x10000 {
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(n))
br.Emit(PUSHDATA2, buf)
} else {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(n))
br.Emit(PUSHDATA4, buf)
}
_, br.err = br.w.Write(b)
return br
}
// EmitSyscall emits the syscall API to the given buffer.
// Syscall API string cannot be 0.
func (br *Builder) EmitSyscall(api string) *Builder {
if br.err != nil {
return br
}
if len(api) == 0 {
br.err = errors.New("syscall api cannot be of length 0")
}
buf := make([]byte, len(api)+1)
buf[0] = byte(len(api))
copy(buf[1:], []byte(api))
return br.Emit(SYSCALL, buf)
}
// EmitCall emits a call Opcode with label to the given buffer.
func (br *Builder) EmitCall(op Instruction, label int16) *Builder {
return br.EmitJmp(op, label)
}
// EmitJmp emits a jump Opcode along with label to the given buffer.
func (br *Builder) EmitJmp(op Instruction, label int16) *Builder {
if !isOpcodeJmp(op) {
br.err = fmt.Errorf("opcode %d is not a jump or call type", op)
}
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(label))
return br.Emit(op, buf)
}
// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be
// emitted instead.
func (br *Builder) EmitAppCall(scriptHash util.Uint160, tailCall bool) *Builder {
op := APPCALL
if tailCall {
op = TAILCALL
}
return br.Emit(op, scriptHash.Bytes())
}
// EmitAppCallWithOperationAndData emits an appcall with the given operation and data.
func (br *Builder) EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) *Builder {
br.EmitBytes(data)
br.EmitString(operation)
return br.EmitAppCall(scriptHash, false)
}
// EmitAppCallWithOperation emits an appcall with the given operation.
func (br *Builder) EmitAppCallWithOperation(scriptHash util.Uint160, operation string) *Builder {
br.EmitBool(false)
br.EmitString(operation)
return br.EmitAppCall(scriptHash, false)
}
func isOpcodeJmp(op Instruction) bool {
if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL {
return true
}
return false
}