From 1d3a297a6b270b872893948a380b9d9692f91cc5 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 16 Apr 2021 13:54:23 +0300 Subject: [PATCH 1/5] rpc: add Data field to NEP17 TransferTarget It's a part of transfer, thus it should be passed along with the other transfer parameters. --- cli/wallet/nep17.go | 4 +++- pkg/rpc/client/nep17.go | 21 ++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 76096bb94..68c3606f7 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -411,6 +411,7 @@ func multiTransferNEP17(ctx *cli.Context) error { Token: token.Hash, Address: addr, Amount: amount.Int64(), + Data: nil, }) } @@ -462,13 +463,14 @@ func transferNEP17(ctx *cli.Context) error { Token: token.Hash, Address: to, Amount: amount.Int64(), + Data: nil, }}) } func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account, recipients []client.TransferTarget) error { gas := flags.Fixed8FromContext(ctx, "gas") - tx, err := c.CreateNEP17MultiTransferTx(acc, int64(gas), recipients, nil) + tx, err := c.CreateNEP17MultiTransferTx(acc, int64(gas), recipients) if err != nil { return cli.NewExitError(err, 1) } diff --git a/pkg/rpc/client/nep17.go b/pkg/rpc/client/nep17.go index 51d7b5ed7..3b27ebf54 100644 --- a/pkg/rpc/client/nep17.go +++ b/pkg/rpc/client/nep17.go @@ -13,11 +13,12 @@ import ( "github.com/nspcc-dev/neo-go/pkg/wallet" ) -// TransferTarget represents target address and token amount for transfer. +// TransferTarget represents target address, token amount and data for transfer. type TransferTarget struct { Token util.Uint160 Address util.Uint160 Amount int64 + Data interface{} } // SignerAccount represents combination of the transaction.Signer and the @@ -73,28 +74,22 @@ func (c *Client) CreateNEP17TransferTx(acc *wallet.Account, to util.Uint160, tok {Token: token, Address: to, Amount: amount, + Data: data, }, - }, []interface{}{data}) + }) } // CreateNEP17MultiTransferTx creates an invocation transaction for performing NEP17 transfers // from a single sender to multiple recipients with the given data. -func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, recipients []TransferTarget, data []interface{}) (*transaction.Transaction, error) { +func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, recipients []TransferTarget) (*transaction.Transaction, error) { from, err := address.StringToUint160(acc.Address) if err != nil { return nil, fmt.Errorf("bad account address: %w", err) } - if data == nil { - data = make([]interface{}, len(recipients)) - } else { - if len(data) != len(recipients) { - return nil, fmt.Errorf("data and recipients number mismatch: %d vs %d", len(data), len(recipients)) - } - } w := io.NewBufBinWriter() for i := range recipients { emit.AppCall(w.BinWriter, recipients[i].Token, "transfer", callflag.All, - from, recipients[i].Address, recipients[i].Amount, data[i]) + from, recipients[i].Address, recipients[i].Amount, recipients[i].Data) emit.Opcodes(w.BinWriter, opcode.ASSERT) } if w.Err != nil { @@ -167,12 +162,12 @@ func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util. } // MultiTransferNEP17 is similar to TransferNEP17, buf allows to have multiple recipients. -func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget, data []interface{}) (util.Uint256, error) { +func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget) (util.Uint256, error) { if !c.initDone { return util.Uint256{}, errNetworkNotInitialized } - tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, data) + tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients) if err != nil { return util.Uint256{}, err } From f7dcb7ae290d94ac82f666d2a4922d23ab33761a Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 16 Apr 2021 16:19:37 +0300 Subject: [PATCH 2/5] vm: allow emit.Array handle uint256 --- pkg/vm/emit/emit.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 55949f8cb..9c6da396a 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -88,6 +88,8 @@ func Array(w *io.BinWriter, es ...interface{}) { String(w, e) case util.Uint160: Bytes(w, e.BytesBE()) + case util.Uint256: + Bytes(w, e.BytesBE()) case []byte: Bytes(w, e) case bool: From c77997a357de4945d06daddd4adc7b10fe479921 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 16 Apr 2021 16:21:25 +0300 Subject: [PATCH 3/5] config: enable Notary contract for single unittestnet chain We need this for tests. --- config/protocol.unit_testnet.single.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/protocol.unit_testnet.single.yml b/config/protocol.unit_testnet.single.yml index dc47603ad..076487504 100644 --- a/config/protocol.unit_testnet.single.yml +++ b/config/protocol.unit_testnet.single.yml @@ -8,7 +8,7 @@ ProtocolConfiguration: ValidatorsCount: 1 VerifyBlocks: true VerifyTransactions: true - P2PSigExtensions: false + P2PSigExtensions: true NativeActivations: ContractManagement: [0] StdLib: [0] @@ -20,6 +20,7 @@ ProtocolConfiguration: RoleManagement: [0] OracleContract: [0] NameService: [0] + Notary: [0] ApplicationConfiguration: # LogPath could be set up in case you need stdout logs to some proper file. From 28b74cb64794cbb15fb8acac0fa391fcd2064b86 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 16 Apr 2021 17:30:25 +0300 Subject: [PATCH 4/5] smartcontract: add ExpandParameterToEmitable method It'll help to convert Parameter to a atandard type which then can be emitted ass an array item. --- pkg/smartcontract/parameter.go | 26 +++++++++ pkg/smartcontract/parameter_test.go | 85 +++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/pkg/smartcontract/parameter.go b/pkg/smartcontract/parameter.go index 5c870add7..d961b191e 100644 --- a/pkg/smartcontract/parameter.go +++ b/pkg/smartcontract/parameter.go @@ -14,6 +14,7 @@ import ( "strings" "unicode/utf8" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -437,3 +438,28 @@ func NewParameterFromString(in string) (*Parameter, error) { } return res, nil } + +// ExpandParameterToEmitable converts parameter to a type which can be handled as +// an array item by emit.Array. It correlates with the way RPC server handles +// FuncParams for invoke* calls inside the request.ExpandArrayIntoScript function. +func ExpandParameterToEmitable(param Parameter) (interface{}, error) { + var err error + switch t := param.Type; t { + case PublicKeyType: + return param.Value.(*keys.PublicKey).Bytes(), nil + case ArrayType: + arr := param.Value.([]Parameter) + res := make([]interface{}, len(arr)) + for i := range arr { + res[i], err = ExpandParameterToEmitable(arr[i]) + if err != nil { + return nil, err + } + } + return res, nil + case MapType, InteropInterfaceType, UnknownType, AnyType, VoidType: + return nil, fmt.Errorf("unsupported parameter type: %s", t.String()) + default: + return param.Value, nil + } +} diff --git a/pkg/smartcontract/parameter_test.go b/pkg/smartcontract/parameter_test.go index 45b421568..021f9ca9d 100644 --- a/pkg/smartcontract/parameter_test.go +++ b/pkg/smartcontract/parameter_test.go @@ -9,7 +9,10 @@ import ( "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -517,3 +520,85 @@ func hexToBase64(s string) string { b, _ := hex.DecodeString(s) return base64.StdEncoding.EncodeToString(b) } + +func TestExpandParameterToEmitable(t *testing.T) { + pk, _ := keys.NewPrivateKey() + testCases := []struct { + In Parameter + Expected interface{} + }{ + { + In: Parameter{Type: BoolType, Value: true}, + Expected: true, + }, + { + In: Parameter{Type: IntegerType, Value: int64(123)}, + Expected: int64(123), + }, + { + In: Parameter{Type: ByteArrayType, Value: []byte{1, 2, 3}}, + Expected: []byte{1, 2, 3}, + }, + { + In: Parameter{Type: StringType, Value: "writing's on the wall"}, + Expected: "writing's on the wall", + }, + { + In: Parameter{Type: Hash160Type, Value: util.Uint160{1, 2, 3}}, + Expected: util.Uint160{1, 2, 3}, + }, + { + In: Parameter{Type: Hash256Type, Value: util.Uint256{1, 2, 3}}, + Expected: util.Uint256{1, 2, 3}, + }, + { + In: Parameter{Type: PublicKeyType, Value: pk.PublicKey()}, + Expected: pk.PublicKey().Bytes(), + }, + { + In: Parameter{Type: SignatureType, Value: []byte{1, 2, 3}}, + Expected: []byte{1, 2, 3}, + }, + { + In: Parameter{Type: ArrayType, Value: []Parameter{ + { + Type: IntegerType, + Value: int64(123), + }, + { + Type: ByteArrayType, + Value: []byte{1, 2, 3}, + }, + { + Type: ArrayType, + Value: []Parameter{ + { + Type: BoolType, + Value: true, + }, + }, + }, + }}, + Expected: []interface{}{int64(123), []byte{1, 2, 3}, []interface{}{true}}, + }, + } + bw := io.NewBufBinWriter() + for _, testCase := range testCases { + actual, err := ExpandParameterToEmitable(testCase.In) + require.NoError(t, err) + require.Equal(t, testCase.Expected, actual) + + emit.Array(bw.BinWriter, actual) + require.NoError(t, bw.Err) + } + errCases := []Parameter{ + {Type: AnyType}, + {Type: UnknownType}, + {Type: MapType}, + {Type: InteropInterfaceType}, + } + for _, errCase := range errCases { + _, err := ExpandParameterToEmitable(errCase) + require.Error(t, err) + } +} From db868f033e58be27e2023b760129f76684db09bf Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 16 Apr 2021 13:45:54 +0300 Subject: [PATCH 5/5] cli: allow to provide `data` for nep17 transfer commands --- cli/nep17_test.go | 16 ++++++++++++++ cli/smartcontract/smart_contract.go | 28 ++++++++++++++++++++---- cli/smartcontract/smart_contract_test.go | 8 +++---- cli/wallet/nep17.go | 13 +++++++++-- 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/cli/nep17_test.go b/cli/nep17_test.go index 6930cfe85..4a28ddbc0 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -161,6 +161,22 @@ func TestNEP17Transfer(t *testing.T) { b, _ = e.Chain.GetGoverningTokenBalance(sh) require.Equal(t, big.NewInt(41), b) }) + + t.Run("with data", func(t *testing.T) { + e.In.WriteString("one\r") + validTil := e.Chain.BlockHeight() + 100 + e.Run(t, []string{ + "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addr, + "--wallet", validatorWallet, + "--to", address.Uint160ToString(e.Chain.GetNotaryContractScriptHash()), + "--token", "GAS", + "--amount", "1", + "--from", validatorAddr, + "[", validatorAddr, strconv.Itoa(int(validTil)), "]", + }...) + e.checkTxPersisted(t) + }) } func TestNEP17MultiTransfer(t *testing.T) { diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index faad19f58..1bfee6e36 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -539,7 +539,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { paramsStart++ if len(args) > paramsStart { - cosignersOffset, params, err = parseParams(args[paramsStart:], true) + cosignersOffset, params, err = ParseParams(args[paramsStart:], true) if err != nil { return cli.NewExitError(err, 1) } @@ -624,12 +624,12 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { return nil } -// parseParams extracts array of smartcontract.Parameter from the given args and +// ParseParams extracts array of smartcontract.Parameter from the given args and // returns the number of handled words, the array itself and an error. // `calledFromMain` denotes whether the method was called from the outside or // recursively and used to check if cosignersSeparator and closing bracket are // allowed to be in `args` sequence. -func parseParams(args []string, calledFromMain bool) (int, []smartcontract.Parameter, error) { +func ParseParams(args []string, calledFromMain bool) (int, []smartcontract.Parameter, error) { res := []smartcontract.Parameter{} for k := 0; k < len(args); { s := args[k] @@ -640,7 +640,7 @@ func parseParams(args []string, calledFromMain bool) (int, []smartcontract.Param } return 0, []smartcontract.Parameter{}, errors.New("invalid array syntax: missing closing bracket") case arrayStartSeparator: - numWordsRead, array, err := parseParams(args[k+1:], false) + numWordsRead, array, err := ParseParams(args[k+1:], false) if err != nil { return 0, nil, fmt.Errorf("failed to parse array: %w", err) } @@ -888,6 +888,26 @@ func contractDeploy(ctx *cli.Context) error { return nil } +// GetDataFromContext returns data parameter from context args. +func GetDataFromContext(ctx *cli.Context) (interface{}, *cli.ExitError) { + var data interface{} + args := ctx.Args() + if args.Present() { + _, params, err := ParseParams(args, true) + if err != nil { + return nil, cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) + } + if len(params) != 1 { + return nil, cli.NewExitError("'data' should be represented as a single parameter", 1) + } + data, err = smartcontract.ExpandParameterToEmitable(params[0]) + if err != nil { + return nil, cli.NewExitError(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1) + } + } + return data, nil +} + // ParseContractConfig reads contract configuration file (.yaml) and returns unmarshalled ProjectConfig. func ParseContractConfig(confFile string) (ProjectConfig, error) { conf := ProjectConfig{} diff --git a/cli/smartcontract/smart_contract_test.go b/cli/smartcontract/smart_contract_test.go index 64e8811fb..89af4f7be 100644 --- a/cli/smartcontract/smart_contract_test.go +++ b/cli/smartcontract/smart_contract_test.go @@ -202,7 +202,7 @@ func TestParseParams_CalledFromItself(t *testing.T) { for str, expected := range testCases { input := strings.Split(str, " ") - offset, actual, err := parseParams(input, false) + offset, actual, err := ParseParams(input, false) require.NoError(t, err) require.Equal(t, expected.WordsRead, offset) require.Equal(t, expected.Value, actual) @@ -218,7 +218,7 @@ func TestParseParams_CalledFromItself(t *testing.T) { for _, str := range errorCases { input := strings.Split(str, " ") - _, _, err := parseParams(input, false) + _, _, err := ParseParams(input, false) require.Error(t, err) } } @@ -400,7 +400,7 @@ func TestParseParams_CalledFromOutside(t *testing.T) { } for str, expected := range testCases { input := strings.Split(str, " ") - offset, arr, err := parseParams(input, true) + offset, arr, err := ParseParams(input, true) require.NoError(t, err) require.Equal(t, expected.WordsRead, offset) require.Equal(t, expected.Parameters, arr) @@ -415,7 +415,7 @@ func TestParseParams_CalledFromOutside(t *testing.T) { } for _, str := range errorCases { input := strings.Split(str, " ") - _, _, err := parseParams(input, true) + _, _, err := ParseParams(input, true) require.Error(t, err) } } diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 68c3606f7..efc226bcd 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" + smartcontractcli "github.com/nspcc-dev/neo-go/cli/smartcontract" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/rpc/client" @@ -111,9 +112,12 @@ func newNEP17Commands() []cli.Command { { Name: "transfer", Usage: "transfer NEP17 tokens", - UsageText: "transfer --wallet --rpc-endpoint --timeout