From db2dccf7cbd7489f5a4ac90e76441b4fef8198c1 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 27 Mar 2020 11:00:06 +0300 Subject: [PATCH] emit: implement AppCallWithOperationAndArgs It is nice to have a typical task of calling contract method with specific arguments incapsulated inside some function. --- pkg/core/helper_test.go | 20 +++----------------- pkg/rpc/client/nep5.go | 8 +------- pkg/vm/emit/emit.go | 30 ++++++++++++++++++++++++++++++ pkg/vm/emit/emit_test.go | 30 ++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 763ffb6eb..c44b008e0 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -318,12 +318,7 @@ func TestCreateBasicChain(t *testing.T) { // Now invoke this contract. script = io.NewBufBinWriter() - emit.String(script.BinWriter, "testvalue") - emit.String(script.BinWriter, "testkey") - emit.Int(script.BinWriter, 2) - emit.Opcode(script.BinWriter, opcode.PACK) - emit.String(script.BinWriter, "Put") - emit.AppCall(script.BinWriter, hash.Hash160(avm), false) + emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "Put", "testkey", "testvalue") txInv := transaction.NewInvocationTX(script.Bytes(), 0) b = bc.newBlock(newMinerTX(), txInv) @@ -355,10 +350,7 @@ func TestCreateBasicChain(t *testing.T) { sh := hash.Hash160(avm) w := io.NewBufBinWriter() - emit.Int(w.BinWriter, 0) - emit.Opcode(w.BinWriter, opcode.NEWARRAY) - emit.String(w.BinWriter, "init") - emit.AppCall(w.BinWriter, sh, true) + emit.AppCallWithOperationAndArgs(w.BinWriter, sh, "init") initTx := transaction.NewInvocationTX(w.Bytes(), 0) transferTx := newNEP5Transfer(sh, sh, priv0.GetScriptHash(), 1000) @@ -406,13 +398,7 @@ func TestCreateBasicChain(t *testing.T) { func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Transaction { w := io.NewBufBinWriter() - emit.Int(w.BinWriter, amount) - emit.Bytes(w.BinWriter, to.BytesBE()) - emit.Bytes(w.BinWriter, from.BytesBE()) - emit.Int(w.BinWriter, 3) - emit.Opcode(w.BinWriter, opcode.PACK) - emit.String(w.BinWriter, "transfer") - emit.AppCall(w.BinWriter, sc, false) + emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", from, to, amount) emit.Opcode(w.BinWriter, opcode.THROWIFNOT) script := w.Bytes() diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 5505a9aad..fd38e399c 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -104,13 +104,7 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle // Note: we don't use invoke function here because it requires // 2 round trips instead of one. w := io.NewBufBinWriter() - emit.Int(w.BinWriter, amount) - emit.Bytes(w.BinWriter, to.BytesBE()) - emit.Bytes(w.BinWriter, from.BytesBE()) - emit.Int(w.BinWriter, 3) - emit.Opcode(w.BinWriter, opcode.PACK) - emit.String(w.BinWriter, "transfer") - emit.AppCall(w.BinWriter, token.Hash, false) + emit.AppCallWithOperationAndArgs(w.BinWriter, token.Hash, "transfer", from, to, amount) emit.Opcode(w.BinWriter, opcode.THROWIFNOT) tx := transaction.NewInvocationTX(w.Bytes(), gas) diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 60ace95c8..81d81ecef 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -48,6 +48,29 @@ func Int(w *io.BinWriter, i int64) { } } +// Array emits array of elements to the given buffer. +func Array(w *io.BinWriter, es ...interface{}) { + for i := len(es) - 1; i >= 0; i-- { + switch e := es[i].(type) { + case int64: + Int(w, e) + case string: + String(w, e) + case util.Uint160: + Bytes(w, e.BytesBE()) + case []byte: + Bytes(w, e) + case bool: + Bool(w, e) + default: + w.Err = errors.New("unsupported type") + return + } + } + Int(w, int64(len(es))) + Opcode(w, opcode.PACK) +} + // String emits a string to the given buffer. func String(w *io.BinWriter, s string) { Bytes(w, []byte(s)) @@ -118,6 +141,13 @@ func AppCall(w *io.BinWriter, scriptHash util.Uint160, tailCall bool) { Instruction(w, op, scriptHash.BytesBE()) } +// AppCallWithOperationAndArgs emits an APPCALL with the given operation and arguments. +func AppCallWithOperationAndArgs(w *io.BinWriter, scriptHash util.Uint160, operation string, args ...interface{}) { + Array(w, args...) + String(w, operation) + AppCall(w, scriptHash, false) +} + // AppCallWithOperationAndData emits an appcall with the given operation and data. func AppCallWithOperationAndData(w *io.BinWriter, scriptHash util.Uint160, operation string, data []byte) { Bytes(w, data) diff --git a/pkg/vm/emit/emit_test.go b/pkg/vm/emit/emit_test.go index 8dd3e525b..cb028cb9e 100644 --- a/pkg/vm/emit/emit_test.go +++ b/pkg/vm/emit/emit_test.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestEmitInt(t *testing.T) { @@ -107,6 +108,35 @@ func TestBytes(t *testing.T) { }) } +func TestEmitArray(t *testing.T) { + t.Run("good", func(t *testing.T) { + buf := io.NewBufBinWriter() + Array(buf.BinWriter, int64(1), "str", true, []byte{0xCA, 0xFE}) + require.NoError(t, buf.Err) + + res := buf.Bytes() + assert.EqualValues(t, opcode.PUSHBYTES2, res[0]) + assert.EqualValues(t, []byte{0xCA, 0xFE}, res[1:3]) + assert.EqualValues(t, opcode.PUSHT, res[3]) + assert.EqualValues(t, opcode.PUSHBYTES3, res[4]) + assert.EqualValues(t, []byte("str"), res[5:8]) + assert.EqualValues(t, opcode.PUSH1, res[8]) + }) + + t.Run("empty", func(t *testing.T) { + buf := io.NewBufBinWriter() + Array(buf.BinWriter) + require.NoError(t, buf.Err) + assert.EqualValues(t, []byte{0, byte(opcode.PACK)}, buf.Bytes()) + }) + + t.Run("invalid type", func(t *testing.T) { + buf := io.NewBufBinWriter() + Array(buf.BinWriter, struct{}{}) + require.Error(t, buf.Err) + }) +} + func TestEmitBool(t *testing.T) { buf := io.NewBufBinWriter() Bool(buf.BinWriter, true)