From 8243a8b3a7686b280a93590e5c11046ca6889a69 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 3 Feb 2020 17:46:51 +0300 Subject: [PATCH] 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)