From 39559b90e28ec9d83d6704cb3d3b0b5104ff297f Mon Sep 17 00:00:00 2001 From: Ekaterina Pavlova Date: Fri, 5 Jul 2024 01:34:40 +0300 Subject: [PATCH] cli: add Required field for flags Close #2861 Signed-off-by: Ekaterina Pavlova --- cli/cmdargs/parser.go | 11 +++ cli/nep_test/nep11_test.go | 25 ++--- cli/nep_test/nep17_test.go | 6 +- cli/options/options.go | 13 ++- cli/server/server.go | 4 +- cli/smartcontract/contract_test.go | 43 +++++---- cli/smartcontract/generate.go | 2 + cli/smartcontract/generate_test.go | 95 +++++++------------ cli/smartcontract/manifest.go | 9 -- cli/smartcontract/smart_contract.go | 137 ++++++++++++++-------------- cli/util/convert.go | 6 +- cli/wallet/candidate_test.go | 6 +- cli/wallet/multisig_test.go | 4 +- cli/wallet/nep11.go | 34 ++----- cli/wallet/nep17.go | 15 +-- cli/wallet/validator.go | 28 +++--- cli/wallet/wallet.go | 60 ++++++------ cli/wallet/wallet_test.go | 24 ++--- internal/testcli/executor.go | 15 ++- 19 files changed, 261 insertions(+), 276 deletions(-) diff --git a/cli/cmdargs/parser.go b/cli/cmdargs/parser.go index 83e41c7ac..12cf6b180 100644 --- a/cli/cmdargs/parser.go +++ b/cli/cmdargs/parser.go @@ -348,3 +348,14 @@ func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers } return signersAccounts, nil } + +// EnsureNotEmpty returns a function that checks if the flag with the given name +// is not empty. +func EnsureNotEmpty(flagName string) func(*cli.Context, string) error { + return func(ctx *cli.Context, name string) error { + if ctx.String(flagName) == "" { + return cli.Exit(fmt.Errorf("required flag --%s is empty", flagName), 1) + } + return nil + } +} diff --git a/cli/nep_test/nep11_test.go b/cli/nep_test/nep11_test.go index e2866c107..bba2f705b 100644 --- a/cli/nep_test/nep11_test.go +++ b/cli/nep_test/nep11_test.go @@ -55,11 +55,14 @@ func TestNEP11Import(t *testing.T) { "--wallet", walletPath, } // missing token hash - e.RunWithError(t, args...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, args...) // excessive parameters e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE(), "something")...) + // empty token hash + e.RunWithErrorCheck(t, `invalid value "" for flag -token: zero length string`, append(args, "--token", "")...) + // good: non-divisible e.Run(t, append(args, "--token", nnsContractHash.StringLE())...) @@ -229,7 +232,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOf", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdOwnerOf...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...) cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE()) // ownerOf: missing token ID @@ -244,11 +247,11 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdTokensOf...) + e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...) cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE()) // tokensOf: missing owner address - e.RunWithError(t, cmdTokensOf...) + e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...) cmdTokensOf = append(cmdTokensOf, "--address", nftOwnerAddr) // tokensOf: good @@ -260,7 +263,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { "neo-go", "wallet", "nep11", "properties", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdProperties...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...) cmdProperties = append(cmdProperties, "--token", h.StringLE()) // properties: no token ID @@ -286,7 +289,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdTokens...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...) cmdTokens = append(cmdTokens, "--token", h.StringLE()) // tokens: excessive parameters @@ -514,7 +517,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdOwnerOf...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...) cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE()) // ownerOfD: missing token ID @@ -529,11 +532,11 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdTokensOf...) + e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...) cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE()) // tokensOf: missing owner address - e.RunWithError(t, cmdTokensOf...) + e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...) cmdTokensOf = append(cmdTokensOf, "--address", testcli.ValidatorAddr) // tokensOf: good @@ -547,7 +550,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { "neo-go", "wallet", "nep11", "properties", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdProperties...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...) cmdProperties = append(cmdProperties, "--token", h.StringLE()) // properties: no token ID @@ -580,7 +583,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } - e.RunWithError(t, cmdTokens...) + e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...) cmdTokens = append(cmdTokens, "--token", h.StringLE()) // tokens: good, several tokens diff --git a/cli/nep_test/nep17_test.go b/cli/nep_test/nep17_test.go index a7f037190..e7aa29393 100644 --- a/cli/nep_test/nep17_test.go +++ b/cli/nep_test/nep17_test.go @@ -113,7 +113,7 @@ func TestNEP17Balance(t *testing.T) { e.CheckEOF(t) }) t.Run("Bad wallet", func(t *testing.T) { - e.RunWithError(t, append(cmdbalance, "--wallet", "/dev/null")...) + e.RunWithError(t, append(cmdbalance, "--wallet", "/dev/null", "-r", "test")...) }) } @@ -136,7 +136,7 @@ func TestNEP17Transfer(t *testing.T) { as := append([]string{}, args[:8]...) as = append(as, args[10:]...) e.In.WriteString("one\r") - e.RunWithError(t, as...) + e.RunWithErrorCheck(t, `Required flag "to" not set`, as...) e.In.Reset() }) @@ -326,7 +326,7 @@ func TestNEP17ImportToken(t *testing.T) { e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) // missing token hash - e.RunWithError(t, "neo-go", "wallet", "nep17", "import", + e.RunWithErrorCheck(t, `Required flag "token" not set`, "neo-go", "wallet", "nep17", "import", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath) diff --git a/cli/options/options.go b/cli/options/options.go index e20365f4a..12c5db701 100644 --- a/cli/options/options.go +++ b/cli/options/options.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/pkg/config" @@ -86,9 +87,11 @@ var Network = []cli.Flag{ // RPC is a set of flags used for RPC connections (endpoint and timeout). var RPC = []cli.Flag{ &cli.StringFlag{ - Name: RPCEndpointFlag, - Aliases: []string{"r"}, - Usage: "RPC node address", + Name: RPCEndpointFlag, + Aliases: []string{"r"}, + Usage: "RPC node address", + Required: true, + Action: cmdargs.EnsureNotEmpty("rpc-endpoint"), }, &cli.DurationFlag{ Name: "timeout", @@ -131,7 +134,6 @@ var Debug = &cli.BoolFlag{ Usage: "Enable debug logging (LOTS of output, overrides configuration)", } -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") var errNoWallet = errors.New("no wallet parameter found, specify it with the '--wallet' or '-w' flag or specify wallet config file with the '--wallet-config' flag") var errConflictingWalletFlags = errors.New("--wallet flag conflicts with --wallet-config flag, please, provide one of them to specify wallet location") @@ -167,9 +169,6 @@ func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) { // GetRPCClient returns an RPC client instance for the given Context. func GetRPCClient(gctx context.Context, ctx *cli.Context) (*rpcclient.Client, cli.ExitCoder) { endpoint := ctx.String(RPCEndpointFlag) - if len(endpoint) == 0 { - return nil, cli.Exit(errNoEndpoint, 1) - } c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{}) if err != nil { return nil, cli.Exit(err, 1) diff --git a/cli/server/server.go b/cli/server/server.go index 5d2a50ae9..882423405 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -101,14 +101,14 @@ func NewCommands() []*cli.Command { { Name: "dump", Usage: "Dump blocks (starting with block #1) to the file", - UsageText: "neo-go db dump -o file [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]", + UsageText: "neo-go db dump [-o file] [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]", Action: dumpDB, Flags: cfgCountOutFlags, }, { Name: "restore", Usage: "Restore blocks from the file", - UsageText: "neo-go db restore -i file [--dump] [-n] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]", + UsageText: "neo-go db restore [-i file] [--dump] [-n] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]", Action: restoreDB, Flags: cfgCountInFlags, }, diff --git a/cli/smartcontract/contract_test.go b/cli/smartcontract/contract_test.go index 481c1a21b..e0fff3540 100644 --- a/cli/smartcontract/contract_test.go +++ b/cli/smartcontract/contract_test.go @@ -56,13 +56,13 @@ func TestCalcHash(t *testing.T) { cmd := []string{"neo-go", "contract", "calc-hash"} t.Run("no sender", func(t *testing.T) { - e.RunWithError(t, append(cmd, "--in", nefPath, "--manifest", manifestPath)...) + e.RunWithErrorCheck(t, `Required flag "sender" not set`, append(cmd, "--in", nefPath, "--manifest", manifestPath)...) }) t.Run("no nef file", func(t *testing.T) { - e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--manifest", manifestPath)...) + e.RunWithErrorCheck(t, `Required flag "in" not set`, append(cmd, "--sender", sender.StringLE(), "--manifest", manifestPath)...) }) t.Run("no manifest file", func(t *testing.T) { - e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...) + e.RunWithErrorCheck(t, `Required flag "manifest" not set`, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...) }) t.Run("invalid nef path", func(t *testing.T) { e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), @@ -289,7 +289,7 @@ func TestContractInitAndCompile(t *testing.T) { e := testcli.NewExecutor(t, false) t.Run("no path is provided", func(t *testing.T) { - e.RunWithError(t, "neo-go", "contract", "init") + e.RunWithErrorCheck(t, `Required flag "name" not set`, "neo-go", "contract", "init") }) t.Run("invalid path", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "init", "--name", "\x00") @@ -313,7 +313,7 @@ func TestContractInitAndCompile(t *testing.T) { manifestPath := filepath.Join(tmpDir, "testcontract.manifest.json") cmd := []string{"neo-go", "contract", "compile"} t.Run("missing source", func(t *testing.T) { - e.RunWithError(t, cmd...) + e.RunWithErrorCheck(t, `Required flag "in" not set`, cmd...) }) cmd = append(cmd, "--in", srcPath, "--out", nefPath, "--manifest", manifestPath) @@ -487,10 +487,10 @@ func TestDeployWithSigners(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "deploy", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, - "--in", "", "--manifest", manifestName) + "--in", nefName, "--manifest", manifestName) }) t.Run("missing manifest", func(t *testing.T) { - e.RunWithError(t, "neo-go", "contract", "deploy", + e.RunWithErrorCheck(t, "required flag --manifest is empty", "neo-go", "contract", "deploy", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", "") @@ -517,7 +517,7 @@ func TestDeployWithSigners(t *testing.T) { "[", "str1", "str2", "]") }) t.Run("missing RPC", func(t *testing.T) { - e.RunWithError(t, "neo-go", "contract", "deploy", + e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "contract", "deploy", "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName, "[", "str1", "str2", "]") @@ -548,28 +548,29 @@ func TestContractManifestGroups(t *testing.T) { "--out", nefName, "--manifest", manifestName) t.Run("missing wallet", func(t *testing.T) { - e.RunWithError(t, "neo-go", "contract", "manifest", "add-group") + e.RunWithErrorCheck(t, `Required flags "sender, address, nef, manifest" not set`, "neo-go", "contract", "manifest", "add-group") }) t.Run("invalid wallet", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", - "--wallet", t.TempDir()) + "--wallet", t.TempDir(), "--sender", testcli.TestWalletAccount, "--address", testcli.TestWalletAccount, + "--nef", nefName, "--manifest", manifestName) }) t.Run("invalid sender", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, - "--sender", "not-a-sender") + "--sender", "not-a-sender", "--nef", nefName, "--manifest", manifestName) }) t.Run("invalid NEF file", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, - "--sender", testcli.TestWalletAccount, "--nef", tmpDir) + "--sender", testcli.TestWalletAccount, "--nef", tmpDir, "--manifest", manifestName) }) t.Run("corrupted NEF file", func(t *testing.T) { f := filepath.Join(tmpDir, "invalid.nef") require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, - "--sender", testcli.TestWalletAccount, "--nef", f) + "--sender", testcli.TestWalletAccount, "--nef", f, "--manifest", manifestName) }) t.Run("invalid manifest file", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", @@ -630,9 +631,17 @@ func TestContract_TestInvokeScript(t *testing.T) { "--out", goodNef, "--manifest", manifestName) t.Run("missing in", func(t *testing.T) { - e.RunWithError(t, "neo-go", "contract", "testinvokescript", + e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "contract", "testinvokescript", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) }) + t.Run("empty in", func(t *testing.T) { + e.RunWithErrorCheck(t, "required flag --in is empty", "neo-go", "contract", "testinvokescript", "-i", "", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) + }) + t.Run("empty rpc", func(t *testing.T) { + e.RunWithErrorCheck(t, "required flag --rpc-endpoint is empty", "neo-go", "contract", "testinvokescript", "-i", goodNef, + "--rpc-endpoint", "") + }) t.Run("unexisting in", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "testinvokescript", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], @@ -723,7 +732,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { t.Run("check calc hash", func(t *testing.T) { // missing sender - e.RunWithError(t, "neo-go", "contract", "calc-hash", + e.RunWithErrorCheck(t, `Required flag "sender" not set`, "neo-go", "contract", "calc-hash", "--in", nefName, "--manifest", manifestName) @@ -755,7 +764,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.RunWithError(t, append(cmd, "--", "notahash")...) }) t.Run("missing RPC address", func(t *testing.T) { - e.RunWithError(t, "neo-go", "contract", "testinvokefunction", + e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "contract", "testinvokefunction", h.StringLE(), "getValue") }) @@ -1038,7 +1047,7 @@ func TestContractInspect(t *testing.T) { cmd := []string{"neo-go", "contract", "inspect"} t.Run("missing input", func(t *testing.T) { - e.RunWithError(t, cmd...) + e.RunWithErrorCheck(t, `Required flag "in" not set`, cmd...) }) t.Run("with raw '.go'", func(t *testing.T) { e.RunWithError(t, append(cmd, "--in", srcPath)...) diff --git a/cli/smartcontract/generate.go b/cli/smartcontract/generate.go index 7fdf115d4..03a2803ca 100644 --- a/cli/smartcontract/generate.go +++ b/cli/smartcontract/generate.go @@ -24,12 +24,14 @@ var generatorFlags = []cli.Flag{ Aliases: []string{"m"}, Required: true, Usage: "Read contract manifest (*.manifest.json) file", + Action: cmdargs.EnsureNotEmpty("manifest"), }, &cli.StringFlag{ Name: "out", Aliases: []string{"o"}, Required: true, Usage: "Output of the compiled wrapper", + Action: cmdargs.EnsureNotEmpty("out"), }, &cli.StringFlag{ Name: "hash", diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index 89391cd9d..19e1cdc1d 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -1,4 +1,4 @@ -package smartcontract +package smartcontract_test import ( "bytes" @@ -6,14 +6,13 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" + "github.com/nspcc-dev/neo-go/internal/testcli" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" - "github.com/urfave/cli/v2" ) func TestGenerate(t *testing.T) { @@ -124,8 +123,7 @@ func TestGenerate(t *testing.T) { 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, } - app := cli.NewApp() - app.Commands = []*cli.Command{generateWrapperCmd} + e := testcli.NewExecutor(t, false) rawCfg := `package: wrapper hash: ` + h.StringLE() + ` @@ -144,12 +142,12 @@ callflags: cfgPath := filepath.Join(t.TempDir(), "binding.yml") require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) - require.NoError(t, app.Run([]string{"", "generate-wrapper", + e.Run(t, []string{"", "contract", "generate-wrapper", "--manifest", manifestFile, "--config", cfgPath, "--out", outFile, "--hash", h.StringLE(), - })) + }...) const expected = `// Code generated by neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. @@ -234,11 +232,11 @@ func MyFunc(in map[int]mycontract.Input) []mycontract.Output { require.NoError(t, err) require.Equal(t, expected, string(data)) - require.NoError(t, app.Run([]string{"", "generate-wrapper", + e.Run(t, []string{"", "contract", "generate-wrapper", "--manifest", manifestFile, "--config", cfgPath, "--out", outFile, - })) + }...) expectedWithDynamicHash := `// Code generated by neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]; DO NOT EDIT. // Package wrapper contains wrappers for MyContract contract. @@ -350,13 +348,12 @@ func TestGenerateValidPackageName(t *testing.T) { 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, } - app := cli.NewApp() - app.Commands = []*cli.Command{generateWrapperCmd, generateRPCWrapperCmd} - require.NoError(t, app.Run([]string{"", "generate-wrapper", + e := testcli.NewExecutor(t, false) + e.Run(t, []string{"", "contract", "generate-wrapper", "--manifest", manifestFile, "--out", outFile, "--hash", "0x" + h.StringLE(), - })) + }...) data, err := os.ReadFile(outFile) require.NoError(t, err) @@ -378,11 +375,11 @@ func Get() int { return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int) } `, string(data)) - require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", + e.Run(t, []string{"", "contract", "generate-rpcwrapper", "--manifest", manifestFile, "--out", outFile, "--hash", "0x" + h.StringLE(), - })) + }...) data, err = os.ReadFile(outFile) require.NoError(t, err) @@ -431,17 +428,16 @@ const rewriteExpectedOutputs = false func TestGenerateRPCBindings(t *testing.T) { tmpDir := t.TempDir() - app := cli.NewApp() - app.Commands = []*cli.Command{generateWrapperCmd, generateRPCWrapperCmd} + e := testcli.NewExecutor(t, false) var checkBinding = func(manifest string, hash string, good string) { t.Run(manifest, func(t *testing.T) { outFile := filepath.Join(tmpDir, "out.go") - require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", + e.Run(t, []string{"", "contract", "generate-rpcwrapper", "--manifest", manifest, "--out", outFile, "--hash", hash, - })) + }...) data, err := os.ReadFile(outFile) require.NoError(t, err) @@ -478,8 +474,7 @@ func TestGenerateRPCBindings(t *testing.T) { func TestAssistedRPCBindings(t *testing.T) { tmpDir := t.TempDir() - app := cli.NewApp() - app.Commands = NewCommands() + e := testcli.NewExecutor(t, false) var checkBinding = func(source string, hasDefinedHash bool, guessEventTypes bool, suffix ...string) { testName := source @@ -510,7 +505,7 @@ func TestAssistedRPCBindings(t *testing.T) { if guessEventTypes { cmd = append(cmd, "--guess-eventtypes") } - require.NoError(t, app.Run(cmd)) + e.Run(t, cmd...) cmds := []string{"", "contract", "generate-rpcwrapper", "--config", bindingF, @@ -520,7 +515,7 @@ func TestAssistedRPCBindings(t *testing.T) { if hasDefinedHash { cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233") } - require.NoError(t, app.Run(cmds)) + e.Run(t, cmds...) data, err := os.ReadFile(outFile) require.NoError(t, err) @@ -548,28 +543,22 @@ func TestAssistedRPCBindings(t *testing.T) { } func TestGenerate_Errors(t *testing.T) { - app := cli.NewApp() - app.Commands = []*cli.Command{generateWrapperCmd} - app.ExitErrHandler = func(*cli.Context, error) {} + e := testcli.NewExecutor(t, false) + args := []string{"neo-go", "contract", "generate-wrapper"} - checkError := func(t *testing.T, msg string, args ...string) { - // cli.ExitError doesn't implement wraping properly, so we check for an error message. - err := app.Run(append([]string{"", "generate-wrapper"}, args...)) - require.True(t, strings.Contains(err.Error(), msg), "got: %v", err) - } t.Run("invalid hash", func(t *testing.T) { - checkError(t, "invalid contract hash", "--hash", "xxx", "--manifest", "yyy", "--out", "zzz") + e.RunWithErrorCheckExit(t, "invalid contract hash", append(args, "--hash", "xxx", "--manifest", "yyy", "--out", "zzz")...) }) t.Run("missing manifest argument", func(t *testing.T) { - checkError(t, "Required flag \"manifest\" not set", "--hash", util.Uint160{}.StringLE(), "--out", "zzz") + e.RunWithErrorCheck(t, `Required flag "manifest" not set`, append(args, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) }) t.Run("missing manifest file", func(t *testing.T) { - checkError(t, "can't read contract manifest", "--manifest", "notexists", "--hash", util.Uint160{}.StringLE(), "--out", "zzz") + e.RunWithErrorCheckExit(t, "can't read contract manifest", append(args, "--manifest", "notexists", "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) }) t.Run("empty manifest", func(t *testing.T) { manifestFile := filepath.Join(t.TempDir(), "invalid.json") require.NoError(t, os.WriteFile(manifestFile, []byte("[]"), os.ModePerm)) - checkError(t, "json: cannot unmarshal array into Go value of type manifest.Manifest", "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz") + e.RunWithErrorCheckExit(t, "json: cannot unmarshal array into Go value of type manifest.Manifest", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) }) t.Run("invalid manifest", func(t *testing.T) { manifestFile := filepath.Join(t.TempDir(), "invalid.json") @@ -577,7 +566,7 @@ func TestGenerate_Errors(t *testing.T) { rawManifest, err := json.Marshal(m) require.NoError(t, err) require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) - checkError(t, "ABI: no methods", "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz") + e.RunWithErrorCheckExit(t, "ABI: no methods", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...) }) manifestFile := filepath.Join(t.TempDir(), "manifest.json") @@ -593,9 +582,8 @@ func TestGenerate_Errors(t *testing.T) { require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) t.Run("missing config", func(t *testing.T) { - checkError(t, "can't read config file", - "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), - "--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz") + e.RunWithErrorCheckExit(t, "can't read config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), + "--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz")...) }) t.Run("invalid config", func(t *testing.T) { rawCfg := `package: wrapper @@ -605,23 +593,13 @@ callflags: cfgPath := filepath.Join(t.TempDir(), "binding.yml") require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) - checkError(t, "can't parse config file", - "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), - "--config", cfgPath, "--out", "zzz") + e.RunWithErrorCheckExit(t, "can't parse config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), + "--config", cfgPath, "--out", "zzz")...) }) } func TestCompile_GuessEventTypes(t *testing.T) { - app := cli.NewApp() - app.Commands = NewCommands() - app.ExitErrHandler = func(*cli.Context, error) {} - - checkError := func(t *testing.T, msg string, args ...string) { - // cli.ExitError doesn't implement wraping properly, so we check for an error message. - err := app.Run(args) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), msg), "got: %v", err) - } + e := testcli.NewExecutor(t, false) check := func(t *testing.T, source string, expectedErrText string) { tmpDir := t.TempDir() configFile := filepath.Join(source, "invalid.yml") @@ -636,7 +614,7 @@ func TestCompile_GuessEventTypes(t *testing.T) { "--out", nefF, "--guess-eventtypes", } - checkError(t, expectedErrText, cmd...) + e.RunWithErrorCheckExit(t, expectedErrText, cmd...) } t.Run("not declared in manifest", func(t *testing.T) { @@ -664,10 +642,7 @@ func TestCompile_GuessEventTypes(t *testing.T) { } func TestGenerateRPCBindings_Errors(t *testing.T) { - app := cli.NewApp() - app.Commands = NewCommands() - app.ExitErrHandler = func(*cli.Context, error) {} - + e := testcli.NewExecutor(t, false) t.Run("duplicating resulting fields", func(t *testing.T) { check := func(t *testing.T, packageName string, autogen bool, expectedError string) { tmpDir := t.TempDir() @@ -687,16 +662,14 @@ func TestGenerateRPCBindings_Errors(t *testing.T) { if autogen { cmd = append(cmd, "--guess-eventtypes") } - require.NoError(t, app.Run(cmd)) + e.Run(t, cmd...) cmds := []string{"", "contract", "generate-rpcwrapper", "--config", bindingF, "--manifest", manifestF, "--out", out, } - err := app.Run(cmds) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), expectedError), err.Error()) + e.RunWithErrorCheckExit(t, expectedError, cmds...) } t.Run("event", func(t *testing.T) { diff --git a/cli/smartcontract/manifest.go b/cli/smartcontract/manifest.go index f6488f2f3..3d71c4ddb 100644 --- a/cli/smartcontract/manifest.go +++ b/cli/smartcontract/manifest.go @@ -2,7 +2,6 @@ package smartcontract import ( "encoding/json" - "errors" "fmt" "os" @@ -75,10 +74,6 @@ func manifestAddGroup(ctx *cli.Context) error { } func readNEFFile(filename string) (*nef.File, []byte, error) { - if len(filename) == 0 { - return nil, nil, errors.New("no nef file was provided") - } - f, err := os.ReadFile(filename) if err != nil { return nil, nil, err @@ -96,10 +91,6 @@ func readNEFFile(filename string) (*nef.File, []byte, error) { // it for validness against the provided contract hash. If empty hash is specified // then no hash-related manifest groups check is performed. func readManifest(filename string, hash util.Uint160) (*manifest.Manifest, []byte, error) { - if len(filename) == 0 { - return nil, nil, errNoManifestFile - } - manifestBytes, err := os.ReadFile(filename) if err != nil { return nil, nil, err diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index ad80a3a9f..4ad20ea72 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -40,14 +40,11 @@ const ( ) var ( - errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag") - errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag") - errNoManifestFile = errors.New("no manifest file was found, specify manifest file with '--manifest' or '-m' flag") - errNoMethod = errors.New("no method specified for function invocation command") - errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") - errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag") - errFileExist = errors.New("A file with given smart-contract name already exists") - addressFlag = &flags.AddressFlag{ + errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag") + errNoMethod = errors.New("no method specified for function invocation command") + errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") + errFileExist = errors.New("A file with given smart-contract name already exists") + addressFlag = &flags.AddressFlag{ Name: addressFlagName, Aliases: []string{addressFlagAlias}, Usage: "Address to use as transaction signee (and gas source)", @@ -82,9 +79,11 @@ func RuntimeNotify(args []any) { func NewCommands() []*cli.Command { testInvokeScriptFlags := []cli.Flag{ &cli.StringFlag{ - Name: "in", - Aliases: []string{"i"}, - Usage: "Input location of the .nef file that needs to be invoked", + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input location of the .nef file that needs to be invoked", + Action: cmdargs.EnsureNotEmpty("in"), }, options.Historic, } @@ -103,36 +102,47 @@ func NewCommands() []*cli.Command { invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) deployFlags := append(invokeFunctionFlags, []cli.Flag{ &cli.StringFlag{ - Name: "in", - Aliases: []string{"i"}, - Usage: "Input file for the smart contract (*.nef)", + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file for the smart contract (*.nef)", + Action: cmdargs.EnsureNotEmpty("in"), }, &cli.StringFlag{ - Name: "manifest", - Aliases: []string{"m"}, - Usage: "Manifest input file (*.manifest.json)", + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Manifest input file (*.manifest.json)", + Action: cmdargs.EnsureNotEmpty("manifest"), }, }...) manifestAddGroupFlags := append([]cli.Flag{ &cli.StringFlag{ - Name: "sender", - Aliases: []string{"s"}, - Usage: "Deploy transaction sender", + Name: "sender", + Aliases: []string{"s"}, + Required: true, + Usage: "Deploy transaction sender", + Action: cmdargs.EnsureNotEmpty("sender"), }, &flags.AddressFlag{ - Name: addressFlagName, // use the same name for handler code unification. - Aliases: []string{addressFlagAlias}, - Usage: "Account to sign group with", + Name: addressFlagName, // use the same name for handler code unification. + Aliases: []string{addressFlagAlias}, + Required: true, + Usage: "Account to sign group with", }, &cli.StringFlag{ - Name: "nef", - Aliases: []string{"n"}, - Usage: "Path to the NEF file", + Name: "nef", + Aliases: []string{"n"}, + Required: true, + Usage: "Path to the NEF file", + Action: cmdargs.EnsureNotEmpty("nef"), }, &cli.StringFlag{ - Name: "manifest", - Aliases: []string{"m"}, - Usage: "Path to the manifest", + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Path to the manifest", + Action: cmdargs.EnsureNotEmpty("manifest"), }, }, options.Wallet...) return []*cli.Command{{ @@ -154,9 +164,11 @@ func NewCommands() []*cli.Command { Action: contractCompile, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "in", - Aliases: []string{"i"}, - Usage: "Input file for the smart contract to be compiled (*.go file or directory)", + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file for the smart contract to be compiled (*.go file or directory)", + Action: cmdargs.EnsureNotEmpty("in"), }, &cli.StringFlag{ Name: "out", @@ -273,9 +285,11 @@ func NewCommands() []*cli.Command { Action: initSmartContract, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "name", - Aliases: []string{"n"}, - Usage: "Name of the smart-contract to be initialized", + Name: "name", + Aliases: []string{"n"}, + Required: true, + Usage: "Name of the smart-contract to be initialized", + Action: cmdargs.EnsureNotEmpty("name"), }, &cli.BoolFlag{ Name: "skip-details", @@ -296,9 +310,11 @@ func NewCommands() []*cli.Command { Usage: "Compile input file (it should be go code then)", }, &cli.StringFlag{ - Name: "in", - Aliases: []string{"i"}, - Usage: "Input file of the program (either .go or .nef)", + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file of the program (either .go or .nef)", + Action: cmdargs.EnsureNotEmpty("in"), }, }, }, @@ -309,18 +325,23 @@ func NewCommands() []*cli.Command { Action: calcHash, Flags: []cli.Flag{ &flags.AddressFlag{ - Name: "sender", - Aliases: []string{"s"}, - Usage: "Sender script hash or address", + Name: "sender", + Aliases: []string{"s"}, + Required: true, + Usage: "Sender script hash or address", }, &cli.StringFlag{ - Name: "in", - Usage: "Path to NEF file", + Name: "in", + Required: true, + Usage: "Path to NEF file", + Action: cmdargs.EnsureNotEmpty("in"), }, &cli.StringFlag{ - Name: "manifest", - Aliases: []string{"m"}, - Usage: "Path to manifest file", + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Path to manifest file", + Action: cmdargs.EnsureNotEmpty("manifest"), }, }, }, @@ -347,9 +368,6 @@ func initSmartContract(ctx *cli.Context) error { return err } contractName := ctx.String("name") - if contractName == "" { - return cli.Exit(errNoSmartContractName, 1) - } // Check if the file already exists, if yes, exit if _, err := os.Stat(contractName); err == nil { @@ -424,9 +442,6 @@ func contractCompile(ctx *cli.Context) error { return err } src := ctx.String("in") - if len(src) == 0 { - return cli.Exit(errNoInput, 1) - } manifestFile := ctx.String("manifest") confFile := ctx.String("config") debugFile := ctx.String("debug") @@ -508,18 +523,9 @@ func calcHash(ctx *cli.Context) error { return err } sender := ctx.Generic("sender").(*flags.Address) - if !sender.IsSet { - return cli.Exit("sender is not set", 1) - } - p := ctx.String("in") - if p == "" { - return cli.Exit(errors.New("no .nef file was provided"), 1) - } mpath := ctx.String("manifest") - if mpath == "" { - return cli.Exit(errors.New("no manifest file provided"), 1) - } + f, err := os.ReadFile(p) if err != nil { return cli.Exit(fmt.Errorf("can't read .nef file: %w", err), 1) @@ -680,10 +686,6 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, func testInvokeScript(ctx *cli.Context) error { src := ctx.String("in") - if len(src) == 0 { - return cli.Exit(errNoInput, 1) - } - b, err := os.ReadFile(src) if err != nil { return cli.Exit(err, 1) @@ -739,9 +741,6 @@ func inspect(ctx *cli.Context) error { } in := ctx.String("in") compile := ctx.Bool("compile") - if len(in) == 0 { - return cli.Exit(errNoInput, 1) - } var ( b []byte err error diff --git a/cli/util/convert.go b/cli/util/convert.go index 2b7e80d73..c030aff35 100644 --- a/cli/util/convert.go +++ b/cli/util/convert.go @@ -16,7 +16,11 @@ import ( // NewCommands returns util commands for neo-go CLI. func NewCommands() []*cli.Command { - txDumpFlags := append([]cli.Flag{}, options.RPC...) + // By default, RPC flag is required. sendtx and txdump may be called without provided rpc-endpoint. + rpcFlagOriginal, _ := options.RPC[0].(*cli.StringFlag) + rpcFlag := *rpcFlagOriginal + rpcFlag.Required = false + txDumpFlags := append([]cli.Flag{&rpcFlag}, options.RPC[1:]...) txSendFlags := append(txDumpFlags, txctx.AwaitFlag) txCancelFlags := append([]cli.Flag{ &flags.AddressFlag{ diff --git a/cli/wallet/candidate_test.go b/cli/wallet/candidate_test.go index 15f75ddee..2585a4609 100644 --- a/cli/wallet/candidate_test.go +++ b/cli/wallet/candidate_test.go @@ -39,7 +39,7 @@ func TestRegisterCandidate(t *testing.T) { e.CheckEOF(t) // missing address - e.RunWithError(t, "neo-go", "wallet", "candidate", "register", + e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "candidate", "register", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet) @@ -131,7 +131,7 @@ func TestRegisterCandidate(t *testing.T) { }) // missing address - e.RunWithError(t, "neo-go", "wallet", "candidate", "unregister", + e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "candidate", "unregister", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet) // additional argument @@ -153,7 +153,7 @@ func TestRegisterCandidate(t *testing.T) { require.Equal(t, 0, len(vs)) // query voter: missing address - e.RunWithError(t, "neo-go", "query", "voter") + e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) // Excessive parameters. e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress, validatorAddress) e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something") diff --git a/cli/wallet/multisig_test.go b/cli/wallet/multisig_test.go index fad718c38..10e5b676d 100644 --- a/cli/wallet/multisig_test.go +++ b/cli/wallet/multisig_test.go @@ -79,10 +79,10 @@ func TestSignMultisigTx(t *testing.T) { "--out", txPath) // missing wallet - e.RunWithError(t, "neo-go", "wallet", "sign") + e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "wallet", "sign") // missing in - e.RunWithError(t, "neo-go", "wallet", "sign", + e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "wallet", "sign", "--wallet", wallet2Path) // missing address diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index a74e83077..7c4d97206 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -25,12 +25,14 @@ import ( func newNEP11Commands() []*cli.Command { maxIters := strconv.Itoa(config.DefaultMaxIteratorResultItems) tokenAddressFlag := &flags.AddressFlag{ - Name: "token", - Usage: "Token contract address or hash in LE", + Name: "token", + Usage: "Token contract address or hash in LE", + Required: true, } ownerAddressFlag := &flags.AddressFlag{ - Name: "address", - Usage: "NFT owner address or hash in LE", + Name: "address", + Usage: "NFT owner address or hash in LE", + Required: true, } tokenID := &cli.StringFlag{ Name: "id", @@ -71,7 +73,7 @@ func newNEP11Commands() []*cli.Command { { Name: "import", Usage: "Import NEP-11 token to a wallet", - UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint --timeout