neoneo-go/pkg/vm/stack/builder.go
2019-03-15 22:27:34 +00:00

177 lines
4 KiB
Go

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
}