From 4403a95ae67481a7aa40ac99d5bbe5b797be6263 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 8 Sep 2022 15:57:27 +0300 Subject: [PATCH 1/2] cli: don't return sender from invokeWithArgs It's not really needed, deployer knows the sender exactly already. --- cli/smartcontract/smart_contract.go | 36 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 009cf94e0..af4b7ffbf 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -653,17 +653,15 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { defer w.Close() } - _, err = invokeWithArgs(ctx, acc, w, script, operation, params, cosigners) - return err + return invokeWithArgs(ctx, acc, w, script, operation, params, cosigners) } -func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []smartcontract.Parameter, cosigners []transaction.Signer) (util.Uint160, error) { +func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []smartcontract.Parameter, cosigners []transaction.Signer) error { var ( err error gas, sysgas fixedn.Fixed8 signersAccounts []actor.SignerAccount resp *result.Invoke - sender util.Uint160 signAndPush = acc != nil act *actor.Actor ) @@ -672,21 +670,20 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, sysgas = flags.Fixed8FromContext(ctx, "sysgas") signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None) if err != nil { - return sender, cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) + return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) } - sender = signersAccounts[0].Signer.Account } gctx, cancel := options.GetTimeoutContext(ctx) defer cancel() c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return sender, err + return err } if signAndPush { act, err = actor.New(c, signersAccounts) if err != nil { - return sender, cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1) + return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1) } } out := ctx.String("out") @@ -695,12 +692,12 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, // to []interface{}. resp, err = c.InvokeFunction(script, operation, params, cosigners) if err != nil { - return sender, cli.NewExitError(err, 1) + return cli.NewExitError(err, 1) } if resp.State != "HALT" { errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException) if !signAndPush { - return sender, cli.NewExitError(errText, 1) + return cli.NewExitError(errText, 1) } action := "send" @@ -710,25 +707,25 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, process = "Saving" } if !ctx.Bool("force") { - return sender, cli.NewExitError(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1) + return cli.NewExitError(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1) } fmt.Fprintln(ctx.App.Writer, errText+".\n"+process+" transaction...") } if !signAndPush { b, err := json.MarshalIndent(resp, "", " ") if err != nil { - return sender, cli.NewExitError(err, 1) + return cli.NewExitError(err, 1) } fmt.Fprintln(ctx.App.Writer, string(b)) } else { if len(resp.Script) == 0 { - return sender, cli.NewExitError(errors.New("no script returned from the RPC node"), 1) + return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) } ver := act.GetVersion() tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed+int64(sysgas), nil) if err != nil { - return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) + return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) } tx.NetworkFee += int64(gas) if out != "" { @@ -736,7 +733,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2 m := act.GetNetwork() if err := paramcontext.InitAndSave(m, tx, acc, out); err != nil { - return sender, cli.NewExitError(err, 1) + return cli.NewExitError(err, 1) } fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) } else { @@ -744,7 +741,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, promptTime := time.Now() err := input.ConfirmTx(ctx.App.Writer, tx) if err != nil { - return sender, cli.NewExitError(err, 1) + return cli.NewExitError(err, 1) } waitTime := time.Since(promptTime) // Compensate for confirmation waiting. @@ -752,13 +749,13 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, } txHash, _, err := act.SignAndSend(tx) if err != nil { - return sender, cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) + return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) } fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE()) } } - return sender, nil + return nil } func testInvokeScript(ctx *cli.Context) error { @@ -951,6 +948,7 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1) } defer w.Close() + sender := acc.ScriptHash() cosigners, sgnErr := cmdargs.GetSignersFromContext(ctx, signOffset) if sgnErr != nil { @@ -962,7 +960,7 @@ func contractDeploy(ctx *cli.Context) error { }} } - sender, extErr := invokeWithArgs(ctx, acc, w, management.Hash, "deploy", appCallParams, cosigners) + extErr := invokeWithArgs(ctx, acc, w, management.Hash, "deploy", appCallParams, cosigners) if extErr != nil { return extErr } From d2d190913b11cfdfaa39d2aa820055d5f24a2334 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 8 Sep 2022 19:05:32 +0300 Subject: [PATCH 2/2] cli: add historic abilities for all invoking commands Notice that we can't do this for balance commands (unless we change the interface in some manner) because they use getnepXXbalances. Fixes #2620. --- cli/contract_test.go | 103 ++++++++++++++++++++++------ cli/nep11_test.go | 19 +++++ cli/options/options.go | 43 ++++++++++++ cli/smartcontract/smart_contract.go | 49 +++++++------ cli/wallet/nep11.go | 42 +++++++----- 5 files changed, 194 insertions(+), 62 deletions(-) diff --git a/cli/contract_test.go b/cli/contract_test.go index d0cc32596..55e6bfa6d 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -602,6 +602,39 @@ func TestContract_TestInvokeScript(t *testing.T) { "--rpc-endpoint", "http://123456789", "--in", goodNef) }) + t.Run("good", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", goodNef) + }) + t.Run("good with hashed signer", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", goodNef, "--", util.Uint160{1, 2, 3}.StringLE()) + }) + t.Run("good with addressed signer", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", goodNef, "--", address.Uint160ToString(util.Uint160{1, 2, 3})) + }) + t.Run("historic, invalid", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--historic", "bad", + "--in", goodNef) + }) + t.Run("historic, index", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--historic", "0", + "--in", goodNef) + }) + t.Run("historic, hash", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--historic", e.Chain.GetHeaderHash(0).StringLE(), + "--in", goodNef) + }) } func TestComlileAndInvokeFunction(t *testing.T) { @@ -618,16 +651,6 @@ func TestComlileAndInvokeFunction(t *testing.T) { "--config", "testdata/deploy/neo-go.yml", "--out", nefName, "--manifest", manifestName) - // Check that it is possible to invoke before deploy. - // This doesn't make much sense, because every method has an offset - // which is contained in the manifest. This should be either removed or refactored. - e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--in", nefName, "--", util.Uint160{1, 2, 3}.StringLE()) - e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--in", nefName, "--", address.Uint160ToString(util.Uint160{1, 2, 3})) - tmp := t.TempDir() configPath := filepath.Join(tmp, "config.yaml") cfg := config.Wallet{ @@ -689,11 +712,14 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.Run(t, cmd...) - res := new(result.Invoke) - require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) - require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) - require.Len(t, res.Stack, 1) - require.Equal(t, []byte("on create|sub create"), res.Stack[0].Value()) + checkGetValueOut := func(str string) { + res := new(result.Invoke) + require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) + require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) + require.Len(t, res.Stack, 1) + require.Equal(t, []byte(str), res.Stack[0].Value()) + } + checkGetValueOut("on create|sub create") // deploy verification contract hVerify := deployVerifyContract(t, e) @@ -866,6 +892,12 @@ func TestComlileAndInvokeFunction(t *testing.T) { }) }) + var ( + hashBeforeUpdate util.Uint256 + indexBeforeUpdate uint32 + indexAfterUpdate uint32 + stateBeforeUpdate util.Uint256 + ) t.Run("Update", func(t *testing.T) { nefName := filepath.Join(tmpDir, "updated.nef") manifestName := filepath.Join(tmpDir, "updated.manifest.json") @@ -884,6 +916,11 @@ func TestComlileAndInvokeFunction(t *testing.T) { rawManifest, err := os.ReadFile(manifestName) require.NoError(t, err) + indexBeforeUpdate = e.Chain.BlockHeight() + hashBeforeUpdate = e.Chain.CurrentHeaderHash() + mptBeforeUpdate, err := e.Chain.GetStateRoot(indexBeforeUpdate) + require.NoError(t, err) + stateBeforeUpdate = mptBeforeUpdate.Root e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "invokefunction", "--rpc-endpoint", "http://"+e.RPC.Addr, @@ -895,16 +932,40 @@ func TestComlileAndInvokeFunction(t *testing.T) { ) e.checkTxPersisted(t, "Sent invocation transaction ") + indexAfterUpdate = e.Chain.BlockHeight() e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "testinvokefunction", "--rpc-endpoint", "http://"+e.RPC.Addr, h.StringLE(), "getValue") - - res := new(result.Invoke) - require.NoError(t, json.Unmarshal(e.Out.Bytes(), res)) - require.Equal(t, vmstate.Halt.String(), res.State) - require.Len(t, res.Stack, 1) - require.Equal(t, []byte("on update|sub update"), res.Stack[0].Value()) + checkGetValueOut("on update|sub update") + }) + t.Run("historic", func(t *testing.T) { + t.Run("bad ref", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--historic", "bad", + h.StringLE(), "getValue") + }) + for name, ref := range map[string]string{ + "by index": strconv.FormatUint(uint64(indexBeforeUpdate), 10), + "by block hash": hashBeforeUpdate.StringLE(), + "by state hash": stateBeforeUpdate.StringLE(), + } { + t.Run(name, func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--historic", ref, + h.StringLE(), "getValue") + }) + checkGetValueOut("on create|sub create") + } + t.Run("updated historic", func(t *testing.T) { + e.Run(t, "neo-go", "contract", "testinvokefunction", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--historic", strconv.FormatUint(uint64(indexAfterUpdate), 10), + h.StringLE(), "getValue") + checkGetValueOut("on update|sub update") + }) }) } diff --git a/cli/nep11_test.go b/cli/nep11_test.go index 771af8446..a652ec094 100644 --- a/cli/nep11_test.go +++ b/cli/nep11_test.go @@ -161,6 +161,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { } tokenID := mint(t) + var hashBeforeTransfer = e.Chain.CurrentHeaderHash() // check the balance cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance", @@ -358,6 +359,24 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // check balance after transfer e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) checkBalanceResult(t, nftOwnerAddr) + + // historic calls still remember the good old days. + cmdOwnerOf = append(cmdOwnerOf, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdOwnerOf...) + e.checkNextLine(t, nftOwnerAddr) + + cmdTokensOf = append(cmdTokensOf, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdTokensOf...) + require.Equal(t, hex.EncodeToString(tokenID), e.getNextLine(t)) + + cmdTokens = append(cmdTokens, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdTokens...) + require.Equal(t, hex.EncodeToString(tokenID), e.getNextLine(t)) + + // this one is not affected by transfer, but anyway + cmdProperties = append(cmdProperties, "--historic", hashBeforeTransfer.StringLE()) + e.Run(t, cmdProperties...) + require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.getNextLine(t)) } func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { diff --git a/cli/options/options.go b/cli/options/options.go index cc73d3a97..cb23cb72c 100644 --- a/cli/options/options.go +++ b/cli/options/options.go @@ -6,10 +6,14 @@ package options import ( "context" "errors" + "strconv" "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/urfave/cli" ) @@ -42,7 +46,14 @@ var RPC = []cli.Flag{ }, } +// Historic is a flag for commands that can perform historic invocations. +var Historic = cli.StringFlag{ + Name: "historic", + Usage: "Use historic state (height, block hash or state root hash)", +} + var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'") +var errInvalidHistoric = errors.New("invalid 'historic' parameter, neither a block number, nor a block/state hash") // GetNetwork examines Context's flags and returns the appropriate network. It // defaults to PrivNet if no flags are given. @@ -85,3 +96,35 @@ func GetRPCClient(gctx context.Context, ctx *cli.Context) (*rpcclient.Client, cl } return c, nil } + +// GetInvoker returns an invoker using the given RPC client, context and signers. +// It parses "--historic" parameter to adjust it. +func GetInvoker(c *rpcclient.Client, ctx *cli.Context, signers []transaction.Signer) (*invoker.Invoker, cli.ExitCoder) { + historic := ctx.String("historic") + if historic == "" { + return invoker.New(c, signers), nil + } + if index, err := strconv.ParseUint(historic, 10, 32); err == nil { + return invoker.NewHistoricAtHeight(uint32(index), c, signers), nil + } + if u256, err := util.Uint256DecodeStringLE(historic); err == nil { + // Might as well be a block hash, but it makes no practical difference. + return invoker.NewHistoricWithState(u256, c, signers), nil + } + return nil, cli.NewExitError(errInvalidHistoric, 1) +} + +// GetRPCWithInvoker combines GetRPCClient with GetInvoker for cases where it's +// appropriate to do so. +func GetRPCWithInvoker(gctx context.Context, ctx *cli.Context, signers []transaction.Signer) (*rpcclient.Client, *invoker.Invoker, cli.ExitCoder) { + c, err := GetRPCClient(gctx, ctx) + if err != nil { + return nil, nil, err + } + inv, err := GetInvoker(c, ctx, signers) + if err != nil { + c.Close() + return nil, nil, err + } + return c, inv, err +} diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index af4b7ffbf..ef2159dd3 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -23,6 +23,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -110,8 +111,11 @@ func NewCommands() []cli.Command { Name: "in, i", Usage: "Input location of the .nef file that needs to be invoked", }, + options.Historic, } testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...) + testInvokeFunctionFlags := []cli.Flag{options.Historic} + testInvokeFunctionFlags = append(testInvokeFunctionFlags, options.RPC...) invokeFunctionFlags := []cli.Flag{ walletFlag, walletConfigFlag, @@ -213,7 +217,7 @@ func NewCommands() []cli.Command { { Name: "testinvokefunction", Usage: "invoke deployed contract on the blockchain (test mode)", - UsageText: "neo-go contract testinvokefunction -r endpoint scripthash [method] [arguments...] [--] [signers...]", + UsageText: "neo-go contract testinvokefunction -r endpoint [--historic index/hash] scripthash [method] [arguments...] [--] [signers...]", Description: `Executes given (as a script hash) deployed script with the given method, arguments and signers (sender is not included by default). If no method is given "" is passed to the script, if no arguments are given, an empty array is @@ -328,12 +332,12 @@ func NewCommands() []cli.Command { `CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02' `, Action: testInvokeFunction, - Flags: options.RPC, + Flags: testInvokeFunctionFlags, }, { Name: "testinvokescript", Usage: "Invoke compiled AVM code in NEF format on the blockchain (test mode, not creating a transaction for it)", - UsageText: "neo-go contract testinvokescript -r endpoint -i input.nef [signers...]", + UsageText: "neo-go contract testinvokescript -r endpoint -i input.nef [--historic index/hash] [signers...]", Description: `Executes given compiled AVM instructions in NEF format with the given set of signers not included sender by default. See testinvokefunction documentation for the details about parameters. @@ -608,8 +612,9 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { err error exitErr *cli.ExitError operation string - params = make([]smartcontract.Parameter, 0) + params []interface{} paramsStart = 1 + scParams []smartcontract.Parameter cosigners []transaction.Signer cosignersOffset = 0 ) @@ -629,10 +634,14 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { paramsStart++ if len(args) > paramsStart { - cosignersOffset, params, err = cmdargs.ParseParams(args[paramsStart:], true) + cosignersOffset, scParams, err = cmdargs.ParseParams(args[paramsStart:], true) if err != nil { return cli.NewExitError(err, 1) } + params = make([]interface{}, len(scParams)) + for i := range scParams { + params[i] = scParams[i] + } } cosignersStart := paramsStart + cosignersOffset @@ -656,13 +665,14 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { return invokeWithArgs(ctx, acc, w, script, operation, params, cosigners) } -func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []smartcontract.Parameter, cosigners []transaction.Signer) error { +func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []interface{}, cosigners []transaction.Signer) error { var ( err error gas, sysgas fixedn.Fixed8 signersAccounts []actor.SignerAccount resp *result.Invoke signAndPush = acc != nil + inv *invoker.Invoker act *actor.Actor ) if signAndPush { @@ -685,12 +695,15 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, if err != nil { return cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1) } + inv = &act.Invoker + } else { + inv, err = options.GetInvoker(c, ctx, cosigners) + if err != nil { + return err + } } out := ctx.String("out") - // It's a bit easier to keep this as is (not using invoker.Invoker) - // during transition period. Mostly because of the need to convert params - // to []interface{}. - resp, err = c.InvokeFunction(script, operation, params, cosigners) + resp, err = inv.Call(script, operation, params...) if err != nil { return cli.NewExitError(err, 1) } @@ -781,12 +794,12 @@ func testInvokeScript(ctx *cli.Context) error { gctx, cancel := options.GetTimeoutContext(ctx) defer cancel() - c, err := options.GetRPCClient(gctx, ctx) + _, inv, err := options.GetRPCWithInvoker(gctx, ctx, signers) if err != nil { return err } - resp, err := c.InvokeScript(nefFile.Script, signers) + resp, err := inv.Run(nefFile.Script) if err != nil { return cli.NewExitError(err, 1) } @@ -922,16 +935,8 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("failed to read manifest file: %w", err), 1) } - appCallParams := []smartcontract.Parameter{ - { - Type: smartcontract.ByteArrayType, - Value: f, - }, - { - Type: smartcontract.ByteArrayType, - Value: manifestBytes, - }, - } + var appCallParams = []interface{}{f, manifestBytes} + signOffset, data, err := cmdargs.ParseParams(ctx.Args(), true) if err != nil { return cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index 1d7034957..5fb8c950e 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -13,7 +13,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" @@ -116,50 +115,55 @@ func newNEP11Commands() []cli.Command { { Name: "properties", Usage: "print properties of NEP-11 token", - UsageText: "properties --rpc-endpoint --timeout