diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 0a22cf76b..9e9e4867b 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -23,6 +23,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/smartcontract/trigger" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm" + "github.com/CityOfZion/neo-go/pkg/vm/emit" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -1486,7 +1487,7 @@ func ScriptFromWitness(hash util.Uint160, witness *transaction.Witness) ([]byte, if len(verification) == 0 { bb := new(bytes.Buffer) - err := vm.EmitAppCall(bb, hash, false) + err := emit.AppCall(bb, hash, false) if err != nil { return nil, err } diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/txBuilder.go index 38c8d2665..12eb440df 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/txBuilder.go @@ -12,7 +12,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" - "github.com/CityOfZion/neo-go/pkg/vm" + "github.com/CityOfZion/neo-go/pkg/vm/emit" "github.com/CityOfZion/neo-go/pkg/vm/opcode" errs "github.com/pkg/errors" ) @@ -114,19 +114,19 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro var props smartcontract.PropertyState script := new(bytes.Buffer) - if err := vm.EmitBytes(script, []byte(contract.Description)); err != nil { + if err := emit.Bytes(script, []byte(contract.Description)); err != nil { return nil, err } - if err := vm.EmitBytes(script, []byte(contract.Email)); err != nil { + if err := emit.Bytes(script, []byte(contract.Email)); err != nil { return nil, err } - if err := vm.EmitBytes(script, []byte(contract.Author)); err != nil { + if err := emit.Bytes(script, []byte(contract.Author)); err != nil { return nil, err } - if err := vm.EmitBytes(script, []byte(contract.Version)); err != nil { + if err := emit.Bytes(script, []byte(contract.Version)); err != nil { return nil, err } - if err := vm.EmitBytes(script, []byte(contract.ProjectName)); err != nil { + if err := emit.Bytes(script, []byte(contract.ProjectName)); err != nil { return nil, err } if contract.HasStorage { @@ -138,23 +138,23 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro if contract.IsPayable { props |= smartcontract.IsPayable } - if err := vm.EmitInt(script, int64(props)); err != nil { + if err := emit.Int(script, int64(props)); err != nil { return nil, err } - if err := vm.EmitInt(script, int64(contract.ReturnType)); err != nil { + if err := emit.Int(script, int64(contract.ReturnType)); err != nil { return nil, err } params := make([]byte, len(contract.Parameters)) for k := range contract.Parameters { params[k] = byte(contract.Parameters[k]) } - if err := vm.EmitBytes(script, params); err != nil { + if err := emit.Bytes(script, params); err != nil { return nil, err } - if err := vm.EmitBytes(script, avm); err != nil { + if err := emit.Bytes(script, avm); err != nil { return nil, err } - if err := vm.EmitSyscall(script, "Neo.Contract.Create"); err != nil { + if err := emit.Syscall(script, "Neo.Contract.Create"); err != nil { return nil, err } return script.Bytes(), nil @@ -174,7 +174,7 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := vm.EmitBytes(script, str); err != nil { + if err := emit.Bytes(script, str); err != nil { return err } case String: @@ -182,7 +182,7 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := vm.EmitString(script, str); err != nil { + if err := emit.String(script, str); err != nil { return err } case Hash160: @@ -190,7 +190,7 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := vm.EmitBytes(script, hash.BytesBE()); err != nil { + if err := emit.Bytes(script, hash.BytesBE()); err != nil { return err } case Hash256: @@ -198,7 +198,7 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := vm.EmitBytes(script, hash.BytesBE()); err != nil { + if err := emit.Bytes(script, hash.BytesBE()); err != nil { return err } case PublicKey: @@ -210,7 +210,7 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := vm.EmitBytes(script, key.Bytes()); err != nil { + if err := emit.Bytes(script, key.Bytes()); err != nil { return err } case Integer: @@ -218,7 +218,7 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := vm.EmitInt(script, int64(val)); err != nil { + if err := emit.Int(script, int64(val)); err != nil { return err } case Boolean: @@ -228,9 +228,9 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { } switch str { case "true": - err = vm.EmitInt(script, 1) + err = emit.Int(script, 1) case "false": - err = vm.EmitInt(script, 0) + err = emit.Int(script, 0) default: err = errors.New("wrong boolean value") } @@ -251,7 +251,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt for i := len(params) - 1; i >= 0; i-- { switch params[i].Type { case stringT: - if err := vm.EmitString(script, params[i].String()); err != nil { + if err := emit.String(script, params[i].String()); err != nil { return nil, err } case numberT: @@ -259,7 +259,7 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt if err != nil { return nil, err } - if err := vm.EmitString(script, strconv.Itoa(num)); err != nil { + if err := emit.String(script, strconv.Itoa(num)); err != nil { return nil, err } case arrayT: @@ -271,18 +271,18 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt if err != nil { return nil, err } - err = vm.EmitInt(script, int64(len(slice))) + err = emit.Int(script, int64(len(slice))) if err != nil { return nil, err } - err = vm.EmitOpcode(script, opcode.PACK) + err = emit.Opcode(script, opcode.PACK) if err != nil { return nil, err } } } - if err := vm.EmitAppCall(script, contract, false); err != nil { + if err := emit.AppCall(script, contract, false); err != nil { return nil, err } return script.Bytes(), nil @@ -298,7 +298,7 @@ func CreateInvocationScript(contract util.Uint160, funcParams []Param) ([]byte, if err != nil { return nil, err } - if err = vm.EmitAppCall(script, contract, false); err != nil { + if err = emit.AppCall(script, contract, false); err != nil { return nil, err } return script.Bytes(), nil diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go index d1c4f7521..73efdc0f7 100644 --- a/pkg/smartcontract/contract.go +++ b/pkg/smartcontract/contract.go @@ -6,7 +6,7 @@ import ( "sort" "github.com/CityOfZion/neo-go/pkg/crypto/keys" - "github.com/CityOfZion/neo-go/pkg/vm" + "github.com/CityOfZion/neo-go/pkg/vm/emit" "github.com/CityOfZion/neo-go/pkg/vm/opcode" ) @@ -23,19 +23,19 @@ func CreateMultiSigRedeemScript(m int, publicKeys keys.PublicKeys) ([]byte, erro } buf := new(bytes.Buffer) - if err := vm.EmitInt(buf, int64(m)); err != nil { + if err := emit.Int(buf, int64(m)); err != nil { return nil, err } sort.Sort(publicKeys) for _, pubKey := range publicKeys { - if err := vm.EmitBytes(buf, pubKey.Bytes()); err != nil { + if err := emit.Bytes(buf, pubKey.Bytes()); err != nil { return nil, err } } - if err := vm.EmitInt(buf, int64(len(publicKeys))); err != nil { + if err := emit.Int(buf, int64(len(publicKeys))); err != nil { return nil, err } - if err := vm.EmitOpcode(buf, opcode.CHECKMULTISIG); err != nil { + if err := emit.Opcode(buf, opcode.CHECKMULTISIG); err != nil { return nil, err } diff --git a/pkg/vm/emit.go b/pkg/vm/emit.go deleted file mode 100644 index 72720c2f3..000000000 --- a/pkg/vm/emit.go +++ /dev/null @@ -1,151 +0,0 @@ -package vm - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "math/big" - - "github.com/CityOfZion/neo-go/pkg/util" - "github.com/CityOfZion/neo-go/pkg/vm/opcode" -) - -// Emit a VM Instruction with data to the given buffer. -func Emit(w *bytes.Buffer, op opcode.Opcode, b []byte) error { - if err := w.WriteByte(byte(op)); err != nil { - return err - } - _, err := w.Write(b) - return err -} - -// EmitOpcode emits a single VM Instruction the given buffer. -func EmitOpcode(w io.ByteWriter, op opcode.Opcode) error { - return w.WriteByte(byte(op)) -} - -// EmitBool emits a bool type the given buffer. -func EmitBool(w io.ByteWriter, ok bool) error { - if ok { - return EmitOpcode(w, opcode.PUSHT) - } - return EmitOpcode(w, opcode.PUSHF) -} - -// EmitInt emits a int type to the given buffer. -func EmitInt(w *bytes.Buffer, i int64) error { - if i == -1 { - return EmitOpcode(w, opcode.PUSHM1) - } - if i == 0 { - return EmitOpcode(w, opcode.PUSHF) - } - if i > 0 && i < 16 { - val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i)) - return EmitOpcode(w, val) - } - - bInt := big.NewInt(i) - val := util.ArrayReverse(bInt.Bytes()) - return EmitBytes(w, val) -} - -// EmitString emits a string to the given buffer. -func EmitString(w *bytes.Buffer, s string) error { - return EmitBytes(w, []byte(s)) -} - -// EmitBytes emits a byte array to the given buffer. -func EmitBytes(w *bytes.Buffer, b []byte) error { - var ( - err error - n = len(b) - ) - - if n <= int(opcode.PUSHBYTES75) { - return Emit(w, opcode.Opcode(n), b) - } else if n < 0x100 { - err = Emit(w, opcode.PUSHDATA1, []byte{byte(n)}) - } else if n < 0x10000 { - buf := make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(n)) - err = Emit(w, opcode.PUSHDATA2, buf) - } else { - buf := make([]byte, 4) - binary.LittleEndian.PutUint32(buf, uint32(n)) - err = Emit(w, opcode.PUSHDATA4, buf) - } - if err != nil { - return err - } - _, err = w.Write(b) - return err -} - -// EmitSyscall emits the syscall API to the given buffer. -// Syscall API string cannot be 0. -func EmitSyscall(w *bytes.Buffer, api string) error { - if len(api) == 0 { - return errors.New("syscall api cannot be of length 0") - } - buf := make([]byte, len(api)+1) - buf[0] = byte(len(api)) - copy(buf[1:], api) - return Emit(w, opcode.SYSCALL, buf) -} - -// EmitCall emits a call Instruction with label to the given buffer. -func EmitCall(w *bytes.Buffer, op opcode.Opcode, label int16) error { - return EmitJmp(w, op, label) -} - -// EmitJmp emits a jump Instruction along with label to the given buffer. -func EmitJmp(w *bytes.Buffer, op opcode.Opcode, label int16) error { - if !isInstructionJmp(op) { - return fmt.Errorf("opcode %s is not a jump or call type", op.String()) - } - buf := make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(label)) - return Emit(w, op, buf) -} - -// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be -// emitted instead. -func EmitAppCall(w *bytes.Buffer, scriptHash util.Uint160, tailCall bool) error { - op := opcode.APPCALL - if tailCall { - op = opcode.TAILCALL - } - return Emit(w, op, scriptHash.BytesBE()) -} - -// EmitAppCallWithOperationAndData emits an appcall with the given operation and data. -func EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) error { - if err := EmitBytes(w, data); err != nil { - return err - } - if err := EmitString(w, operation); err != nil { - return err - } - return EmitAppCall(w, scriptHash, false) -} - -// EmitAppCallWithOperation emits an appcall with the given operation. -func EmitAppCallWithOperation(w *bytes.Buffer, scriptHash util.Uint160, operation string) error { - if err := EmitBool(w, false); err != nil { - return err - } - if err := EmitString(w, operation); err != nil { - return err - } - return EmitAppCall(w, scriptHash, false) -} - -func isInstructionJmp(op opcode.Opcode) bool { - if op == opcode.JMP || op == opcode.JMPIFNOT || op == opcode.JMPIF || op == opcode.CALL { - return true - } - return false -} diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go new file mode 100644 index 000000000..96aa09063 --- /dev/null +++ b/pkg/vm/emit/emit.go @@ -0,0 +1,151 @@ +package emit + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/vm/opcode" +) + +// Instruction emits a VM Instruction with data to the given buffer. +func Instruction(w *bytes.Buffer, op opcode.Opcode, b []byte) error { + if err := w.WriteByte(byte(op)); err != nil { + return err + } + _, err := w.Write(b) + return err +} + +// Opcode emits a single VM Instruction without arguments to the given buffer. +func Opcode(w io.ByteWriter, op opcode.Opcode) error { + return w.WriteByte(byte(op)) +} + +// Bool emits a bool type the given buffer. +func Bool(w io.ByteWriter, ok bool) error { + if ok { + return Opcode(w, opcode.PUSHT) + } + return Opcode(w, opcode.PUSHF) +} + +// Int emits a int type to the given buffer. +func Int(w *bytes.Buffer, i int64) error { + if i == -1 { + return Opcode(w, opcode.PUSHM1) + } + if i == 0 { + return Opcode(w, opcode.PUSHF) + } + if i > 0 && i < 16 { + val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i)) + return Opcode(w, val) + } + + bInt := big.NewInt(i) + val := util.ArrayReverse(bInt.Bytes()) + return Bytes(w, val) +} + +// String emits a string to the given buffer. +func String(w *bytes.Buffer, s string) error { + return Bytes(w, []byte(s)) +} + +// Bytes emits a byte array to the given buffer. +func Bytes(w *bytes.Buffer, b []byte) error { + var ( + err error + n = len(b) + ) + + if n <= int(opcode.PUSHBYTES75) { + return Instruction(w, opcode.Opcode(n), b) + } else if n < 0x100 { + err = Instruction(w, opcode.PUSHDATA1, []byte{byte(n)}) + } else if n < 0x10000 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(n)) + err = Instruction(w, opcode.PUSHDATA2, buf) + } else { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(n)) + err = Instruction(w, opcode.PUSHDATA4, buf) + } + if err != nil { + return err + } + _, err = w.Write(b) + return err +} + +// Syscall emits the syscall API to the given buffer. +// Syscall API string cannot be 0. +func Syscall(w *bytes.Buffer, api string) error { + if len(api) == 0 { + return errors.New("syscall api cannot be of length 0") + } + buf := make([]byte, len(api)+1) + buf[0] = byte(len(api)) + copy(buf[1:], api) + return Instruction(w, opcode.SYSCALL, buf) +} + +// Call emits a call Instruction with label to the given buffer. +func Call(w *bytes.Buffer, op opcode.Opcode, label int16) error { + return Jmp(w, op, label) +} + +// Jmp emits a jump Instruction along with label to the given buffer. +func Jmp(w *bytes.Buffer, op opcode.Opcode, label int16) error { + if !isInstructionJmp(op) { + return fmt.Errorf("opcode %s is not a jump or call type", op.String()) + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(label)) + return Instruction(w, op, buf) +} + +// AppCall emits an appcall, if tailCall is true, tailCall opcode will be +// emitted instead. +func AppCall(w *bytes.Buffer, scriptHash util.Uint160, tailCall bool) error { + op := opcode.APPCALL + if tailCall { + op = opcode.TAILCALL + } + return Instruction(w, op, scriptHash.BytesBE()) +} + +// AppCallWithOperationAndData emits an appcall with the given operation and data. +func AppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) error { + if err := Bytes(w, data); err != nil { + return err + } + if err := String(w, operation); err != nil { + return err + } + return AppCall(w, scriptHash, false) +} + +// AppCallWithOperation emits an appcall with the given operation. +func AppCallWithOperation(w *bytes.Buffer, scriptHash util.Uint160, operation string) error { + if err := Bool(w, false); err != nil { + return err + } + if err := String(w, operation); err != nil { + return err + } + return AppCall(w, scriptHash, false) +} + +func isInstructionJmp(op opcode.Opcode) bool { + if op == opcode.JMP || op == opcode.JMPIFNOT || op == opcode.JMPIF || op == opcode.CALL { + return true + } + return false +} diff --git a/pkg/vm/emit_test.go b/pkg/vm/emit/emit_test.go similarity index 88% rename from pkg/vm/emit_test.go rename to pkg/vm/emit/emit_test.go index 1278f3d7b..487cbc35a 100644 --- a/pkg/vm/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -1,4 +1,4 @@ -package vm +package emit import ( "bytes" @@ -11,22 +11,22 @@ import ( func TestEmitInt(t *testing.T) { buf := new(bytes.Buffer) - EmitInt(buf, 10) + Int(buf, 10) assert.Equal(t, opcode.Opcode(buf.Bytes()[0]), opcode.PUSH10) buf.Reset() - EmitInt(buf, 100) + Int(buf, 100) assert.Equal(t, buf.Bytes()[0], uint8(1)) assert.Equal(t, buf.Bytes()[1], uint8(100)) buf.Reset() - EmitInt(buf, 1000) + Int(buf, 1000) assert.Equal(t, buf.Bytes()[0], uint8(2)) assert.Equal(t, buf.Bytes()[1:3], []byte{0xe8, 0x03}) } func TestEmitBool(t *testing.T) { buf := new(bytes.Buffer) - EmitBool(buf, true) - EmitBool(buf, false) + Bool(buf, true) + Bool(buf, false) assert.Equal(t, opcode.Opcode(buf.Bytes()[0]), opcode.PUSH1) assert.Equal(t, opcode.Opcode(buf.Bytes()[1]), opcode.PUSH0) } @@ -34,7 +34,7 @@ func TestEmitBool(t *testing.T) { func TestEmitString(t *testing.T) { buf := new(bytes.Buffer) str := "City Of Zion" - EmitString(buf, str) + String(buf, str) assert.Equal(t, buf.Len(), len(str)+1) assert.Equal(t, buf.Bytes()[1:], []byte(str)) } @@ -48,7 +48,7 @@ func TestEmitSyscall(t *testing.T) { buf := new(bytes.Buffer) for _, syscall := range syscalls { - EmitSyscall(buf, syscall) + Syscall(buf, syscall) assert.Equal(t, opcode.Opcode(buf.Bytes()[0]), opcode.SYSCALL) assert.Equal(t, buf.Bytes()[1], uint8(len(syscall))) assert.Equal(t, buf.Bytes()[2:], []byte(syscall)) @@ -58,7 +58,7 @@ func TestEmitSyscall(t *testing.T) { func TestEmitCall(t *testing.T) { buf := new(bytes.Buffer) - EmitCall(buf, opcode.JMP, 100) + Call(buf, opcode.JMP, 100) assert.Equal(t, opcode.Opcode(buf.Bytes()[0]), opcode.JMP) label := binary.LittleEndian.Uint16(buf.Bytes()[1:3]) assert.Equal(t, label, uint16(100)) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index cab01864a..222cace89 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -11,6 +11,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/vm/emit" "github.com/CityOfZion/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -31,8 +32,8 @@ func TestInteropHook(t *testing.T) { v.RegisterInteropGetter(fooInteropGetter) buf := new(bytes.Buffer) - EmitSyscall(buf, "foo") - EmitOpcode(buf, opcode.RET) + emit.Syscall(buf, "foo") + emit.Opcode(buf, opcode.RET) v.Load(buf.Bytes()) runVM(t, v) assert.Equal(t, 1, v.estack.Len()) @@ -47,8 +48,8 @@ func TestInteropHookViaID(t *testing.T) { fooid := InteropNameToID([]byte("foo")) var id = make([]byte, 4) binary.LittleEndian.PutUint32(id, fooid) - _ = EmitSyscall(buf, string(id)) - _ = EmitOpcode(buf, opcode.RET) + _ = emit.Syscall(buf, string(id)) + _ = emit.Opcode(buf, opcode.RET) v.Load(buf.Bytes()) runVM(t, v) assert.Equal(t, 1, v.estack.Len()) @@ -133,7 +134,7 @@ func TestPushBytes1to75(t *testing.T) { buf := new(bytes.Buffer) for i := 1; i <= 75; i++ { b := randomBytes(i) - EmitBytes(buf, b) + emit.Bytes(buf, b) vm := load(buf.Bytes()) err := vm.Step() require.NoError(t, err)