From 4d8a3a359bea32de3b42ddc245ef22605fb3cf36 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 17:24:57 +0300 Subject: [PATCH 1/9] vm: move Emit* functions to a separate package Also strip 'Emit' prefix because 'emit' is now in the package name. --- pkg/core/blockchain.go | 3 +- pkg/rpc/txBuilder.go | 50 +++++------ pkg/smartcontract/contract.go | 10 +-- pkg/vm/emit.go | 151 --------------------------------- pkg/vm/emit/emit.go | 151 +++++++++++++++++++++++++++++++++ pkg/vm/{ => emit}/emit_test.go | 18 ++-- pkg/vm/vm_test.go | 11 +-- 7 files changed, 198 insertions(+), 196 deletions(-) delete mode 100644 pkg/vm/emit.go create mode 100644 pkg/vm/emit/emit.go rename pkg/vm/{ => emit}/emit_test.go (88%) 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) From c821e1c4c8cdb65e5217f9a1c7afb63ab2f33d64 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 18:05:13 +0300 Subject: [PATCH 2/9] vm: move IntToBytes and BytesToInt to emit package --- pkg/vm/{ => emit}/bigint.go | 2 +- pkg/vm/{ => emit}/bigint_test.go | 2 +- pkg/vm/json_test.go | 4 +++- pkg/vm/serialization.go | 6 ++++-- pkg/vm/stack.go | 4 +++- pkg/vm/stack_item.go | 4 +++- 6 files changed, 15 insertions(+), 7 deletions(-) rename pkg/vm/{ => emit}/bigint.go (99%) rename pkg/vm/{ => emit}/bigint_test.go (99%) diff --git a/pkg/vm/bigint.go b/pkg/vm/emit/bigint.go similarity index 99% rename from pkg/vm/bigint.go rename to pkg/vm/emit/bigint.go index f0698c87e..8c14a377c 100644 --- a/pkg/vm/bigint.go +++ b/pkg/vm/emit/bigint.go @@ -1,4 +1,4 @@ -package vm +package emit import ( "encoding/binary" diff --git a/pkg/vm/bigint_test.go b/pkg/vm/emit/bigint_test.go similarity index 99% rename from pkg/vm/bigint_test.go rename to pkg/vm/emit/bigint_test.go index 756accf62..542c4050b 100644 --- a/pkg/vm/bigint_test.go +++ b/pkg/vm/emit/bigint_test.go @@ -1,4 +1,4 @@ -package vm +package emit import ( "math" diff --git a/pkg/vm/json_test.go b/pkg/vm/json_test.go index d459f3d14..9216d75be 100644 --- a/pkg/vm/json_test.go +++ b/pkg/vm/json_test.go @@ -13,6 +13,8 @@ import ( "strings" "testing" + "github.com/CityOfZion/neo-go/pkg/vm/emit" + "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/vm/opcode" @@ -196,7 +198,7 @@ func compareItems(t *testing.T, a, b StackItem) { case *BigIntegerItem: require.Equal(t, val, ac.value.Int64()) case *ByteArrayItem: - require.Equal(t, val, BytesToInt(ac.value).Int64()) + require.Equal(t, val, emit.BytesToInt(ac.value).Int64()) case *BoolItem: if ac.value { require.Equal(t, val, int64(1)) diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go index 245102f20..6de372163 100644 --- a/pkg/vm/serialization.go +++ b/pkg/vm/serialization.go @@ -3,6 +3,8 @@ package vm import ( "errors" + "github.com/CityOfZion/neo-go/pkg/vm/emit" + "github.com/CityOfZion/neo-go/pkg/io" ) @@ -48,7 +50,7 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { w.WriteBool(t.value) case *BigIntegerItem: w.WriteBytes([]byte{byte(integerT)}) - w.WriteVarBytes(IntToBytes(t.value)) + w.WriteVarBytes(emit.IntToBytes(t.value)) case *InteropItem: w.Err = errors.New("not supported") case *ArrayItem, *StructItem: @@ -106,7 +108,7 @@ func DecodeBinaryStackItem(r *io.BinReader) StackItem { return NewBoolItem(b) case integerT: data := r.ReadVarBytes() - num := BytesToInt(data) + num := emit.BytesToInt(data) return &BigIntegerItem{ value: num, } diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index 2b8596414..b968d9cc2 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "math/big" + + "github.com/CityOfZion/neo-go/pkg/vm/emit" ) // Stack implementation for the neo-go virtual machine. The stack implements @@ -81,7 +83,7 @@ func (e *Element) BigInt() *big.Int { return big.NewInt(0) default: b := t.Value().([]uint8) - return BytesToInt(b) + return emit.BytesToInt(b) } } diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index 21db4a785..19833ead4 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -7,6 +7,8 @@ import ( "fmt" "math/big" "reflect" + + "github.com/CityOfZion/neo-go/pkg/vm/emit" ) // A StackItem represents the "real" value that is pushed on the stack. @@ -142,7 +144,7 @@ func NewBigIntegerItem(value int) *BigIntegerItem { // Bytes converts i to a slice of bytes. func (i *BigIntegerItem) Bytes() []byte { - return IntToBytes(i.value) + return emit.IntToBytes(i.value) } // Value implements StackItem interface. From 1400ecfddecd1e0af269c782c89b9d4b732767be Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 18:06:39 +0300 Subject: [PATCH 3/9] emit: fix Int to serialize integers in correct format Related #605, #623. --- pkg/vm/emit/emit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 96aa09063..3fb9e99b3 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -48,7 +48,7 @@ func Int(w *bytes.Buffer, i int64) error { } bInt := big.NewInt(i) - val := util.ArrayReverse(bInt.Bytes()) + val := IntToBytes(bInt) return Bytes(w, val) } From 698c647f07873b1b648e5865c51f2c0d06bc5831 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 17:50:25 +0300 Subject: [PATCH 4/9] emit: refactor tests Add structure and call Bytes() method on buffer once. --- pkg/vm/emit/emit_test.go | 50 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index 487cbc35a..f56b37226 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -10,25 +10,37 @@ import ( ) func TestEmitInt(t *testing.T) { - buf := new(bytes.Buffer) - Int(buf, 10) - assert.Equal(t, opcode.Opcode(buf.Bytes()[0]), opcode.PUSH10) - buf.Reset() - Int(buf, 100) - assert.Equal(t, buf.Bytes()[0], uint8(1)) - assert.Equal(t, buf.Bytes()[1], uint8(100)) - buf.Reset() - Int(buf, 1000) - assert.Equal(t, buf.Bytes()[0], uint8(2)) - assert.Equal(t, buf.Bytes()[1:3], []byte{0xe8, 0x03}) + t.Run("1-byte int", func(t *testing.T) { + buf := new(bytes.Buffer) + Int(buf, 10) + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSH10, result[0]) + }) + + t.Run("2-byte int", func(t *testing.T) { + buf := new(bytes.Buffer) + Int(buf, 100) + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSHBYTES1, result[0]) + assert.EqualValues(t, 100, result[1]) + }) + + t.Run("4-byte int", func(t *testing.T) { + buf := new(bytes.Buffer) + Int(buf, 1000) + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSHBYTES2, result[0]) + assert.EqualValues(t, 1000, binary.LittleEndian.Uint16(result[1:3])) + }) } func TestEmitBool(t *testing.T) { buf := new(bytes.Buffer) 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) + result := buf.Bytes() + assert.Equal(t, opcode.Opcode(result[0]), opcode.PUSH1) + assert.Equal(t, opcode.Opcode(result[1]), opcode.PUSH0) } func TestEmitString(t *testing.T) { @@ -49,9 +61,10 @@ func TestEmitSyscall(t *testing.T) { buf := new(bytes.Buffer) for _, syscall := range syscalls { 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)) + result := buf.Bytes() + assert.Equal(t, opcode.Opcode(result[0]), opcode.SYSCALL) + assert.Equal(t, result[1], uint8(len(syscall))) + assert.Equal(t, result[2:], []byte(syscall)) buf.Reset() } } @@ -59,7 +72,8 @@ func TestEmitSyscall(t *testing.T) { func TestEmitCall(t *testing.T) { buf := new(bytes.Buffer) Call(buf, opcode.JMP, 100) - assert.Equal(t, opcode.Opcode(buf.Bytes()[0]), opcode.JMP) - label := binary.LittleEndian.Uint16(buf.Bytes()[1:3]) + result := buf.Bytes() + assert.Equal(t, opcode.Opcode(result[0]), opcode.JMP) + label := binary.LittleEndian.Uint16(result[1:3]) assert.Equal(t, label, uint16(100)) } From 8243a8b3a7686b280a93590e5c11046ca6889a69 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 17:46:51 +0300 Subject: [PATCH 5/9] emit: use io.BinWriter instead of bytes.Buffer --- pkg/core/blockchain.go | 8 +- pkg/rpc/txBuilder.go | 112 ++++++++------------------- pkg/smartcontract/contract.go | 20 ++--- pkg/vm/emit/emit.go | 137 ++++++++++++++++------------------ pkg/vm/emit/emit_test.go | 32 ++++---- pkg/vm/vm_test.go | 18 +++-- 6 files changed, 128 insertions(+), 199 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 9e9e4867b..d9219c9f5 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1,7 +1,6 @@ package core import ( - "bytes" "fmt" "math" "math/big" @@ -1486,11 +1485,8 @@ func ScriptFromWitness(hash util.Uint160, witness *transaction.Witness) ([]byte, verification := witness.VerificationScript if len(verification) == 0 { - bb := new(bytes.Buffer) - err := emit.AppCall(bb, hash, false) - if err != nil { - return nil, err - } + bb := io.NewBufBinWriter() + emit.AppCall(bb.BinWriter, hash, false) verification = bb.Bytes() } else if h := witness.ScriptHash(); hash != h { return nil, errors.New("witness hash mismatch") diff --git a/pkg/rpc/txBuilder.go b/pkg/rpc/txBuilder.go index 12eb440df..349343bc6 100644 --- a/pkg/rpc/txBuilder.go +++ b/pkg/rpc/txBuilder.go @@ -1,7 +1,6 @@ package rpc import ( - "bytes" "errors" "fmt" "strconv" @@ -113,22 +112,12 @@ func GetInvocationScript(tx *transaction.Transaction, wif *keys.WIF) ([]byte, er func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) { var props smartcontract.PropertyState - script := new(bytes.Buffer) - if err := emit.Bytes(script, []byte(contract.Description)); err != nil { - return nil, err - } - if err := emit.Bytes(script, []byte(contract.Email)); err != nil { - return nil, err - } - if err := emit.Bytes(script, []byte(contract.Author)); err != nil { - return nil, err - } - if err := emit.Bytes(script, []byte(contract.Version)); err != nil { - return nil, err - } - if err := emit.Bytes(script, []byte(contract.ProjectName)); err != nil { - return nil, err - } + script := io.NewBufBinWriter() + emit.Bytes(script.BinWriter, []byte(contract.Description)) + emit.Bytes(script.BinWriter, []byte(contract.Email)) + emit.Bytes(script.BinWriter, []byte(contract.Author)) + emit.Bytes(script.BinWriter, []byte(contract.Version)) + emit.Bytes(script.BinWriter, []byte(contract.ProjectName)) if contract.HasStorage { props |= smartcontract.HasStorage } @@ -138,31 +127,21 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro if contract.IsPayable { props |= smartcontract.IsPayable } - if err := emit.Int(script, int64(props)); err != nil { - return nil, err - } - if err := emit.Int(script, int64(contract.ReturnType)); err != nil { - return nil, err - } + emit.Int(script.BinWriter, int64(props)) + emit.Int(script.BinWriter, int64(contract.ReturnType)) params := make([]byte, len(contract.Parameters)) for k := range contract.Parameters { params[k] = byte(contract.Parameters[k]) } - if err := emit.Bytes(script, params); err != nil { - return nil, err - } - if err := emit.Bytes(script, avm); err != nil { - return nil, err - } - if err := emit.Syscall(script, "Neo.Contract.Create"); err != nil { - return nil, err - } + emit.Bytes(script.BinWriter, params) + emit.Bytes(script.BinWriter, avm) + emit.Syscall(script.BinWriter, "Neo.Contract.Create") return script.Bytes(), nil } // expandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. -func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { +func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { for j := len(slice) - 1; j >= 0; j-- { fp, err := slice[j].GetFuncParam() if err != nil { @@ -174,33 +153,25 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := emit.Bytes(script, str); err != nil { - return err - } + emit.Bytes(script, str) case String: str, err := fp.Value.GetString() if err != nil { return err } - if err := emit.String(script, str); err != nil { - return err - } + emit.String(script, str) case Hash160: hash, err := fp.Value.GetUint160FromHex() if err != nil { return err } - if err := emit.Bytes(script, hash.BytesBE()); err != nil { - return err - } + emit.Bytes(script, hash.BytesBE()) case Hash256: hash, err := fp.Value.GetUint256() if err != nil { return err } - if err := emit.Bytes(script, hash.BytesBE()); err != nil { - return err - } + emit.Bytes(script, hash.BytesBE()) case PublicKey: str, err := fp.Value.GetString() if err != nil { @@ -210,17 +181,13 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { if err != nil { return err } - if err := emit.Bytes(script, key.Bytes()); err != nil { - return err - } + emit.Bytes(script, key.Bytes()) case Integer: val, err := fp.Value.GetInt() if err != nil { return err } - if err := emit.Int(script, int64(val)); err != nil { - return err - } + emit.Int(script, int64(val)) case Boolean: str, err := fp.Value.GetString() if err != nil { @@ -228,14 +195,11 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { } switch str { case "true": - err = emit.Int(script, 1) + emit.Int(script, 1) case "false": - err = emit.Int(script, 0) + emit.Int(script, 0) default: - err = errors.New("wrong boolean value") - } - if err != nil { - return err + return errors.New("wrong boolean value") } default: return fmt.Errorf("parameter type %v is not supported", fp.Type) @@ -247,44 +211,32 @@ func expandArrayIntoScript(script *bytes.Buffer, slice []Param) error { // CreateFunctionInvocationScript creates a script to invoke given contract with // given parameters. func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byte, error) { - script := new(bytes.Buffer) + script := io.NewBufBinWriter() for i := len(params) - 1; i >= 0; i-- { switch params[i].Type { case stringT: - if err := emit.String(script, params[i].String()); err != nil { - return nil, err - } + emit.String(script.BinWriter, params[i].String()) case numberT: num, err := params[i].GetInt() if err != nil { return nil, err } - if err := emit.String(script, strconv.Itoa(num)); err != nil { - return nil, err - } + emit.String(script.BinWriter, strconv.Itoa(num)) case arrayT: slice, err := params[i].GetArray() if err != nil { return nil, err } - err = expandArrayIntoScript(script, slice) - if err != nil { - return nil, err - } - err = emit.Int(script, int64(len(slice))) - if err != nil { - return nil, err - } - err = emit.Opcode(script, opcode.PACK) + err = expandArrayIntoScript(script.BinWriter, slice) if err != nil { return nil, err } + emit.Int(script.BinWriter, int64(len(slice))) + emit.Opcode(script.BinWriter, opcode.PACK) } } - if err := emit.AppCall(script, contract, false); err != nil { - return nil, err - } + emit.AppCall(script.BinWriter, contract, false) return script.Bytes(), nil } @@ -293,13 +245,11 @@ func CreateFunctionInvocationScript(contract util.Uint160, params Params) ([]byt // expects one array of FuncParams and expands it onto the stack as independent // elements. func CreateInvocationScript(contract util.Uint160, funcParams []Param) ([]byte, error) { - script := new(bytes.Buffer) - err := expandArrayIntoScript(script, funcParams) + script := io.NewBufBinWriter() + err := expandArrayIntoScript(script.BinWriter, funcParams) if err != nil { return nil, err } - if err = emit.AppCall(script, contract, false); err != nil { - return nil, err - } + emit.AppCall(script.BinWriter, contract, false) return script.Bytes(), nil } diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go index 73efdc0f7..297c0362a 100644 --- a/pkg/smartcontract/contract.go +++ b/pkg/smartcontract/contract.go @@ -1,11 +1,11 @@ package smartcontract import ( - "bytes" "fmt" "sort" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/vm/emit" "github.com/CityOfZion/neo-go/pkg/vm/opcode" ) @@ -22,22 +22,14 @@ func CreateMultiSigRedeemScript(m int, publicKeys keys.PublicKeys) ([]byte, erro return nil, fmt.Errorf("public key count %d exceeds maximum of length 1024", len(publicKeys)) } - buf := new(bytes.Buffer) - if err := emit.Int(buf, int64(m)); err != nil { - return nil, err - } + buf := io.NewBufBinWriter() + emit.Int(buf.BinWriter, int64(m)) sort.Sort(publicKeys) for _, pubKey := range publicKeys { - if err := emit.Bytes(buf, pubKey.Bytes()); err != nil { - return nil, err - } - } - if err := emit.Int(buf, int64(len(publicKeys))); err != nil { - return nil, err - } - if err := emit.Opcode(buf, opcode.CHECKMULTISIG); err != nil { - return nil, err + emit.Bytes(buf.BinWriter, pubKey.Bytes()) } + emit.Int(buf.BinWriter, int64(len(publicKeys))) + emit.Opcode(buf.BinWriter, opcode.CHECKMULTISIG) return buf.Bytes(), nil } diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 3fb9e99b3..c458d07f9 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -1,146 +1,135 @@ package emit import ( - "bytes" "encoding/binary" "errors" "fmt" - "io" "math/big" + "github.com/CityOfZion/neo-go/pkg/io" "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 +func Instruction(w *io.BinWriter, op opcode.Opcode, b []byte) { + w.WriteB(byte(op)) + w.WriteBytes(b) } // 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)) +func Opcode(w *io.BinWriter, op opcode.Opcode) { + w.WriteB(byte(op)) } // Bool emits a bool type the given buffer. -func Bool(w io.ByteWriter, ok bool) error { +func Bool(w *io.BinWriter, ok bool) { if ok { - return Opcode(w, opcode.PUSHT) + Opcode(w, opcode.PUSHT) + return } - return Opcode(w, opcode.PUSHF) + 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 { +func Int(w *io.BinWriter, i int64) { + switch { + case i == -1: + Opcode(w, opcode.PUSHM1) + case i == 0: + Opcode(w, opcode.PUSHF) + case i > 0 && i < 16: val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i)) - return Opcode(w, val) + Opcode(w, val) + default: + bInt := big.NewInt(i) + val := IntToBytes(bInt) + Bytes(w, val) } - - bInt := big.NewInt(i) - val := IntToBytes(bInt) - 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)) +func String(w *io.BinWriter, s string) { + 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) - ) +func Bytes(w *io.BinWriter, b []byte) { + var 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 { + switch { + case n <= int(opcode.PUSHBYTES75): + Instruction(w, opcode.Opcode(n), b) + return + case n < 0x100: + Instruction(w, opcode.PUSHDATA1, []byte{byte(n)}) + case n < 0x10000: buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(n)) - err = Instruction(w, opcode.PUSHDATA2, buf) - } else { + Instruction(w, opcode.PUSHDATA2, buf) + default: buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(n)) - err = Instruction(w, opcode.PUSHDATA4, buf) + Instruction(w, opcode.PUSHDATA4, buf) } - if err != nil { - return err - } - _, err = w.Write(b) - return err + w.WriteBytes(b) } // 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") +func Syscall(w *io.BinWriter, api string) { + if w.Err != nil { + return + } else if len(api) == 0 { + w.Err = errors.New("syscall api cannot be of length 0") + return } buf := make([]byte, len(api)+1) buf[0] = byte(len(api)) copy(buf[1:], api) - return Instruction(w, opcode.SYSCALL, buf) + 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) +func Call(w *io.BinWriter, op opcode.Opcode, label int16) { + 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()) +func Jmp(w *io.BinWriter, op opcode.Opcode, label int16) { + if w.Err != nil { + return + } else if !isInstructionJmp(op) { + w.Err = fmt.Errorf("opcode %s is not a jump or call type", op.String()) + return } buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(label)) - return Instruction(w, op, buf) + 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 { +func AppCall(w *io.BinWriter, scriptHash util.Uint160, tailCall bool) { op := opcode.APPCALL if tailCall { op = opcode.TAILCALL } - return Instruction(w, op, scriptHash.BytesBE()) + 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) +func AppCallWithOperationAndData(w *io.BinWriter, scriptHash util.Uint160, operation string, data []byte) { + Bytes(w, data) + String(w, operation) + 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 AppCallWithOperation(w *io.BinWriter, scriptHash util.Uint160, operation string) { + Bool(w, false) + String(w, operation) + AppCall(w, scriptHash, false) } func isInstructionJmp(op opcode.Opcode) bool { diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index f56b37226..50abbdb62 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -1,33 +1,33 @@ package emit import ( - "bytes" "encoding/binary" "testing" + "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" ) func TestEmitInt(t *testing.T) { t.Run("1-byte int", func(t *testing.T) { - buf := new(bytes.Buffer) - Int(buf, 10) + buf := io.NewBufBinWriter() + Int(buf.BinWriter, 10) result := buf.Bytes() assert.EqualValues(t, opcode.PUSH10, result[0]) }) t.Run("2-byte int", func(t *testing.T) { - buf := new(bytes.Buffer) - Int(buf, 100) + buf := io.NewBufBinWriter() + Int(buf.BinWriter, 100) result := buf.Bytes() assert.EqualValues(t, opcode.PUSHBYTES1, result[0]) assert.EqualValues(t, 100, result[1]) }) t.Run("4-byte int", func(t *testing.T) { - buf := new(bytes.Buffer) - Int(buf, 1000) + buf := io.NewBufBinWriter() + Int(buf.BinWriter, 1000) result := buf.Bytes() assert.EqualValues(t, opcode.PUSHBYTES2, result[0]) assert.EqualValues(t, 1000, binary.LittleEndian.Uint16(result[1:3])) @@ -35,18 +35,18 @@ func TestEmitInt(t *testing.T) { } func TestEmitBool(t *testing.T) { - buf := new(bytes.Buffer) - Bool(buf, true) - Bool(buf, false) + buf := io.NewBufBinWriter() + Bool(buf.BinWriter, true) + Bool(buf.BinWriter, false) result := buf.Bytes() assert.Equal(t, opcode.Opcode(result[0]), opcode.PUSH1) assert.Equal(t, opcode.Opcode(result[1]), opcode.PUSH0) } func TestEmitString(t *testing.T) { - buf := new(bytes.Buffer) + buf := io.NewBufBinWriter() str := "City Of Zion" - String(buf, str) + String(buf.BinWriter, str) assert.Equal(t, buf.Len(), len(str)+1) assert.Equal(t, buf.Bytes()[1:], []byte(str)) } @@ -58,9 +58,9 @@ func TestEmitSyscall(t *testing.T) { "Neo.Runtime.Whatever", } - buf := new(bytes.Buffer) + buf := io.NewBufBinWriter() for _, syscall := range syscalls { - Syscall(buf, syscall) + Syscall(buf.BinWriter, syscall) result := buf.Bytes() assert.Equal(t, opcode.Opcode(result[0]), opcode.SYSCALL) assert.Equal(t, result[1], uint8(len(syscall))) @@ -70,8 +70,8 @@ func TestEmitSyscall(t *testing.T) { } func TestEmitCall(t *testing.T) { - buf := new(bytes.Buffer) - Call(buf, opcode.JMP, 100) + buf := io.NewBufBinWriter() + Call(buf.BinWriter, opcode.JMP, 100) result := buf.Bytes() assert.Equal(t, opcode.Opcode(result[0]), opcode.JMP) label := binary.LittleEndian.Uint16(result[1:3]) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 222cace89..6cc7547c4 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -8,6 +8,8 @@ import ( "math/rand" "testing" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" @@ -31,9 +33,9 @@ func TestInteropHook(t *testing.T) { v := New() v.RegisterInteropGetter(fooInteropGetter) - buf := new(bytes.Buffer) - emit.Syscall(buf, "foo") - emit.Opcode(buf, opcode.RET) + buf := io.NewBufBinWriter() + emit.Syscall(buf.BinWriter, "foo") + emit.Opcode(buf.BinWriter, opcode.RET) v.Load(buf.Bytes()) runVM(t, v) assert.Equal(t, 1, v.estack.Len()) @@ -44,12 +46,12 @@ func TestInteropHookViaID(t *testing.T) { v := New() v.RegisterInteropGetter(fooInteropGetter) - buf := new(bytes.Buffer) + buf := io.NewBufBinWriter() fooid := InteropNameToID([]byte("foo")) var id = make([]byte, 4) binary.LittleEndian.PutUint32(id, fooid) - _ = emit.Syscall(buf, string(id)) - _ = emit.Opcode(buf, opcode.RET) + emit.Syscall(buf.BinWriter, string(id)) + emit.Opcode(buf.BinWriter, opcode.RET) v.Load(buf.Bytes()) runVM(t, v) assert.Equal(t, 1, v.estack.Len()) @@ -131,10 +133,10 @@ func TestBytesToPublicKey(t *testing.T) { } func TestPushBytes1to75(t *testing.T) { - buf := new(bytes.Buffer) + buf := io.NewBufBinWriter() for i := 1; i <= 75; i++ { b := randomBytes(i) - emit.Bytes(buf, b) + emit.Bytes(buf.BinWriter, b) vm := load(buf.Bytes()) err := vm.Step() require.NoError(t, err) From dbc41b3044b79db5786d372dceeacd7ffdc84dc2 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 17:58:21 +0300 Subject: [PATCH 6/9] compiler: replace emit* instructions with those from emit/ package --- pkg/compiler/codegen.go | 223 ++++++++++++++++++++-------------------- pkg/compiler/emit.go | 119 --------------------- 2 files changed, 112 insertions(+), 230 deletions(-) delete mode 100644 pkg/compiler/emit.go diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index f6a02fd0f..1da9dcd78 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -14,6 +14,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/vm/emit" "github.com/CityOfZion/neo-go/pkg/vm/opcode" ) @@ -73,17 +74,17 @@ func (c *codegen) convertBasicType(t types.TypeAndValue, typ *types.Basic) { switch typ.Kind() { case types.Int, types.UntypedInt, types.Uint: val, _ := constant.Int64Val(t.Value) - emitInt(c.prog.BinWriter, val) + emit.Int(c.prog.BinWriter, val) case types.String, types.UntypedString: val := constant.StringVal(t.Value) - emitString(c.prog.BinWriter, val) + emit.String(c.prog.BinWriter, val) case types.Bool, types.UntypedBool: val := constant.BoolVal(t.Value) - emitBool(c.prog.BinWriter, val) + emit.Bool(c.prog.BinWriter, val) case types.Byte: val, _ := constant.Int64Val(t.Value) b := byte(val) - emitBytes(c.prog.BinWriter, []byte{b}) + emit.Bytes(c.prog.BinWriter, []byte{b}) default: c.prog.Err = fmt.Errorf("compiler doesn't know how to convert this basic type: %v", t) return @@ -100,33 +101,33 @@ func (c *codegen) emitLoadLocal(name string) { } func (c *codegen) emitLoadLocalPos(pos int) { - emitOpcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK) - emitInt(c.prog.BinWriter, int64(pos)) - emitOpcode(c.prog.BinWriter, opcode.PICKITEM) + emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK) + emit.Int(c.prog.BinWriter, int64(pos)) + emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) } func (c *codegen) emitStoreLocal(pos int) { - emitOpcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK) + emit.Opcode(c.prog.BinWriter, opcode.DUPFROMALTSTACK) if pos < 0 { c.prog.Err = fmt.Errorf("invalid position to store local: %d", pos) return } - emitInt(c.prog.BinWriter, int64(pos)) - emitOpcode(c.prog.BinWriter, opcode.ROT) - emitOpcode(c.prog.BinWriter, opcode.SETITEM) + emit.Int(c.prog.BinWriter, int64(pos)) + emit.Opcode(c.prog.BinWriter, opcode.ROT) + emit.Opcode(c.prog.BinWriter, opcode.SETITEM) } func (c *codegen) emitLoadField(i int) { - emitInt(c.prog.BinWriter, int64(i)) - emitOpcode(c.prog.BinWriter, opcode.PICKITEM) + emit.Int(c.prog.BinWriter, int64(i)) + emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) } func (c *codegen) emitStoreStructField(i int) { - emitInt(c.prog.BinWriter, int64(i)) - emitOpcode(c.prog.BinWriter, opcode.ROT) - emitOpcode(c.prog.BinWriter, opcode.SETITEM) + emit.Int(c.prog.BinWriter, int64(i)) + emit.Opcode(c.prog.BinWriter, opcode.ROT) + emit.Opcode(c.prog.BinWriter, opcode.SETITEM) } // convertGlobals traverses the AST and only converts global declarations. @@ -170,9 +171,9 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { // All globals copied into the scope of the function need to be added // to the stack size of the function. - emitInt(c.prog.BinWriter, f.stackSize()+countGlobals(file)) - emitOpcode(c.prog.BinWriter, opcode.NEWARRAY) - emitOpcode(c.prog.BinWriter, opcode.TOALTSTACK) + emit.Int(c.prog.BinWriter, f.stackSize()+countGlobals(file)) + emit.Opcode(c.prog.BinWriter, opcode.NEWARRAY) + emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK) // We need to handle methods, which in Go, is just syntactic sugar. // The method receiver will be passed in as first argument. @@ -210,9 +211,9 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { // If this function returns the void (no return stmt) we will cleanup its junk on the stack. if !hasReturnStmt(decl) { - emitOpcode(c.prog.BinWriter, opcode.FROMALTSTACK) - emitOpcode(c.prog.BinWriter, opcode.DROP) - emitOpcode(c.prog.BinWriter, opcode.RET) + emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK) + emit.Opcode(c.prog.BinWriter, opcode.DROP) + emit.Opcode(c.prog.BinWriter, opcode.RET) } } @@ -258,7 +259,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } if t.Name == "_" { - emitOpcode(c.prog.BinWriter, opcode.DROP) + emit.Opcode(c.prog.BinWriter, opcode.DROP) } else { l := c.scope.loadLocal(t.Name) c.emitStoreLocal(l) @@ -297,8 +298,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emitStoreStructField(index) case *ast.Ident: c.emitLoadLocal(ind.Name) - emitOpcode(c.prog.BinWriter, opcode.ROT) - emitOpcode(c.prog.BinWriter, opcode.SETITEM) + emit.Opcode(c.prog.BinWriter, opcode.ROT) + emit.Opcode(c.prog.BinWriter, opcode.SETITEM) default: c.prog.Err = fmt.Errorf("unsupported index expression") return nil @@ -316,9 +317,9 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Results[i]) } - emitOpcode(c.prog.BinWriter, opcode.FROMALTSTACK) - emitOpcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack. - emitOpcode(c.prog.BinWriter, opcode.RET) + emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK) + emit.Opcode(c.prog.BinWriter, opcode.DROP) // Cleanup the stack. + emit.Opcode(c.prog.BinWriter, opcode.RET) return nil case *ast.IfStmt: @@ -328,13 +329,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if n.Cond != nil { ast.Walk(c, n.Cond) - emitJmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(lElse)) + emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(lElse)) } c.setLabel(lIf) ast.Walk(c, n.Body) if n.Else != nil { - emitJmp(c.prog.BinWriter, opcode.JMP, int16(lElseEnd)) + emit.Jmp(c.prog.BinWriter, opcode.JMP, int16(lElseEnd)) } c.setLabel(lElse) @@ -358,13 +359,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { if l := len(cc.List); l != 0 { // if not `default` for j := range cc.List { - emitOpcode(c.prog.BinWriter, opcode.DUP) + emit.Opcode(c.prog.BinWriter, opcode.DUP) ast.Walk(c, cc.List[j]) - emitOpcode(c.prog.BinWriter, eqOpcode) + emit.Opcode(c.prog.BinWriter, eqOpcode) if j == l-1 { - emitJmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(lEnd)) + emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(lEnd)) } else { - emitJmp(c.prog.BinWriter, opcode.JMPIF, int16(lStart)) + emit.Jmp(c.prog.BinWriter, opcode.JMPIF, int16(lStart)) } } } @@ -373,12 +374,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for _, stmt := range cc.Body { ast.Walk(c, stmt) } - emitJmp(c.prog.BinWriter, opcode.JMP, int16(switchEnd)) + emit.Jmp(c.prog.BinWriter, opcode.JMP, int16(switchEnd)) c.setLabel(lEnd) } c.setLabel(switchEnd) - emitOpcode(c.prog.BinWriter, opcode.DROP) + emit.Opcode(c.prog.BinWriter, opcode.DROP) return nil @@ -421,8 +422,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { for i := ln - 1; i >= 0; i-- { ast.Walk(c, n.Elts[i]) } - emitInt(c.prog.BinWriter, int64(ln)) - emitOpcode(c.prog.BinWriter, opcode.PACK) + emit.Int(c.prog.BinWriter, int64(ln)) + emit.Opcode(c.prog.BinWriter, opcode.PACK) return nil } @@ -439,13 +440,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch n.Op { case token.LAND: ast.Walk(c, n.X) - emitJmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(len(c.l)-1)) + emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(len(c.l)-1)) ast.Walk(c, n.Y) return nil case token.LOR: ast.Walk(c, n.X) - emitJmp(c.prog.BinWriter, opcode.JMPIF, int16(len(c.l)-3)) + emit.Jmp(c.prog.BinWriter, opcode.JMPIF, int16(len(c.l)-3)) ast.Walk(c, n.Y) return nil @@ -470,21 +471,21 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case n.Op == token.ADD: // VM has separate opcodes for number and string concatenation if isStringType(tinfo.Type) { - emitOpcode(c.prog.BinWriter, opcode.CAT) + emit.Opcode(c.prog.BinWriter, opcode.CAT) } else { - emitOpcode(c.prog.BinWriter, opcode.ADD) + emit.Opcode(c.prog.BinWriter, opcode.ADD) } case n.Op == token.EQL: // VM has separate opcodes for number and string equality op := c.getEqualityOpcode(n.X) - emitOpcode(c.prog.BinWriter, op) + emit.Opcode(c.prog.BinWriter, op) case n.Op == token.NEQ: // VM has separate opcodes for number and string equality if isStringType(c.typeInfo.Types[n.X].Type) { - emitOpcode(c.prog.BinWriter, opcode.EQUAL) - emitOpcode(c.prog.BinWriter, opcode.NOT) + emit.Opcode(c.prog.BinWriter, opcode.EQUAL) + emit.Opcode(c.prog.BinWriter, opcode.NOT) } else { - emitOpcode(c.prog.BinWriter, opcode.NUMNOTEQUAL) + emit.Opcode(c.prog.BinWriter, opcode.NUMNOTEQUAL) } default: c.convertToken(n.Op) @@ -540,14 +541,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // Do not swap for builtin functions. if !isBuiltin { if numArgs == 2 { - emitOpcode(c.prog.BinWriter, opcode.SWAP) + emit.Opcode(c.prog.BinWriter, opcode.SWAP) } else if numArgs == 3 { - emitInt(c.prog.BinWriter, 2) - emitOpcode(c.prog.BinWriter, opcode.XSWAP) + emit.Int(c.prog.BinWriter, 2) + emit.Opcode(c.prog.BinWriter, opcode.XSWAP) } else { for i := 1; i < numArgs; i++ { - emitInt(c.prog.BinWriter, int64(i)) - emitOpcode(c.prog.BinWriter, opcode.ROLL) + emit.Int(c.prog.BinWriter, int64(i)) + emit.Opcode(c.prog.BinWriter, opcode.ROLL) } } } @@ -561,7 +562,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case isSyscall(f): c.convertSyscall(f.selector.Name, f.name) default: - emitCall(c.prog.BinWriter, opcode.CALL, int16(f.label)) + emit.Call(c.prog.BinWriter, opcode.CALL, int16(f.label)) } return nil @@ -591,11 +592,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case token.ADD: // +10 == 10, no need to do anything in this case case token.SUB: - emitOpcode(c.prog.BinWriter, opcode.NEGATE) + emit.Opcode(c.prog.BinWriter, opcode.NEGATE) case token.NOT: - emitOpcode(c.prog.BinWriter, opcode.NOT) + emit.Opcode(c.prog.BinWriter, opcode.NOT) case token.XOR: - emitOpcode(c.prog.BinWriter, opcode.INVERT) + emit.Opcode(c.prog.BinWriter, opcode.INVERT) default: c.prog.Err = fmt.Errorf("invalid unary operator: %s", n.Op) return nil @@ -634,7 +635,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Index) } - emitOpcode(c.prog.BinWriter, opcode.PICKITEM) // just pickitem here + emit.Opcode(c.prog.BinWriter, opcode.PICKITEM) // just pickitem here return nil @@ -654,7 +655,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Cond) // Jump if the condition is false - emitJmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(fend)) + emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, int16(fend)) // Walk body followed by the iterator (post stmt). ast.Walk(c, n.Body) @@ -663,7 +664,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { } // Jump back to condition. - emitJmp(c.prog.BinWriter, opcode.JMP, int16(fstart)) + emit.Jmp(c.prog.BinWriter, opcode.JMP, int16(fstart)) c.setLabel(fend) return nil @@ -721,11 +722,11 @@ func (c *codegen) convertSyscall(api, name string) { c.prog.Err = fmt.Errorf("unknown VM syscall api: %s", name) return } - emitSyscall(c.prog.BinWriter, api) + emit.Syscall(c.prog.BinWriter, api) // This NOP instruction is basically not needed, but if we do, we have a // one to one matching avm file with neo-python which is very nice for debugging. - emitOpcode(c.prog.BinWriter, opcode.NOP) + emit.Opcode(c.prog.BinWriter, opcode.NOP) } func (c *codegen) convertBuiltin(expr *ast.CallExpr) { @@ -742,44 +743,44 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { arg := expr.Args[0] typ := c.typeInfo.Types[arg].Type if isStringType(typ) { - emitOpcode(c.prog.BinWriter, opcode.SIZE) + emit.Opcode(c.prog.BinWriter, opcode.SIZE) } else { - emitOpcode(c.prog.BinWriter, opcode.ARRAYSIZE) + emit.Opcode(c.prog.BinWriter, opcode.ARRAYSIZE) } case "append": arg := expr.Args[0] typ := c.typeInfo.Types[arg].Type if isByteArrayType(typ) { - emitOpcode(c.prog.BinWriter, opcode.CAT) + emit.Opcode(c.prog.BinWriter, opcode.CAT) } else { - emitOpcode(c.prog.BinWriter, opcode.OVER) - emitOpcode(c.prog.BinWriter, opcode.SWAP) - emitOpcode(c.prog.BinWriter, opcode.APPEND) + emit.Opcode(c.prog.BinWriter, opcode.OVER) + emit.Opcode(c.prog.BinWriter, opcode.SWAP) + emit.Opcode(c.prog.BinWriter, opcode.APPEND) } case "panic": arg := expr.Args[0] if isExprNil(arg) { - emitOpcode(c.prog.BinWriter, opcode.DROP) - emitOpcode(c.prog.BinWriter, opcode.THROW) + emit.Opcode(c.prog.BinWriter, opcode.DROP) + emit.Opcode(c.prog.BinWriter, opcode.THROW) } else if isStringType(c.typeInfo.Types[arg].Type) { ast.Walk(c, arg) - emitSyscall(c.prog.BinWriter, "Neo.Runtime.Log") - emitOpcode(c.prog.BinWriter, opcode.THROW) + emit.Syscall(c.prog.BinWriter, "Neo.Runtime.Log") + emit.Opcode(c.prog.BinWriter, opcode.THROW) } else { c.prog.Err = errors.New("panic should have string or nil argument") } case "SHA256": - emitOpcode(c.prog.BinWriter, opcode.SHA256) + emit.Opcode(c.prog.BinWriter, opcode.SHA256) case "SHA1": - emitOpcode(c.prog.BinWriter, opcode.SHA1) + emit.Opcode(c.prog.BinWriter, opcode.SHA1) case "Hash256": - emitOpcode(c.prog.BinWriter, opcode.HASH256) + emit.Opcode(c.prog.BinWriter, opcode.HASH256) case "Hash160": - emitOpcode(c.prog.BinWriter, opcode.HASH160) + emit.Opcode(c.prog.BinWriter, opcode.HASH160) case "VerifySignature": - emitOpcode(c.prog.BinWriter, opcode.VERIFY) + emit.Opcode(c.prog.BinWriter, opcode.VERIFY) case "AppCall": - emitOpcode(c.prog.BinWriter, opcode.APPCALL) + emit.Opcode(c.prog.BinWriter, opcode.APPCALL) buf := c.getByteArray(expr.Args[0]) if len(buf) != 20 { c.prog.Err = errors.New("invalid script hash") @@ -787,7 +788,7 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { c.prog.WriteBytes(buf) case "Equals": - emitOpcode(c.prog.BinWriter, opcode.EQUAL) + emit.Opcode(c.prog.BinWriter, opcode.EQUAL) case "FromAddress": // We can be sure that this is a ast.BasicLit just containing a simple // address string. Note that the string returned from calling Value will @@ -800,7 +801,7 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { return } bytes := uint160.BytesBE() - emitBytes(c.prog.BinWriter, bytes) + emit.Bytes(c.prog.BinWriter, bytes) } } @@ -835,17 +836,17 @@ func (c *codegen) convertByteArray(lit *ast.CompositeLit) { val, _ := constant.Int64Val(t.Value) buf[i] = byte(val) } - emitBytes(c.prog.BinWriter, buf) + emit.Bytes(c.prog.BinWriter, buf) } func (c *codegen) convertMap(lit *ast.CompositeLit) { - emitOpcode(c.prog.BinWriter, opcode.NEWMAP) + emit.Opcode(c.prog.BinWriter, opcode.NEWMAP) for i := range lit.Elts { elem := lit.Elts[i].(*ast.KeyValueExpr) - emitOpcode(c.prog.BinWriter, opcode.DUP) + emit.Opcode(c.prog.BinWriter, opcode.DUP) ast.Walk(c, elem.Key) ast.Walk(c, elem.Value) - emitOpcode(c.prog.BinWriter, opcode.SETITEM) + emit.Opcode(c.prog.BinWriter, opcode.SETITEM) } } @@ -858,10 +859,10 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { return } - emitOpcode(c.prog.BinWriter, opcode.NOP) - emitInt(c.prog.BinWriter, int64(strct.NumFields())) - emitOpcode(c.prog.BinWriter, opcode.NEWSTRUCT) - emitOpcode(c.prog.BinWriter, opcode.TOALTSTACK) + emit.Opcode(c.prog.BinWriter, opcode.NOP) + emit.Int(c.prog.BinWriter, int64(strct.NumFields())) + emit.Opcode(c.prog.BinWriter, opcode.NEWSTRUCT) + emit.Opcode(c.prog.BinWriter, opcode.TOALTSTACK) // We need to locally store all the fields, even if they are not initialized. // We will initialize all fields to their "zero" value. @@ -894,59 +895,59 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { c.emitLoadConst(typeAndVal) c.emitStoreLocal(i) } - emitOpcode(c.prog.BinWriter, opcode.FROMALTSTACK) + emit.Opcode(c.prog.BinWriter, opcode.FROMALTSTACK) } func (c *codegen) convertToken(tok token.Token) { switch tok { case token.ADD_ASSIGN: - emitOpcode(c.prog.BinWriter, opcode.ADD) + emit.Opcode(c.prog.BinWriter, opcode.ADD) case token.SUB_ASSIGN: - emitOpcode(c.prog.BinWriter, opcode.SUB) + emit.Opcode(c.prog.BinWriter, opcode.SUB) case token.MUL_ASSIGN: - emitOpcode(c.prog.BinWriter, opcode.MUL) + emit.Opcode(c.prog.BinWriter, opcode.MUL) case token.QUO_ASSIGN: - emitOpcode(c.prog.BinWriter, opcode.DIV) + emit.Opcode(c.prog.BinWriter, opcode.DIV) case token.REM_ASSIGN: - emitOpcode(c.prog.BinWriter, opcode.MOD) + emit.Opcode(c.prog.BinWriter, opcode.MOD) case token.ADD: - emitOpcode(c.prog.BinWriter, opcode.ADD) + emit.Opcode(c.prog.BinWriter, opcode.ADD) case token.SUB: - emitOpcode(c.prog.BinWriter, opcode.SUB) + emit.Opcode(c.prog.BinWriter, opcode.SUB) case token.MUL: - emitOpcode(c.prog.BinWriter, opcode.MUL) + emit.Opcode(c.prog.BinWriter, opcode.MUL) case token.QUO: - emitOpcode(c.prog.BinWriter, opcode.DIV) + emit.Opcode(c.prog.BinWriter, opcode.DIV) case token.REM: - emitOpcode(c.prog.BinWriter, opcode.MOD) + emit.Opcode(c.prog.BinWriter, opcode.MOD) case token.LSS: - emitOpcode(c.prog.BinWriter, opcode.LT) + emit.Opcode(c.prog.BinWriter, opcode.LT) case token.LEQ: - emitOpcode(c.prog.BinWriter, opcode.LTE) + emit.Opcode(c.prog.BinWriter, opcode.LTE) case token.GTR: - emitOpcode(c.prog.BinWriter, opcode.GT) + emit.Opcode(c.prog.BinWriter, opcode.GT) case token.GEQ: - emitOpcode(c.prog.BinWriter, opcode.GTE) + emit.Opcode(c.prog.BinWriter, opcode.GTE) case token.EQL: - emitOpcode(c.prog.BinWriter, opcode.NUMEQUAL) + emit.Opcode(c.prog.BinWriter, opcode.NUMEQUAL) case token.NEQ: - emitOpcode(c.prog.BinWriter, opcode.NUMNOTEQUAL) + emit.Opcode(c.prog.BinWriter, opcode.NUMNOTEQUAL) case token.DEC: - emitOpcode(c.prog.BinWriter, opcode.DEC) + emit.Opcode(c.prog.BinWriter, opcode.DEC) case token.INC: - emitOpcode(c.prog.BinWriter, opcode.INC) + emit.Opcode(c.prog.BinWriter, opcode.INC) case token.NOT: - emitOpcode(c.prog.BinWriter, opcode.NOT) + emit.Opcode(c.prog.BinWriter, opcode.NOT) case token.AND: - emitOpcode(c.prog.BinWriter, opcode.AND) + emit.Opcode(c.prog.BinWriter, opcode.AND) case token.OR: - emitOpcode(c.prog.BinWriter, opcode.OR) + emit.Opcode(c.prog.BinWriter, opcode.OR) case token.SHL: - emitOpcode(c.prog.BinWriter, opcode.SHL) + emit.Opcode(c.prog.BinWriter, opcode.SHL) case token.SHR: - emitOpcode(c.prog.BinWriter, opcode.SHR) + emit.Opcode(c.prog.BinWriter, opcode.SHR) case token.XOR: - emitOpcode(c.prog.BinWriter, opcode.XOR) + emit.Opcode(c.prog.BinWriter, opcode.XOR) default: c.prog.Err = fmt.Errorf("compiler could not convert token: %s", tok) return diff --git a/pkg/compiler/emit.go b/pkg/compiler/emit.go deleted file mode 100644 index c27a06701..000000000 --- a/pkg/compiler/emit.go +++ /dev/null @@ -1,119 +0,0 @@ -package compiler - -import ( - "encoding/binary" - "errors" - "fmt" - "math/big" - - "github.com/CityOfZion/neo-go/pkg/io" - "github.com/CityOfZion/neo-go/pkg/vm" - "github.com/CityOfZion/neo-go/pkg/vm/opcode" -) - -// emit a VM Instruction with data to the given buffer. -func emit(w *io.BinWriter, instr opcode.Opcode, b []byte) { - emitOpcode(w, instr) - w.WriteBytes(b) -} - -// emitOpcode emits a single VM Instruction the given buffer. -func emitOpcode(w *io.BinWriter, instr opcode.Opcode) { - w.WriteBytes([]byte{byte(instr)}) -} - -// emitBool emits a bool type the given buffer. -func emitBool(w *io.BinWriter, ok bool) { - if ok { - emitOpcode(w, opcode.PUSHT) - return - } - emitOpcode(w, opcode.PUSHF) -} - -// emitInt emits a int type to the given buffer. -func emitInt(w *io.BinWriter, i int64) { - switch { - case i == -1: - emitOpcode(w, opcode.PUSHM1) - return - case i == 0: - emitOpcode(w, opcode.PUSHF) - return - case i > 0 && i < 16: - val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i)) - emitOpcode(w, val) - return - } - - bInt := big.NewInt(i) - val := vm.IntToBytes(bInt) - emitBytes(w, val) -} - -// emitString emits a string to the given buffer. -func emitString(w *io.BinWriter, s string) { - emitBytes(w, []byte(s)) -} - -// emitBytes emits a byte array to the given buffer. -func emitBytes(w *io.BinWriter, b []byte) { - n := len(b) - - switch { - case n <= int(opcode.PUSHBYTES75): - emit(w, opcode.Opcode(n), b) - return - case n < 0x100: - emit(w, opcode.PUSHDATA1, []byte{byte(n)}) - case n < 0x10000: - buf := make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(n)) - emit(w, opcode.PUSHDATA2, buf) - default: - buf := make([]byte, 4) - binary.LittleEndian.PutUint32(buf, uint32(n)) - emit(w, opcode.PUSHDATA4, buf) - if w.Err != nil { - return - } - } - - w.WriteBytes(b) -} - -// emitSyscall emits the syscall API to the given buffer. -// Syscall API string cannot be 0. -func emitSyscall(w *io.BinWriter, api string) { - if len(api) == 0 { - w.Err = errors.New("syscall api cannot be of length 0") - return - } - buf := make([]byte, len(api)+1) - buf[0] = byte(len(api)) - copy(buf[1:], api) - emit(w, opcode.SYSCALL, buf) -} - -// emitCall emits a call Instruction with label to the given buffer. -func emitCall(w *io.BinWriter, instr opcode.Opcode, label int16) { - emitJmp(w, instr, label) -} - -// emitJmp emits a jump Instruction along with label to the given buffer. -func emitJmp(w *io.BinWriter, instr opcode.Opcode, label int16) { - if !isInstrJmp(instr) { - w.Err = fmt.Errorf("opcode %s is not a jump or call type", instr) - return - } - buf := make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(label)) - emit(w, instr, buf) -} - -func isInstrJmp(instr opcode.Opcode) bool { - if instr == opcode.JMP || instr == opcode.JMPIFNOT || instr == opcode.JMPIF || instr == opcode.CALL { - return true - } - return false -} From 5e992d8cdd54355ab4c1f268a270b57638ce0f70 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Feb 2020 10:13:43 +0300 Subject: [PATCH 7/9] emit: add tests for Syscall, Jmp --- pkg/vm/emit/emit_test.go | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index 50abbdb62..f25dade21 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -2,6 +2,7 @@ package emit import ( "encoding/binary" + "errors" "testing" "github.com/CityOfZion/neo-go/pkg/io" @@ -67,6 +68,55 @@ func TestEmitSyscall(t *testing.T) { assert.Equal(t, result[2:], []byte(syscall)) buf.Reset() } + + t.Run("empty syscall", func(t *testing.T) { + buf := io.NewBufBinWriter() + Syscall(buf.BinWriter, "") + assert.Error(t, buf.Err) + }) + + t.Run("empty syscall after error", func(t *testing.T) { + buf := io.NewBufBinWriter() + err := errors.New("first error") + + buf.Err = err + Syscall(buf.BinWriter, "") + assert.Equal(t, err, buf.Err) + }) +} + +func TestJmp(t *testing.T) { + const label = 0x23 + + t.Run("correct", func(t *testing.T) { + ops := []opcode.Opcode{opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL} + for i := range ops { + t.Run(ops[i].String(), func(t *testing.T) { + buf := io.NewBufBinWriter() + Jmp(buf.BinWriter, ops[i], label) + assert.NoError(t, buf.Err) + + result := buf.Bytes() + assert.EqualValues(t, ops[i], result[0]) + assert.EqualValues(t, 0x23, binary.LittleEndian.Uint16(result[1:])) + }) + } + }) + + t.Run("not a jump instruction", func(t *testing.T) { + buf := io.NewBufBinWriter() + Jmp(buf.BinWriter, opcode.ABS, label) + assert.Error(t, buf.Err) + }) + + t.Run("not a jump after error", func(t *testing.T) { + buf := io.NewBufBinWriter() + err := errors.New("first error") + + buf.Err = err + Jmp(buf.BinWriter, opcode.ABS, label) + assert.Error(t, buf.Err) + }) } func TestEmitCall(t *testing.T) { From 6fd3f0fa4898e9f9dc47e50fbb4080a41e6f3a89 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Feb 2020 10:21:31 +0300 Subject: [PATCH 8/9] emit: add tests for Bytes --- pkg/vm/emit/emit_test.go | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index f25dade21..bbb83fc72 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -35,6 +35,62 @@ func TestEmitInt(t *testing.T) { }) } +func getSlice(n int) []byte { + data := make([]byte, n) + for i := range data { + data[i] = byte(i) + } + + return data +} + +func TestBytes(t *testing.T) { + t.Run("small slice", func(t *testing.T) { + buf := io.NewBufBinWriter() + Bytes(buf.BinWriter, []byte{0, 1, 2, 3}) + + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSHBYTES4, result[0]) + assert.EqualValues(t, []byte{0, 1, 2, 3}, result[1:]) + }) + + t.Run("slice with len <= 255", func(t *testing.T) { + const size = 200 + + buf := io.NewBufBinWriter() + Bytes(buf.BinWriter, getSlice(size)) + + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSHDATA1, result[0]) + assert.EqualValues(t, size, result[1]) + assert.Equal(t, getSlice(size), result[2:]) + }) + + t.Run("slice with len <= 65535", func(t *testing.T) { + const size = 60000 + + buf := io.NewBufBinWriter() + Bytes(buf.BinWriter, getSlice(size)) + + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSHDATA2, result[0]) + assert.EqualValues(t, size, binary.LittleEndian.Uint16(result[1:3])) + assert.Equal(t, getSlice(size), result[3:]) + }) + + t.Run("slice with len > 65535", func(t *testing.T) { + const size = 100000 + + buf := io.NewBufBinWriter() + Bytes(buf.BinWriter, getSlice(size)) + + result := buf.Bytes() + assert.EqualValues(t, opcode.PUSHDATA4, result[0]) + assert.EqualValues(t, size, binary.LittleEndian.Uint32(result[1:5])) + assert.Equal(t, getSlice(size), result[5:]) + }) +} + func TestEmitBool(t *testing.T) { buf := io.NewBufBinWriter() Bool(buf.BinWriter, true) From a8dc7041978a5682fc0504a8728237b6d50706df Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 5 Feb 2020 10:29:43 +0300 Subject: [PATCH 9/9] emit: add tests for Int --- pkg/vm/emit/emit_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index bbb83fc72..ed6c94cd5 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -11,6 +11,22 @@ import ( ) func TestEmitInt(t *testing.T) { + t.Run("minis one", func(t *testing.T) { + buf := io.NewBufBinWriter() + Int(buf.BinWriter, -1) + result := buf.Bytes() + assert.Len(t, result, 1) + assert.EqualValues(t, opcode.PUSHM1, result[0]) + }) + + t.Run("zero", func(t *testing.T) { + buf := io.NewBufBinWriter() + Int(buf.BinWriter, 0) + result := buf.Bytes() + assert.Len(t, result, 1) + assert.EqualValues(t, opcode.PUSH0, result[0]) + }) + t.Run("1-byte int", func(t *testing.T) { buf := io.NewBufBinWriter() Int(buf.BinWriter, 10)