package vm import ( "bytes" "encoding/binary" "errors" "fmt" "math/big" "github.com/CityOfZion/neo-go/pkg/util" ) // ScriptBuilder generates bytecode and will write all // generated bytecode into its internal buffer. type ScriptBuilder struct { buf *bytes.Buffer } func (sb *ScriptBuilder) emit(op OpCode, b []byte) error { if err := sb.buf.WriteByte(byte(op)); err != nil { return err } _, err := sb.buf.Write(b) return err } func (sb *ScriptBuilder) emitPush(op OpCode) error { return sb.buf.WriteByte(byte(op)) } func (sb *ScriptBuilder) emitPushBool(b bool) error { if b { return sb.emitPush(OpPushT) } return sb.emitPush(OpPushF) } func (sb *ScriptBuilder) emitPushInt(i int64) error { if i == -1 { return sb.emitPush(OpPushM1) } if i == 0 { return sb.emitPush(OpPushF) } if i > 0 && i < 16 { val := OpCode((int(OpPush1) - 1 + int(i))) return sb.emitPush(val) } bInt := big.NewInt(i) val := util.ToArrayReverse(bInt.Bytes()) return sb.emitPushArray(val) } func (sb *ScriptBuilder) emitPushArray(b []byte) error { var ( err error n = len(b) ) if n == 0 { return errors.New("0 bytes given in pushArray") } if n <= int(OpPushBytes75) { return sb.emit(OpCode(n), b) } else if n < 0x100 { err = sb.emit(OpPushData1, []byte{byte(n)}) } else if n < 0x10000 { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(n)) err = sb.emit(OpPushData2, buf) } else { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(n)) err = sb.emit(OpPushData4, buf) } if err != nil { return err } _, err = sb.buf.Write(b) return err } func (sb *ScriptBuilder) emitPushString(str string) error { return sb.emitPushArray([]byte(str)) } func (sb *ScriptBuilder) emitSysCall(api string) error { lenAPI := len(api) if lenAPI == 0 { return errors.New("syscall argument cant be 0") } if lenAPI > 252 { return fmt.Errorf("invalid syscall argument: %s", api) } bapi := []byte(api) args := make([]byte, lenAPI+1) args[0] = byte(lenAPI) copy(args, bapi[1:]) return sb.emit(OpSysCall, args) } func (sb *ScriptBuilder) emitPushCall(scriptHash []byte, tailCall bool) error { if len(scriptHash) != 20 { return errors.New("expected a 20 byte long scriptHash (uint160) for pushCall") } op := OpAppCall if tailCall { op = OpTailCall } return sb.emit(op, scriptHash) } func (sb *ScriptBuilder) emitJump(op OpCode, offset int16) error { if op != OpJMP && op != OpJMPIF && op != OpJMPIFNOT && op != OpCall { return fmt.Errorf("invalid jump opcode: %v", op) } return sb.emit(op, []byte{}) // convert to bits? } func (sb *ScriptBuilder) dumpOpcode() { buf := sb.buf.Bytes() for i := 0; i < len(buf); i++ { fmt.Printf("OPCODE AT INDEX \t %d \t 0x%2x \t %s \n", i, buf[i], OpCode(buf[i])) } }